-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Pitch] MutableSpan and MutableRawSpan #2681
base: main
Are you sure you want to change the base?
Conversation
41fcd63
to
2f625f9
Compare
In addition to the new types, we will propose adding new API some standard library types to take advantage of `MutableSpan` and `MutableRawSpan`. | ||
|
||
## Proposed solution | ||
We introduced `Span` to provide shared read-only access to containers. The natural next step is to provide a similar capability for mutable access. Mutability requires exclusive access, per Swift's [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. Exclusive access cannot be modeled with a copyable type, since a copy would represent an additional access, in violation of the law of exclusivity. We therefore need a non-copyable type separate from `Span` in order to model mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whitespace: should have a newline between heading and paragraph?
|
||
#### MutableSpan | ||
|
||
`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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strongly recommend using the more accessible terms "bounds safety" and "lifetime safety" instead of "spatial safety" and "temporal safety".
@unsafe | ||
subscript(unchecked position: Index) -> Element { borrow; mutate } | ||
|
||
/// Exchange the elements at the two given offsets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whitespace: this is a tab
``` | ||
##### Bulk updating of a `MutableSpan`'s elements: | ||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's surprising to me that you can specify a start index on all of these, but not an end index, especially for update(startingAt:repeating:)
.
) -> Index | ||
} | ||
``` | ||
##### Interoperability with unsafe code: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can pass an Array to a function that takes an UnsafePointer/UnsafeMutablePointer using &array
as the argument. Does that also work with MutableSpan?
var isEmpty: Bool { get } | ||
|
||
/// The range of valid byte offsets into this `RawSpan` | ||
var byteOffsets: Range<Int> { get } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this basically indices
? I assume it is because MutableRawSpan requires its content to be initialized?
|
||
```swift | ||
extension MutableRawSpan { | ||
mutating func storeBytes<T: BitwiseCopyable>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does BitwiseCopyable
imply Sendable
?
} | ||
``` | ||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, I'm surprised there's a start index but no end index. In the absence of slicing on Span types, it seems to mean there's no way to bulk-modify part of a MutableRawSpan from part of another span. This is functionality I would use in a project that I'm working towards migrating to Span/RawSpan. (I could still do it by hand, I guess?)
} | ||
``` | ||
|
||
These unsafe conversions returns a value whose lifetime is dependent on the _binding_ of the `UnsafeMutable[Raw]BufferPointer`. This dependency does not keep the underlying memory alive. As is usual where the `UnsafePointer` family of types is involved, the programmer must ensure the memory remains allocated while it is in use. Additionally, the following invariants must remain true for as long as the `MutableSpan` or `MutableRawSpan` value exists: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intended that these are not mutating get
accessors?
|
||
#### <a name="performance"></a>Performance | ||
|
||
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling:
and if the backing buffer is not uniquely reference a copy must
^~~~~~~~~ referenced
Pitch
MutableSpan
andMutableRawSpan
, non-escapable types to model mutations of containers. This will begin replacing uses of thewithUnsafeMutableBufferPointer()
andwithUnsafeMutableBytes()
functions to mutate standard library types.