Skip to content
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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

glessard
Copy link
Contributor

@glessard glessard commented Feb 5, 2025

Pitch MutableSpan and MutableRawSpan, non-escapable types to model mutations of containers. This will begin replacing uses of the withUnsafeMutableBufferPointer() and withUnsafeMutableBytes() functions to mutate standard library types.

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.

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.

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

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.

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:

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 }

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>(

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.

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:

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`.

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

@rjmccall rjmccall added the LSG Contains topics under the domain of the Language Steering Group label Feb 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LSG Contains topics under the domain of the Language Steering Group
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants