Skip to content

Commit 023becf

Browse files
authored
[se0447] edits for Span proposal (#2579)
* [se0447] edit regarding _read accessor * [se0447] remove an aside * [se0447] small api update - remove `isWithin` - rename `indicesWithin()` to `indices(of:)`, and reverse polarity. * [se0447] update some doc-comments * [se0447] remove index validation utilities - plan to introduce an API on Range instead * [se0447] explicitly give `Span` an `Index` * [se0447] wordsmith around `unsafeLoad` * [se0447] add the `byteOffsets` property to `RawSpan`
1 parent a1022a7 commit 023becf

File tree

1 file changed

+36
-67
lines changed

1 file changed

+36
-67
lines changed

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

+36-67
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ extension Span where Element: ~Copyable {
8181
public var count: Int { get }
8282
public var isEmpty: Bool { get }
8383

84-
public subscript(_ position: Int) -> Element { _read }
84+
public typealias Index = Int
85+
public var indices: Range<Index> { get }
86+
87+
public subscript(_ index: Index) -> Element { _read }
8588
}
8689
```
8790

@@ -107,63 +110,43 @@ The following properties, functions and subscripts have direct counterparts in t
107110

108111
```swift
109112
extension Span where Element: ~Copyable {
113+
/// The number of initialized elements in the span.
110114
public var count: Int { get }
111-
public var isEmpty: Bool { get }
112115

113-
public subscript(_ position: Int) -> Element { _read }
116+
/// A Boolean value indicating whether the span is empty.
117+
public var isEmpty: Bool { get }
118+
119+
/// The type that represents a position in `Span`.
120+
public typealias Index = Int
121+
122+
/// The range of indices valid for this `Span`
123+
public var indices: Range<Index> { get }
124+
125+
/// Accesses the element at the specified position.
126+
public subscript(_ position: Index) -> Element { _read }
114127
}
115128
```
116129

117-
Note that we use a `_read` accessor for the subscript, a requirement in order to `yield` a borrowed non-copyable `Element` (see ["Coroutines"](#Coroutines).) This will be updated to a final syntax at a later time, understanding that we intend the replacement to be source-compatible.
130+
Note that we use a `_read` accessor for the subscript, a requirement in order to `yield` a borrowed non-copyable `Element` (see ["Coroutines"](#Coroutines).) This yields an element whose lifetime is scoped around this particular access, as opposed to matching the lifetime dependency of the `Span` itself. This is a language limitation we expect to resolve with a followup proposal introducing a new accessor model. The subscript will then be updated to use the new accessor semantics. We expect the updated accessor to be source-compatible, as it will provide a borrowed element with a wider lifetime than a `_read` accessor can provide.
118131

119132
##### Unchecked access to elements:
120133

121134
The `subscript` mentioned above has always-on bounds checking of its parameter, in order to prevent out-of-bounds accesses. We also want to provide unchecked variants as an alternative for cases where bounds-checking is proving costly, such as in tight loops:
122135

123136
```swift
124137
extension Span where Element: ~Copyable {
125-
// Unchecked subscripting and extraction
126138

127139
/// Accesses the element at the specified `position`.
128140
///
129141
/// This subscript does not validate `position`; this is an unsafe operation.
130142
///
131143
/// - Parameter position: The offset of the element to access. `position`
132144
/// must be greater or equal to zero, and less than `count`.
133-
public subscript(unchecked position: Int) -> Element { _read }
145+
public subscript(unchecked position: Index) -> Element { _read }
134146
}
135147
```
136148

137-
##### Index validation utilities:
138-
139-
Every time `Span` uses a position parameter, it checks for its validity, unless the parameter is marked with the word "unchecked". The validation is performed with these functions:
140-
141-
```swift
142-
extension Span where Element: ~Copyable {
143-
/// Return true if `index` is a valid offset into this `Span`
144-
///
145-
/// - Parameters:
146-
/// - index: an index to validate
147-
/// - Returns: true if `index` is a valid index
148-
public func boundsContain(_ index: Int) -> Bool
149-
150-
/// Return true if `indices` is a valid range of offsets into this `Span`
151-
///
152-
/// - Parameters:
153-
/// - indices: a range of indices to validate
154-
/// - Returns: true if `indices` is a valid range of indices
155-
public func boundsContain(_ indices: Range<Int>) -> Bool
156-
157-
/// Return true if `indices` is a valid range of offsets into this `Span`
158-
///
159-
/// - Parameters:
160-
/// - indices: a range of indices to validate
161-
/// - Returns: true if `indices` is a valid range of indices
162-
public func boundsContain(_ indices: ClosedRange<Int>) -> Bool
163-
}
164-
```
165-
166-
Note: these function names are not ideal.
149+
When using the unchecked subscript, the index must be known to be valid. While we are not proposing explicit index validation API on `Span` itself, its `indices` property can be use to validate a single index, in the form of the function `Range<Int>.contains(_: Int) -> Bool`. We expect that `Range` will also add efficient containment checking of a subrange's endpoints, which should be generally useful for index range validation in this and other contexts.
167150

168151
##### Identifying whether a `Span` is a subrange of another:
169152

@@ -174,21 +157,13 @@ extension Span where Element: ~Copyable {
174157
/// Returns true if the other span represents exactly the same memory
175158
public func isIdentical(to span: borrowing Self) -> Bool
176159

177-
/// Returns true if the memory represented by `self` is a subrange of
178-
/// the memory represented by `span`
179-
///
180-
/// Parameters:
181-
/// - span: a span of the same type as `self`
182-
/// Returns: whether `self` is a subrange of `span`
183-
public func isWithin(_ span: borrowing Self) -> Bool
184-
185-
/// Returns the offsets where the memory of `self` is located within
186-
/// the memory represented by `span`, or `nil`
160+
/// Returns the indices within `self` where the memory represented by `span`
161+
/// is located, or `nil` if `span` is not located within `self`.
187162
///
188163
/// Parameters:
189-
/// - span: a subrange of `self`
164+
/// - span: a span that may be a subrange of `self`
190165
/// Returns: A range of offsets within `self`, or `nil`
191-
public func indicesWithin(_ span: borrowing Self) -> Range<Int>?
166+
public func indices(of span: borrowing Self) -> Range<Index>?
192167
}
193168
```
194169

@@ -256,7 +231,7 @@ Initializers, required for library adoption, will be proposed alongside [lifetim
256231

257232
##### <a name="Load"></a>Accessing the memory of a `RawSpan`:
258233

259-
`RawSpan` has basic operations to access the contents of its memory: `unsafeLoad(as:)` and `unsafeLoadUnaligned(as:)`. These operations are not type-safe, in that the loaded value returned by the operation can be invalid. Some types have a property that makes this operation safe, but there we don't have a way to [formally identify](#SurjectiveBitPattern) such types at this time.
234+
`RawSpan` has basic operations to access the contents of its memory: `unsafeLoad(as:)` and `unsafeLoadUnaligned(as:)`:
260235

261236
```swift
262237
extension RawSpan {
@@ -302,7 +277,10 @@ extension RawSpan {
302277
) -> T
303278
```
304279

305-
These functions have counterparts which omit bounds-checking for cases where redundant checks affect performance:
280+
These operations are not type-safe, in that the loaded value returned by the operation can be invalid, and violate type invariants. Some types have a property that makes the `unsafeLoad(as:)` function safe, but we don't have a way to [formally identify](#SurjectiveBitPattern) such types at this time.
281+
282+
The `unsafeLoad` functions have counterparts which omit bounds-checking for cases where redundant checks affect performance:
283+
306284
```swift
307285
/// Returns a new instance of the given type, constructed from the raw memory
308286
/// at the specified offset.
@@ -382,20 +360,9 @@ extension RawSpan {
382360

383361
/// A Boolean value indicating whether the span is empty.
384362
public var isEmpty: Bool { get }
385-
}
386-
```
387-
388-
##### `RawSpan` bounds checking:
389-
```swift
390-
extension RawSpan {
391-
/// Return true if `offset` is a valid byte offset into this `RawSpan`
392-
public func boundsContain(_ offset: Int) -> Bool
393-
394-
/// Return true if `offsets` is a valid range of offsets into this `RawSpan`
395-
public func boundsContain(_ offsets: Range<Int>) -> Bool
396-
397-
/// Return true if `offsets` is a valid range of offsets into this `RawSpan`
398-
public func boundsContain(_ offsets: ClosedRange<Int>) -> Bool
363+
364+
/// The range of valid byte offsets into this `RawSpan`
365+
public var byteOffsets: Range<Int> { get }
399366
}
400367
```
401368

@@ -407,9 +374,7 @@ When working with multiple `RawSpan` instances, it is often desirable to know wh
407374
extension RawSpan {
408375
public func isIdentical(to span: borrowing Self) -> Bool
409376

410-
public func isWithin(_ span: borrowing Self) -> Bool
411-
412-
public func byteOffsetsWithin(_ span: borrowing Self) -> Range<Int>?
377+
public func byteOffsets(of span: borrowing Self) -> Range<Int>?
413378
}
414379
```
415380

@@ -501,6 +466,10 @@ extension Array where Element: BitwiseCopyable {
501466

502467
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.
503468

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+
504473
#### <a name="ContiguousStorage"></a>A `ContiguousStorage` protocol
505474

506475
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).

0 commit comments

Comments
 (0)