Skip to content

Commit 4e04b79

Browse files
authored
Merge pull request #2695 from glessard/stdlib-span-properties
[SE-0456] post review updates
2 parents 3225ef1 + ea5b8fa commit 4e04b79

File tree

1 file changed

+37
-38
lines changed

1 file changed

+37
-38
lines changed

proposals/0456-stdlib-span-properties.md

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
* Proposal: [SE-0456](0456-stdlib-span-properties.md)
44
* Author: [Guillaume Lessard](https://github.com/glessard)
55
* Review Manager: [Doug Gregor](https://github.com/DougGregor)
6-
* Status: **Active Review (January 15...28, 2024)**
6+
* Status: **Accepted**
77
* Roadmap: [BufferView Roadmap](https://forums.swift.org/t/66211)
88
* Implementation: [PR #78561](https://github.com/swiftlang/swift/pull/78561)
9-
* Review: [Review](https://forums.swift.org/t/se-0456-add-span-providing-properties-to-standard-library-types/77233), [Pitch](https://forums.swift.org/t/76138)
9+
* Review: ([pitch](https://forums.swift.org/t/76138)) ([review](https://forums.swift.org/t/se-0456-add-span-providing-properties-to-standard-library-types/77233)) ([acceptance](https://forums.swift.org/t/77684))
1010

1111
[SE-0446]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0446-non-escapable.md
1212
[SE-0447]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md
@@ -46,9 +46,9 @@ func function() {
4646
```
4747
If we were to attempt using `b` again after the call to `modify(&a)`, the compiler would report an overlapping access error, due to attempting to mutate `a` (with `modify(&a)`) while it is already being accessed through `b`'s borrow. Note that the copyability of `B` means that it cannot represent a mutation of `A`; it therefore represents a non-exclusive borrowing relationship.
4848

49-
Given this, we propose to enable the definition of a borrowing relationship via a computed property. With this feature we then propose to add `storage` computed properties to standard library types that can share their internal typed storage, as well as `bytes` computed properties to those standard library types that can safely share their internal storage as untyped memory.
49+
Given this, we propose to enable the definition of a borrowing relationship via a computed property. With this feature we then propose to add `span` computed properties to standard library types that can share access to their internal typed memory. When a `span` has `BitwiseCopyable` elements, it will have a `bytes` computed property to share a view of the memory it represents as untyped memory.
5050

51-
One of the purposes of `Span` is to provide a safer alternative to `UnsafeBufferPointer`. This proposal builds on it and allows us to rewrite code reliant on `withUnsafeBufferPointer()` to use `storage` properties instead. Eventually, code that requires access to contiguous memory can be rewritten to use `Span`, gaining better composability in the process. For example:
51+
One of the purposes of `Span` is to provide a safer alternative to `UnsafeBufferPointer`. This proposal builds on it and allows us to rewrite code reliant on `withUnsafeBufferPointer()` to use `span` properties instead. Eventually, code that requires access to contiguous memory can be rewritten to use `Span`, gaining better composability in the process. For example:
5252

5353
```swift
5454
let result = try myArray.withUnsafeBufferPointer { buffer in
@@ -63,7 +63,7 @@ let result = try myArray.withUnsafeBufferPointer { buffer in
6363
This closure-based call is difficult to evolve, such as making `result` have a non-copyable type, adding a concurrent task, or adding typed throws. An alternative based on a vended `Span` property would look like this:
6464

6565
```swift
66-
let span = myArray.storage
66+
let span = myArray.span
6767
let indices = findElements(span)
6868
var myResult = MyResult()
6969
for i in indices {
@@ -87,62 +87,57 @@ By allowing the language to define lifetime dependencies in these limited ways,
8787

8888
#### <a name="extensions"></a>Extensions to Standard Library types
8989

90-
The standard library and Foundation will provide `storage` computed properties, returning lifetime-dependent `Span` instances. These computed properties are the safe and composable replacements for the existing `withUnsafeBufferPointer` closure-taking functions.
90+
The standard library and Foundation will provide `span` computed properties, returning lifetime-dependent `Span` instances. These computed properties are the safe and composable replacements for the existing `withUnsafeBufferPointer` closure-taking functions.
9191

9292
```swift
9393
extension Array {
9494
/// Share this `Array`'s elements as a `Span`
95-
var storage: Span<Element> { get }
95+
var span: Span<Element> { get }
9696
}
9797

9898
extension ArraySlice {
9999
/// Share this `Array`'s elements as a `Span`
100-
var storage: Span<Element> { get }
100+
var span: Span<Element> { get }
101101
}
102102

103103
extension ContiguousArray {
104104
/// Share this `Array`'s elements as a `Span`
105-
var storage: Span<Element> { get }
105+
var span: Span<Element> { get }
106106
}
107107

108108
extension String.UTF8View {
109109
/// Share this `UTF8View`'s code units as a `Span`
110-
var storage: Span<Unicode.UTF8.CodeUnit> { get }
110+
var span: Span<Unicode.UTF8.CodeUnit> { get }
111111
}
112112

113113
extension Substring.UTF8View {
114114
/// Share this `UTF8View`'s code units as a `Span`
115-
var storage: Span<Unicode.UTF8.CodeUnit> { get }
115+
var span: Span<Unicode.UTF8.CodeUnit> { get }
116116
}
117117

118118
extension CollectionOfOne {
119119
/// Share this `Collection`'s element as a `Span`
120-
var storage: Span<Element> { get }
121-
}
122-
123-
extension SIMD_N_ { // where _N_ ∈ {2, 3, 4 ,8, 16, 32, 64}
124-
/// Share this vector's elements as a `Span`
125-
var storage: Span<Scalar> { get }
120+
var span: Span<Element> { get }
126121
}
127122

128123
extension KeyValuePairs {
129124
/// Share this `Collection`'s elements as a `Span`
130-
var storage: Span<(Key, Value)> { get }
125+
var span: Span<(Key, Value)> { get }
131126
}
132127
```
133128

134-
Conditionally to the acceptance of [`Vector`][SE-0453], we will also add the following:
129+
Following the acceptance of [`InlineArray`][SE-0453], we will also add the following:
135130

136131
```swift
137-
extension Vector where Element: ~Copyable {
138-
/// Share this vector's elements as a `Span`
139-
var storage: Span<Element> { get }
132+
extension InlineArray where Element: ~Copyable {
133+
/// Share this `InlineArray`'s elements as a `Span`
134+
var span: Span<Element> { get }
140135
}
141136
```
142137

143138
#### Accessing the raw bytes of a `Span`
144139

145-
When a `Span`'s element is `BitwiseCopyable`, we allow viewing the underlying storage as raw bytes with `RawSpan`:
140+
When a `Span`'s element is `BitwiseCopyable`, we allow viewing the underlying memory as raw bytes with `RawSpan`:
146141

147142
```swift
148143
extension Span where Element: BitwiseCopyable {
@@ -160,12 +155,12 @@ We hope that `Span` and `RawSpan` will become the standard ways to access shared
160155
```swift
161156
extension UnsafeBufferPointer {
162157
/// Unsafely view this buffer as a `Span`
163-
var storage: Span<Element> { get }
158+
var span: Span<Element> { get }
164159
}
165160

166161
extension UnsafeMutableBufferPointer {
167162
/// Unsafely view this buffer as a `Span`
168-
var storage: Span<Element> { get }
163+
var span: Span<Element> { get }
169164
}
170165

171166
extension UnsafeRawBufferPointer {
@@ -193,22 +188,22 @@ While the `swift-foundation` package and the `Foundation` framework are not gove
193188
```swift
194189
extension Foundation.Data {
195190
// Share this `Data`'s bytes as a `Span`
196-
var storage: Span<UInt8> { get }
191+
var span: Span<UInt8> { get }
197192

198193
// Share this `Data`'s bytes as a `RawSpan`
199194
var bytes: RawSpan { get }
200195
}
201196
```
202197

203-
Unlike with the standard library types, we plan to have a `bytes` property on `Foundation.Data` directly. This type conceptually consists of untyped bytes, and `bytes` is likely to be the primary way to directly access its memory. As `Data`'s API presents its storage as a collection of `UInt8` elements, we provide both `bytes` and `storage`. Types similar to `Data` may choose to provide both typed and untyped `Span` properties.
198+
Unlike with the standard library types, we plan to have a `bytes` property on `Foundation.Data` directly. This type conceptually consists of untyped bytes, and `bytes` is likely to be the primary way to directly access its memory. As `Data`'s API presents its storage as a collection of `UInt8` elements, we provide both `bytes` and `span`. Types similar to `Data` may choose to provide both typed and untyped `Span` properties.
204199

205200
#### <a name="performance"></a>Performance
206201

207-
The `storage` and `bytes` properties should be performant and return their `Span` or `RawSpan` with very little work, in O(1) time. This is the case for all native standard library types. There is a performance wrinkle for bridged `Array` and `String` instances on Darwin-based platforms, where they can be bridged to Objective-C types that do not guarantee contiguous storage. In such cases the implementation will eagerly copy the underlying data to the native Swift form, and return a `Span` or `RawSpan` pointing to that copy.
202+
The `span` and `bytes` properties should be performant and return their `Span` or `RawSpan` with very little work, in O(1) time. This is the case for all native standard library types. There is a performance wrinkle for bridged `Array` and `String` instances on Darwin-based platforms, where they can be bridged to Objective-C types that may not be represented in contiguous memory. In such cases the implementation will eagerly copy the underlying data to the native Swift form, and return a `Span` or `RawSpan` pointing to that copy.
208203

209-
This eager copy behaviour will be specific to the `storage` and `bytes` properties, and therefore the memory usage behaviour of existing unchanged code will remain the same. New code that adopts the `storage` and `bytes` properties will occasionally have higher memory usage due to the eager copies, but we believe this performance compromise is the right approach for the standard library. The alternative is to compromise the design for all platforms supported by Swift, and we consider that a non-starter.
204+
This eager copy behaviour will be specific to the `span` and `bytes` properties, and therefore the memory usage behaviour of existing unchanged code will remain the same. New code that adopts the `span` and `bytes` properties will occasionally have higher memory usage due to the eager copies, but we believe this performance compromise is the right approach for the standard library. The alternative is to compromise the design for all platforms supported by Swift, and we consider that a non-starter.
210205

211-
As a result of the eager copy behaviour for bridged `String.UTF8View` and `Array` instances, the `storage` property for these types will have a documented performance characteristic of "amortized constant time performance."
206+
As a result of the eager copy behaviour for bridged `String.UTF8View` and `Array` instances, the `span` property for these types will have a documented performance characteristic of "amortized constant time performance."
212207

213208
## Source compatibility
214209

@@ -226,10 +221,10 @@ The additions described in this proposal require a version of the Swift standard
226221

227222
#### Adding `withSpan()` and `withBytes()` closure-taking functions
228223

229-
The `storage` and `bytes` properties aim to be safe replacements for the `withUnsafeBufferPointer()` and `withUnsafeBytes()` closure-taking functions. We could consider `withSpan()` and `withBytes()` closure-taking functions that would provide an quicker migration away from the older unsafe functions. We do not believe the closure-taking functions are desirable in the long run. In the short run, there may be a desire to clearly mark the scope where a `Span` instance is used. The default method would be to explicitly consume a `Span` instance:
224+
The `span` and `bytes` properties aim to be safe replacements for the `withUnsafeBufferPointer()` and `withUnsafeBytes()` closure-taking functions. We could consider `withSpan()` and `withBytes()` closure-taking functions that would provide an quicker migration away from the older unsafe functions. We do not believe the closure-taking functions are desirable in the long run. In the short run, there may be a desire to clearly mark the scope where a `Span` instance is used. The default method would be to explicitly consume a `Span` instance:
230225
```swift
231226
var a = ContiguousArray(0..<8)
232-
var span = a.storage
227+
var span = a.span
233228
read(span)
234229
_ = consume span
235230
a.append(8)
@@ -239,7 +234,7 @@ In order to visually distinguish this lifetime, we could simply use a `do` block
239234
```swift
240235
var a = ContiguousArray(0..<8)
241236
do {
242-
let span = a.storage
237+
let span = a.span
243238
read(span)
244239
}
245240
a.append(8)
@@ -248,7 +243,7 @@ a.append(8)
248243
A more targeted solution may be a consuming function that takes a non-escaping closure:
249244
```swift
250245
var a = ContiguousArray(0..<8)
251-
var span = a.storage
246+
var span = a.span
252247
consuming(span) { span in
253248
read(span)
254249
}
@@ -257,9 +252,9 @@ a.append(8)
257252

258253
During the evolution of Swift, we have learned that closure-based API are difficult to compose, especially with one another. They can also require alterations to support new language features. For example, the generalization of closure-taking API for non-copyable values as well as typed throws is ongoing; adding more closure-taking API may make future feature evolution more labor-intensive. By instead relying on returned values, whether from computed properties or functions, we build for greater composability. Use cases where this approach falls short should be reported as enhancement requests or bugs.
259254

260-
#### Giving the properties different names
255+
#### Different naming for the properties
261256

262-
We chose the names `storage` and `bytes` because those reflect _what_ they represent. Another option would be to name the properties after _how_ they represent what they do, which would be `span` and `rawSpan`. It is possible the name `storage` would be deemed to clash too much with existing properties of types that would like to provide views of their internal storage with `Span`-providing properties. For example, the Standard Library's concrete `SIMD`-conforming types have a property `var _storage`. The current proposal means that making this property of `SIMD` types into public API would entail a name change more significant than simply removing its leading underscore.
257+
We originally proposed the name `storage` for the `span` properties introduced here. That name seems to imply that the returned `Span` is the storage itself, rather than a view of the storage. That would be misleading for types that own their storage, especially those that delegate their storage to another type, such as a `ContiguousArray`. In such cases, it would make sense to have a `storage` property whose type is the type that implements the storage.
263258

264259
#### Disallowing the definition of non-escapable properties of non-escapable types
265260

@@ -269,15 +264,15 @@ The original version of this pitch disallowed this. As a consequence, the `bytes
269264

270265
#### Omitting extensions to `UnsafeBufferPointer` and related types
271266

272-
We could omit the extensions to `UnsafeBufferPointer` and related types, and rely instead of future `Span` and `RawSpan` initializers. The initializers can have the advantage of being able to communicate semantics (somewhat) through their parameter labels. However, they also have a very different shape than the `storage` computed properties we are proposing. We believe that the adding the same API on both safe and unsafe types is advantageous, even if the preconditions for the properties cannot be statically enforced.
267+
We could omit the extensions to `UnsafeBufferPointer` and related types, and rely instead of future `Span` and `RawSpan` initializers. The initializers can have the advantage of being able to communicate semantics (somewhat) through their parameter labels. However, they also have a very different shape than the `span` computed properties we are proposing. We believe that the adding the same API on both safe and unsafe types is advantageous, even if the preconditions for the properties cannot be statically enforced.
273268

274269
## <a name="directions"></a>Future directions
275270

276271
Note: The future directions stated in [SE-0447](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md#Directions) apply here as well.
277272

278273
#### <a name="MutableSpan"></a>Safe mutations with `MutableSpan<T>`
279274

280-
Some data structures can delegate mutations of their owned memory. In the standard library the function `withMutableBufferPointer()` provides this functionality in an unsafe manner. We expect to add a `MutableSpan` type to support delegating mutations of initialized memory. Standard library types will then add a way to vend `MutableSpan` instances. This could be with a closure-taking `withMutableSpan()` function, or a new property, such as `var mutableStorage`. Note that a computed property providing mutable access needs to have a different name than the `storage` properties proposed here, because we cannot overload the return type of computed properties based on whether mutation is desired.
275+
Some data structures can delegate mutations of their owned memory. In the standard library the function `withMutableBufferPointer()` provides this functionality in an unsafe manner. We expect to add a `MutableSpan` type to support delegating mutations of initialized memory. Standard library types will then add a way to vend `MutableSpan` instances. This could be with a closure-taking `withMutableSpan()` function, or a new property, such as `var mutableStorage`. Note that a computed property providing mutable access needs to have a different name than the `span` properties proposed here, because we cannot overload the return type of computed properties based on whether mutation is desired.
281276

282277
#### <a name="ContiguousStorage"></a>A `ContiguousStorage` protocol
283278

@@ -289,6 +284,10 @@ Unfortunately, a major issue prevents us from proposing it at this time: the abi
289284

290285
The other limitation stated in [SE-0447][SE-0447]'s section about `ContiguousStorage` is "the inability to declare a `_read` acessor as a protocol requirement." This proposal's addition to enable defining a borrowing relationship via a computed property is a solution to that, as long as we don't need to use a coroutine accessor to produce a `Span`. While allowing the return of `Span`s through coroutine accessors may be undesirable, whether it is undesirable is unclear until coroutine accessors are formalized in the language.
291286

287+
<a name="simd"></a>`span` properties on standard library SIMD types
288+
289+
This proposal as reviewed included `span` properties for the standard library `SIMD` types. We are deferring this feature at the moment, since it is difficult to define these succinctly. The primary issue is that the `SIMD`-related protocols do not explicitly require contiguous memory; assuming that they are represented in contiguous memory fails with theoretically-possible examples. We could define the `span` property systematically for each concrete SIMD type in the standard library, but that would be very repetitive (and expensive from the point of view of code size.) We could also fix the SIMD protocols to require contiguous memory, enabling a succinct definition of their `span` property. Finally, we could also rely on converting `SIMD` types to `InlineArray`, and use the `span` property defined on `InlineArray`.
290+
292291
## Acknowledgements
293292

294293
Thanks to Ben Rimmington for suggesting that the `bytes` property should be on `Span` rather than on every type.

0 commit comments

Comments
 (0)