Skip to content

Commit 2f625f9

Browse files
committed
Edits, feedback responses
1 parent e8c98f0 commit 2f625f9

File tree

1 file changed

+58
-37
lines changed

1 file changed

+58
-37
lines changed

proposals/nnnn-MutableSpan.md

+58-37
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
[SE-0447]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md
1313
[SE-0456]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0456-stdlib-span-properties.md
1414
[PR-2305]: https://github.com/swiftlang/swift-evolution/pull/2305
15+
[SE-0437]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0437-noncopyable-stdlib-primitives.md
1516
[SE-0453]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0453-vector.md
1617
[SE-0223]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0223-array-uninitialized-initializer.md
1718
[SE-0176]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0176-enforce-exclusive-access-to-memory.md
@@ -29,13 +30,13 @@ These functions have a few different drawbacks, most prominently their reliance
2930
In addition to the new types, we will propose adding new API some standard library types to take advantage of `MutableSpan` and `MutableRawSpan`.
3031

3132
## Proposed solution
32-
We previously introduced `Span` to provide shared read-only access to containers. A question can be raised as to whether this same type could be used for mutations. We cannot, due to the [law of exclusivity][SE-0176]. `Span` is copyable, and it should be copyable in order to properly model read access under the law of exclusivity: a value can be simultaneously accessed through multiple read-only accesses. Mutations, on the other hand, require _exclusive access_. Exclusive access cannot be modeled through a copyable type, since a copy of the value representing the access would violate exclusivity. We therefore need a type separate from `Span` in order to model mutations.
33+
We introduced `Span` to provide shared read-only access to containers. We cannot use `Span` to also model container mutations, due to the [law of exclusivity][SE-0176]. `Span` is copyable, and must be copyable in order to properly model read access under the law of exclusivity: a value can be simultaneously accessed through multiple read-only accesses. Mutations, on the other hand, require _exclusive access_. Exclusive access cannot be modeled with a copyable type, since a copy of the value representing the access would violate exclusivity by adding a second access. We therefore need a non-copyable type separate from `Span` in order to model mutations.
3334

3435
#### MutableSpan
3536

36-
`MutableSpan` allows delegating mutations of a type's contiguous internal representation, by providing access to an exclusively-borrowed view of a range of contiguous, initialized memory. `MutableSpan` relies on guarantees that it has exclusive access to the range of memory it represents, and that the memory it represents will remain valid for the duration of the access. These guarantee data race safety and temporal safety. Like `Span`, `MutableSpan` performs bounds-checking on every access to preserve spatial safety.
37+
`MutableSpan` allows delegating mutations of a type's contiguous internal representation, by providing access to an exclusively-borrowed view of a range of contiguous, initialized memory. `MutableSpan` relies on guarantees that it has exclusive access to the range of memory it represents, and that the memory it represents will remain valid for the duration of the access. These provide data race safety and temporal safety. Like `Span`, `MutableSpan` performs bounds-checking on every access to preserve spatial safety.
3738

38-
A `MutableSpan` provided by a container represents a mutation of that container, via an exclusive borrow. Mutations are implemented by mutating operations, which let the compiler statically enforce exclusivity.
39+
A `MutableSpan` provided by a container represents a mutation of that container, via an exclusive borrow. Mutations are implemented by mutating functions and subscripts, which let the compiler statically enforce exclusivity.
3940

4041
#### MutableRawSpan
4142

@@ -55,7 +56,7 @@ func(_ array: inout Array<Int>) {
5556
}
5657
```
5758

58-
These computed properties represent a case of lifetime relationships between two bindings that wasn't covered in [SE-0456][SE-0456]. There we defined lifetime relationships for computed property getters of non-escapable and copyable types (`~Escapable & Copyable`). We now need to define them for properties of non-escapable and non-copyable types (`~Escapable & ~Copyable`). A `~Escapable & ~Copyable` value borrows another binding; if this borrow is also a mutation then it is an exclusive borrow. The scope of the borrow, whether or not it is exclusive, extends until the last use of the dependent binding.
59+
These computed properties represent a case of lifetime relationships not covered in [SE-0456][SE-0456]. In SE-0456 we defined lifetime relationships for computed property getters of non-escapable and copyable types (`~Escapable & Copyable`). We propose defining them for properties of non-escapable and non-copyable types (`~Escapable & ~Copyable`). A `~Escapable & ~Copyable` value borrows another binding; if this borrow is also a mutation then it is an exclusive borrow. The scope of the borrow, whether or not it is exclusive, extends until the last use of the dependent binding.
5960

6061
## Detailed Design
6162

@@ -75,6 +76,8 @@ extension MutableSpan: @unchecked Sendable where Element: Sendable {}
7576

7677
We store a `UnsafeMutableRawPointer` 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 `MutableSpan`, since accesses are bounds-checked and the pointer is only dereferenced when the `MutableSpan` isn't empty, when the pointer cannot be `nil`.
7778

79+
Initializers, required for library adoption, will be proposed alongside [lifetime annotations][PR-2305]; for details, see "[Initializers](#initializers)" in the [future directions](#Directions) section.
80+
7881
```swift
7982
extension MutableSpan where Element: ~Copyable {
8083
/// The number of initialized elements in this `MutableSpan`.
@@ -90,10 +93,14 @@ extension MutableSpan where Element: ~Copyable {
9093
var indices: Range<Index> { get }
9194

9295
/// Accesses the element at the specified position.
93-
subscript(_ index: Index) -> Element { borrowing read; mutate }
96+
subscript(_ index: Index) -> Element { borrow; mutate }
97+
// accessor syntax from accessors roadmap (https://forums.swift.org/t/76707)
9498

9599
/// Exchange the elements at the two given offsets
96100
mutating func swapAt(_ i: Index, _ j: Index)
101+
102+
/// Borrow the underlying memory for read-only access
103+
var span: Span<Element> { borrowing get }
97104
}
98105
```
99106

@@ -111,24 +118,33 @@ for i in myMutableSpan.indices {
111118
}
112119
```
113120

114-
##### `MutableSpan` API:
121+
##### Unchecked access to elements:
115122

116-
Initializers, required for library adoption, will be proposed alongside [lifetime annotations][PR-2305]; for details, see "[Initializers](#initializers)" in the [future directions](#Directions) section.
123+
The `subscript` mentioned above always checks the bounds of the `MutableSpan` before allowing access to the memory, preventing out-of-bounds accesses. We also provide an unchecked variant of the `subscript` and of the `swapAt` function as an alternative for situations where bounds-checking is costly and has already been performed:
117124

118125
```swift
119126
extension MutableSpan where Element: ~Copyable {
127+
/// Accesses the element at the specified `position`.
128+
///
129+
/// This subscript does not validate `position`; this is an unsafe operation.
130+
///
131+
/// - Parameter position: The offset of the element to access. `position`
132+
/// must be greater or equal to zero, and less than `count`.
120133
@unsafe
121-
subscript(unchecked position: Index) -> Element { borrowing read; mutate }
134+
subscript(unchecked position: Index) -> Element { borrow; mutate }
122135

136+
/// Exchange the elements at the two given offsets
137+
///
138+
/// This function does not validate `i` or `j`; this is an unsafe operation.
123139
@unsafe
124-
mutating func swapAt(unchecked i: Index, unchecked j: Index)
125-
126-
var storage: Span<Element> { borrowing get }
140+
mutating func swapAt(unchecked i: Index, unchecked j: Index)
127141
}
128142
```
129143
##### Bulk updating of a `MutableSpan`'s elements:
130144

131-
We include functions to perform bulk copies into the memory represented by a `MutableSpan`. Updating a `MutableSpan` from known-sized sources (such as `Collection` or `Span`) copies every element of a source. It is an error to do so when there is the span is too short to contain every element from the source. Updating a `MutableSpan` from `Sequence` or `IteratorProtocol` instances will copy as many items as possible, either until the input is empty or until the operation has updated the item at the last index.
145+
We include functions to perform bulk copies of elements into the memory represented by a `MutableSpan`. Updating a `MutableSpan` from known-sized sources (such as `Collection` or `Span`) copies every element of a source. It is an error to do so when there is the span is too short to contain every element from the source. Updating a `MutableSpan` from `Sequence` or `IteratorProtocol` instances will copy as many items as possible, either until the input is empty or until the operation has updated the item at the last index.
146+
147+
<a name="slicing"></a>**Note:** This set of functions is sufficiently complete in functionality, but uses a minimal approach to slicing. This is only one of many possible approaches to slicing `MutableSpan`. We could revive the option of using a `some RangeExpression` parameter, or we could use the return value of a `func extracting(_: some RangeExpression)` such as was [recently added][SE-0437] to `UnsafeBufferPointer`. The latter option in combination with `mutating` functions requires the use of intermediate bindings. This section may change in response to feedback and our investigations.
132148

133149
```swift
134150
extension MutableSpan {
@@ -163,6 +179,7 @@ extension MutableSpan {
163179
) -> Index
164180
}
165181

182+
extension MutableSpan where Element: ~Copyable {
166183
mutating func moveUpdate(
167184
startingAt offset: Index = 0,
168185
fromContentsOf source: UnsafeMutableBufferPointer<Element>
@@ -203,7 +220,7 @@ These functions use a closure to define the scope of validity of `buffer`, ensur
203220

204221
#### MutableRawSpan
205222

206-
`MutableRawSpan` is similar to `MutableSpan<T>`, but reperesents untyped initialized bytes. `MutableRawSpan` specifically supports encoding and decoding applications. Its API supports `unsafeLoad(as:)` and `storeBytes(of: as:)`, as well as a variety of bulk copying operations.
223+
`MutableRawSpan` is similar to `MutableSpan<T>`, but represents untyped initialized bytes. `MutableRawSpan` specifically supports encoding and decoding applications. Its API supports `unsafeLoad(as:)` and `storeBytes(of: as:)`, as well as a variety of bulk copying operations.
207224

208225
##### `MutableRawSpan` API:
209226

@@ -230,12 +247,26 @@ extension MutableRawSpan {
230247
/// The range of valid byte offsets into this `RawSpan`
231248
var byteOffsets: Range<Int> { get }
232249
}
233-
234250
```
235251

236252
##### Accessing and modifying the memory of a `MutableRawSpan`:
237253

238-
The basic operations available on `RawSpan` are available for `MutableRawSpan`. 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](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md#SurjectiveBitPattern) such types at this time.
254+
`MutableRawSpan` supports storing the bytes of a `BitwiseCopyable` value to its underlying memory:
255+
256+
```swift
257+
extension MutableRawSpan {
258+
mutating func storeBytes<T: BitwiseCopyable>(
259+
of value: T, toByteOffset offset: Int = 0, as type: T.Type
260+
)
261+
262+
@unsafe
263+
mutating func storeBytes<T: BitwiseCopyable>(
264+
of value: T, toUncheckedByteOffset offset: Int, as type: T.Type
265+
)
266+
}
267+
```
268+
269+
Additionally, the basic loading operations available on `RawSpan` are available for `MutableRawSpan`. 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](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md#SurjectiveBitPattern) such types at this time.
239270

240271
```swift
241272
extension MutableRawSpan {
@@ -261,22 +292,10 @@ extension MutableRawSpan {
261292
}
262293
```
263294

264-
To these, `MutableRawSpan` adds functions to store the bytes of a `BitwiseCopyable` value:
265-
266-
```swift
267-
extension MutableRawSpan {
268-
func storeBytes<T: BitwiseCopyable>(
269-
of value: T, toByteOffset offset: Int = 0, as type: T.Type
270-
)
295+
We include functions to perform bulk copies into the memory represented by a `MutableRawSpan`. Updating a `MutableRawSpan` from a `Collection` or a `Span` copies every element of a source. It is an error to do so when there is are not enough bytes in the span to contain every element from the source. Updating `MutableRawSpan` from `Sequence` or `IteratorProtocol` instance copies as many items as possible, either until the input is empty or until there are not enough bytes in the span to store another element.
271296

272-
@unsafe
273-
func storeBytes<T: BitwiseCopyable>(
274-
of value: T, toUncheckedByteOffset offset: Int, as type: T.Type
275-
)
276-
}
277-
```
297+
**Note:** This set of functions is sufficiently complete in functionality, but uses a minimal approach to slicing. This is only one of many possible approaches to slicing `MutableRawSpan`. (See the <a href="#slicing">note above</a> for more details on the same considerations.)
278298

279-
We include functions to perform bulk copies into the memory represented by a `MutableRawSpan`. Updating a `MutableRawSpan` from a `Collection` or a `Span` copies every element of a source. It is an error to do so when there is are not enough bytes in the span to contain every element from the source. Updating `MutableRawSpan` from `Sequence` or `IteratorProtocol` instance copies as many items as possible, either until the input is empty or until there are not enough bytes in the span to store another element.
280299
```swift
281300
extension MutableRawSpan {
282301
mutating func update<S: Sequence>(
@@ -347,7 +366,7 @@ A `mutating` computed property getter defined on any type and returning a `~Esca
347366

348367
A `nonmutating` computed property getter returning a `~Escapable & ~Copyable` value establishes a borrowing lifetime relationship, as if returning a `~Escapable & Copyable` value (see [SE-0456][SE-0456].)
349368

350-
The standard library will provide `mutableSpan` computed properties. These return lifetime-dependent `MutableSpan` instances. These computed properties are the safe and composable replacements for the existing `withUnsafeMutableBufferPointer` closure-taking functions.
369+
The standard library will provide `mutating` computed properties providing lifetime-dependent `MutableSpan` instances. These `mutableSpan` computed properties are intended as the safe and composable replacements for the existing `withUnsafeMutableBufferPointer` closure-taking functions.
351370

352371
```swift
353372
extension Array {
@@ -371,8 +390,6 @@ extension CollectionOfOne {
371390
}
372391
```
373392

374-
375-
376393
#### Extensions to unsafe buffer types
377394

378395
We hope that `MutableSpan` and `MutableRawSpan` will become the standard ways to delegate mutations of shared contiguous memory in Swift. Many current API delegate mutations with closure-based functions that receive an `UnsafeMutableBufferPointer` parameter to do this. We will provide ways to unsafely obtain `MutableSpan` instances from `UnsafeMutableBufferPointer` and `MutableRawSpan` instances from `UnsafeMutableRawBufferPointer`, in order to bridge these unsafe types to newer, safer contexts.
@@ -412,7 +429,7 @@ extension Foundation.Data {
412429

413430
The `mutableSpan` and `mutableBytes` properties should be performant and return their `MutableSpan` or `MutableRawSpan` with very little work, in O(1) time. In copy-on-write types, however, obtaining a `MutableSpan` is the start of the mutation, and if the backing buffer is not uniquely reference a copy must be made ahead of returning the `MutableSpan`.
414431

415-
Note that `MutableSpan` incurs no special behaviour for bridged types, since mutations always require a defensive copy of data bridged from Objective-C data structures.
432+
Note that `MutableSpan` incurs no special behaviour for bridged types, since mutable bindings always require a defensive copy of data bridged from Objective-C data structures.
416433

417434
## Source compatibility
418435

@@ -424,7 +441,7 @@ This proposal is additive and ABI-compatible with existing code.
424441

425442
## Implications on adoption
426443

427-
The additions described in this proposal require a new version of the Swift standard library and runtime.
444+
The additions described in this proposal require a new version of the Swift standard library.
428445

429446
## Alternatives considered
430447

@@ -478,7 +495,7 @@ extension MutableRawSpan {
478495
```
479496
We are subsetting functions that require lifetime annotations until such annotations are [proposed][PR-2305].
480497

481-
#### Splitting `MutableSpan` instances
498+
#### Splitting `MutableSpan` instances`MutableSpan` in divide-and-conquer algorithms
482499

483500
It is desirable to have a way to split a `MutableSpan` in multiple parts, for divide-and-conquer algorithms or other reasons:
484501

@@ -488,11 +505,15 @@ extension MutableSpan where Element: ~Copyable {
488505
}
489506
```
490507

491-
Unfortunately, tuples do not support non-copyable values yet. We may be able to use the new `Vector`/`Slab`/`InlineArray` type proposed in [SE-0453][SE-0453], but destructuring its non-copyable elements remains a challenge. Solving this issue for `Span` as well as `MutableSpan` is a top priority.
508+
Unfortunately, tuples do not support non-copyable values yet. We may be able to use `InlineArray` ([SE-0453][SE-0453]), or a bespoke type, but destructuring the non-copyable constituent part remains a challenge. Solving this issue for `Span` and `MutableSpan` is a top priority.
509+
510+
#### Mutating algorithms
511+
512+
Algorithms defined on `MutableCollection` such as `sort(by:)` and `partition(by:)` could be defined on `MutableSpan`. We believe we will be able to define these more generally once we have a generalized container protocol hierarchy.
492513

493514
#### <a name="OutputSpan"></a>Delegated initialization with `OutputSpan<T>`
494515

495516
Some data structures can delegate initialization of parts of their owned memory. The standard library added the `Array` initializer `init(unsafeUninitializedCapacity:initializingWith:)` in [SE-0223][SE-0223]. This initializer relies on `UnsafeMutableBufferPointer` and correct usage of initialization primitives. We should present a simpler and safer model of initialization by leveraging non-copyability and non-escapability.
496517

497-
## Acknowledgements
518+
We expect to propose an `OutputSpan<T>` type to represent partially-initialized memory, and to support to the initialization of memory by appending to the initialized portion of the underlying storage.
498519

0 commit comments

Comments
 (0)