Skip to content

Commit 06d3b8f

Browse files
authored
[SE-0447] Span proposal edits (#2738)
* [se0447] reorder future-direction sections - “Index Validation Utilites” was mistakenly inserted between two other sections that should remain contiguous. * [se0447] use the indices property in an example * [se0447] add omitted `Sendable` conformances - while these were discussed in the form threads and were in the prototype implementations, they weren’t in this document. * [se0447] fix typos * [se0447] address telemetry domain’s Span naming * [se0447] discuss sendability of RawSpan
1 parent e82773f commit 06d3b8f

File tree

1 file changed

+17
-7
lines changed

1 file changed

+17
-7
lines changed

proposals/0447-span-access-shared-contiguous-storage.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ A `RawSpan` can be obtained from containers of `BitwiseCopyable` elements, as we
6666

6767
```swift
6868
@frozen
69-
public struct Span<Element: ~Copyable & ~Escapable>: Copyable, ~Escapable {
69+
public struct Span<Element: ~Copyable>: Copyable, ~Escapable {
7070
internal var _start: UnsafeRawPointer?
7171
internal var _count: Int
7272
}
73+
74+
extension Span: Sendable where Element: Sendable & ~Copyable {}
7375
```
7476

7577
We store a `UnsafeRawPointer` value internally in order to explicitly support reinterpreted views of memory as containing different types of `BitwiseCopyable` elements. Note that the the optionality of the pointer does not affect usage of `Span`, since accesses are bounds-checked and the pointer is only dereferenced when the `Span` isn't empty, and the pointer cannot be `nil`.
@@ -95,7 +97,7 @@ Like `UnsafeBufferPointer`, `Span` uses a simple offset-based indexing. The firs
9597
As a side-effect of not conforming to `Collection` or `Sequence`, `Span` is not directly supported by `for` loops at this time. It is, however, easy to use in a `for` loop via indexing:
9698

9799
```swift
98-
for i in 0..<mySpan.count {
100+
for i in mySpan.indices {
99101
calculation(mySpan[i])
100102
}
101103
```
@@ -225,6 +227,8 @@ public struct RawSpan: Copyable, ~Escapable {
225227
internal var _start: UnsafeRawPointer
226228
internal var _count: Int
227229
}
230+
231+
extension RawSpan: Sendable {}
228232
```
229233

230234
Initializers, required for library adoption, will be proposed alongside [lifetime annotations][PR-2305]; for details, see "[Initializers](#Initializers)" in the [future directions](#Directions) section.
@@ -402,6 +406,12 @@ A non-escapable index type implies that any indexing operation would borrow its
402406

403407
The ideas in this proposal previously used the name `BufferView`. While the use of the word "buffer" would be consistent with the `UnsafeBufferPointer` type, it is nevertheless not a great name, since "buffer" is commonly used in reference to transient storage. Another previous pitch used the term `StorageView` in reference to the `withContiguousStorageIfAvailable()` standard library function. We also considered the name `StorageSpan`, but that did not add much beyond the shorter name `Span`. `Span` clearly identifies itself as a relative of C++'s `std::span`.
404408

409+
The OpenTelemetry project and its related libraries use the word "span" for a concept of a timespan. The domains of use between that and direct memory access are very distinct, and we believe that the confusability between the use cases should be low. We also note that standard library type names can always be shadowed by type names from packages, mitigating the risk of source breaks.
410+
411+
##### <a name="Sendability"></a>Sendability of `RawSpan`
412+
413+
This proposal makes `RawSpan` a `Sendable` type. We believe this is the right decision. The sendability of `RawSpan` could be used to unsafely transfer a pointer value across an isolation boundary, despite the non-sendability of pointers. For example, suppose a `RawSpan` were obtained from an existing `Array<UnsafeRawPointer>` variable. We could send the `RawSpan` across the isolation boundary, and there extract the pointer using `rawSpan.unsafeLoad(as: UnsafeRawPointer.self)`. While this is an unsafe outcome, a similar operation can be done encoding a pointer as an `Int`, and then using `UnsafeRawPointer(bitPattern: mySentInt)` on the other side of the isolation boundary.
414+
405415
##### A more sophisticated approach to indexing
406416

407417
This is discussed more fully in the [indexing appendix](#Indexing) below.
@@ -444,7 +454,7 @@ This proposal includes some `_read` accessors, the coroutine version of the `get
444454

445455
#### Extensions to Standard Library and Foundation types
446456

447-
The standard library and Foundation has a number of types that can in principle provide access to their internal storage as a `Span`. We could provide `withSpan()` and `withBytes()` closure-taking functions as safe replacements for the existing `withUnsafeBufferPointer()` and `withUnsafeBytes()` functions. We could also also provide lifetime-dependent `span` or `bytes` properties. For example, `Array` could be extended as follows:
457+
The standard library and Foundation has a number of types that can in principle provide access to their internal storage as a `Span`. We could provide `withSpan()` and `withBytes()` closure-taking functions as safe replacements for the existing `withUnsafeBufferPointer()` and `withUnsafeBytes()` functions. We could also provide lifetime-dependent `span` or `bytes` properties. For example, `Array` could be extended as follows:
448458

449459
```swift
450460
extension Array {
@@ -466,10 +476,6 @@ extension Array where Element: BitwiseCopyable {
466476

467477
Of these, the closure-taking functions can be implemented now, but it is unclear whether they are desirable. The lifetime-dependent computed properties require lifetime annotations, as initializers do. We are deferring proposing these extensions until the lifetime annotations are proposed.
468478

469-
#### Index Validation Utilities
470-
471-
This proposal originally included index validation utilities for `Span`. such as `boundsContain(_: Index) -> Bool` and `boundsContain(_: Range<Index>) -> Bool`. After review feedback, we believe that the utilities proposed would also be useful for index validation on `UnsafeBufferPointer`, `Array`, and other similar `RandomAccessCollection` types. `Range` already a single-element `contains(_: Bound) -> Bool` function which can be made even more efficient. We should add an additional function that identifies whether a `Range` contains the _endpoints_ of another `Range`. Note that this is not the same as the existing `contains(_: some Collection<Bound>) -> Bool`, which is about the _elements_ of the collection. This semantic difference can lead to different results when examing empty `Range` instances.
472-
473479
#### <a name="ContiguousStorage"></a>A `ContiguousStorage` protocol
474480

475481
An earlier version of this proposal proposed a `ContiguousStorage` protocol by which a type could indicate that it can provide a `Span`. `ContiguousStorage` would form a bridge between generically-typed interfaces and a performant concrete implementation. It would supersede the rejected [SE-0256](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0256-contiguous-collection.md).
@@ -495,6 +501,10 @@ Two issues prevent us from proposing it at this time: (a) the ability to suppres
495501

496502
Many of the standard library collections could conform to `ContiguousStorage`.
497503

504+
#### Index Validation Utilities
505+
506+
This proposal originally included index validation utilities for `Span`. such as `boundsContain(_: Index) -> Bool` and `boundsContain(_: Range<Index>) -> Bool`. After review feedback, we believe that the utilities proposed would also be useful for index validation on `UnsafeBufferPointer`, `Array`, and other similar `RandomAccessCollection` types. `Range` already a single-element `contains(_: Bound) -> Bool` function which can be made even more efficient. We should add an additional function that identifies whether a `Range` contains the _endpoints_ of another `Range`. Note that this is not the same as the existing `contains(_: some Collection<Bound>) -> Bool`, which is about the _elements_ of the collection. This semantic difference can lead to different results when examining empty `Range` instances.
507+
498508
#### Support for `Span` in `for` loops
499509

500510
This proposal does not define an `IteratorProtocol` conformance, since an iterator for `Span` would need to be non-escapable. This is not compatible with `IteratorProtocol`. As such, `Span` is not directly usable in `for` loops as currently defined. A `BorrowingIterator` protocol for non-escapable and non-copyable containers must be defined, providing a `for` loop syntax where the element is borrowed through each iteration. Ultimately we should arrive at a way to iterate through borrowed elements from a borrowed view:

0 commit comments

Comments
 (0)