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