We are proposing a new abstraction for safe, direct access to memory:
StorageView. This is the proposal formerly known as
BufferView, and is a companion to Non-Escapable Types and to Lifetime Dependency Annotations for Non-escapable Types, which were jointly pitched yesterday by @tbkka.
StorageView<T>, an abstraction for container-agnostic access to contiguous memory. It will expand the expressivity of performant Swift code without giving up on the memory safety properties we rely on: temporal safety, spatial safety, definite initialization and type safety.
In the C family of programming languages, memory can be shared with any function by using a pointer and (ideally) a length. This allows contiguous memory to be shared with a function that doesn't know the layout of a struct being used by the caller. A heap-allocated array, contiguously-stored named fields or even a single stack-allocated instance can all be accessed through a C pointer. We aim to create a similar idiom in Swift, with no compromise to memory safety.
Consider for example a program using multiple libraries, including base64 decoding. The program would obtain encoded data from one or more of its dependencies, which could supply it in the form of
Foundation.Data or even
String, among others. None of these types is necessarily more correct than another, but the base64 decoding library must pick an input format. It could declare its input parameter type to be
some Sequence<UInt8>, but such a generic function significantly limits performance. This may force the library author to either declare its entry point as inlinable, or to implement an internal fast path using
withContiguousStorageIfAvailable() and use an unsafe type. The ideal interface would have a combination of the properties of both
some Sequence<UInt8> and
StorageView will allow sharing the contiguous internal representation of a type, by providing access to a borrowed view of a span of contiguous memory. A view does not copy the underlying data: it instead relies on a guarantee that the original container cannot be modified or destroyed during the lifetime of the view.
StorageView's lifetime is statically enforced as a lifetime dependency to a binding of the type vending it, preventing its escape from the scope where it is valid for use. This guarantee preserves temporal safety.
StorageView also performs bounds-checking on every access to preserve spatial safety. Additionally
StorageView always represents initialized memory, preserving the definite initialization guarantee.
By relying on borrowing,
StorageView can provide simultaneous access to a non-copyable container, and can help avoid unwanted copies of copyable containers.