Hello everyone! Continuing on from adding Span/RawSpan (SE-0447) and the computed properties that return them (SE-0456), this is a pitch for MutableSpan and MutableRawSpan, helper types to delegate mutations of exclusively-borrowed memory.
Motivation
Many standard library container types can provide direct access to modify their internal representation. Up to now, it has only been possible to do so in an unsafe way. The standard library provides this unsafe functionality with closure-taking functions such as withUnsafeMutableBufferPointer() and withContiguousMutableStorageIfAvailable().
These functions have a few different drawbacks, most prominently their reliance on unsafe types, which makes them unpalatable in security-conscious environments. We continue addressing these issues with MutableSpan and MutableRawSpan, new non-copyable and non-escapable types that manage respectively mutations of typed and untyped memory.
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.
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.
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.
MutableRawSpan
MutableRawSpan allows delegating mutations to memory representing possibly heterogeneously-typed values, such as memory intended for encoding. It makes the same safety guarantees as MutableSpan. A MutableRawSpan can be obtained from a MutableSpan whose Element is BitwiseCopyable.
Extensions to standard library types
The standard library will provide mutableSpan computed properties. These return lifetime-dependent MutableSpan instances, and represent a mutation of the instance that provided them. These computed properties are the safe and composable replacements for the existing withUnsafeMutableBufferPointer closure-taking functions. For example,
func(_ array: inout Array<Int>) {
var ms = array.mutableSpan
modify(&ms) // call function that mutates a MutableSpan<Int>
// array.append(2) // attempt to modify `array` would be an error here
_ = consume ms // access to `array` via `ms` ends here
array.append(1)
}
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.
Please see the proposal documents for the detailed design:
[Pitch] MutableSpan and MutableRawSpan