Skip to content

Commit f32328a

Browse files
authored
Convert the bytes iterator guide to a proposal (apple#187)
1 parent 3890885 commit f32328a

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

Evolution/NNNN-bytes.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# AsyncBufferedByteIterator
2+
3+
* Proposal: [SAA-NNNN](https://github.com/apple/swift-async-algorithms/blob/main/Evolution/NNNN-bytes.md)
4+
* Authors: [David Smith](https://github.com/Catfish-Man), [Philippe Hausler](https://github.com/phausler)
5+
* Status: **Implemented**
6+
* Implementation: [[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncBufferedByteIterator.swift) |
7+
[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestBufferedByteIterator.swift)]
8+
9+
## Introduction
10+
11+
Sources of bytes are a common point of asynchrony; reading from files, reading from the network, or other such tasks. Having an easy to use, uniform, and performant utility to make this approachable is key to unlocking highly scalable byte handling. This has proven useful for `FileHandle`, `URL`, and a number of others in Foundation.
12+
13+
This type provides infrastructure for creating `AsyncSequence` types with an `Element` of `UInt8` backed by file descriptors or similar read sources.
14+
15+
```swift
16+
struct AsyncBytes: AsyncSequence {
17+
public typealias Element = UInt8
18+
var handle: ReadableThing
19+
20+
internal init(_ readable: ReadableThing) {
21+
handle = readable
22+
}
23+
24+
public func makeAsyncIterator() -> AsyncBufferedByteIterator {
25+
return AsyncBufferedByteIterator(capacity: 16384) { buffer in
26+
// This runs once every 16384 invocations of next()
27+
return try await handle.read(into: buffer)
28+
}
29+
}
30+
}
31+
```
32+
33+
## Detailed Design
34+
35+
```swift
36+
public struct AsyncBufferedByteIterator: AsyncIteratorProtocol, Sendable {
37+
public typealias Element = UInt8
38+
39+
public init(
40+
capacity: Int,
41+
readFunction: @Sendable @escaping (UnsafeMutableRawBufferPointer) async throws -> Int
42+
)
43+
44+
public mutating func next() async throws -> UInt8?
45+
}
46+
```
47+
48+
For each invocation of `next`, the iterator will check if a buffer has been filled. If the buffer is filled with some amount of bytes, a fast path is taken to directly return a byte out of that buffer. If the buffer is not filled, the read function is invoked to acquire the next filled buffer, at which point it takes a byte out of that buffer.
49+
50+
If the read function returns `0`, indicating it didn't read any more bytes, the iterator is decided to be finished and no additional invocations to the read function are made.
51+
52+
If the read function throws, the error will be thrown by the iteration. Subsequent invocations to the iterator will then return `nil` without invoking the read function.
53+
54+
If the task is cancelled during the iteration, the iteration will check the cancellation only in passes where the read function is invoked, and will throw a `CancellationError`.
55+
56+
### Naming
57+
58+
This type was named precisely for what it does: it is an asynchronous iterator that buffers bytes.
59+

0 commit comments

Comments
 (0)