We would like to propose adding a new type constraint ~Escapable
for types that can be locally copied but cannot be assigned or transferred outside of the immediate context. This complements the ~Copyable
types added with SE-0390 by introducing another set of compile-time-enforced lifetime controls that can be used for safe, highly-performant APIs.
In addition, these types will support lifetime-dependency constraints that allow them to safely hold pointers referring to data stored in other objects.
Motivation
The design was motivated by ongoing work to provide a safe version of Swift's current UnsafeBufferPointer
. This would take the form of a new type that we are currently referring to as StorageView
. This new type would store a pointer and size to contiguously stored data, providing the same high performance and universal applicability as UnsafeBufferPointer
. For example, it could be used to write concrete implementations of parsing and other services that work well regardless of whether the underlying data was being stored in a Foundation Data
value, a Swift Standard Library Array<T>
, or some other data structure.
However, unlike UnsafeBufferPointer
, this new type would be entirely safe to use. This requires a number of properties:
- It should allow copying. For example, a common scenario might involve shrinking the view as data is consumed, and making copies at key points in order to easily revert to an earlier state. In particular, this new "non-escapable" concept is fundamentally different from the "non-copyable" concept that was added to Swift last year (and which is still being expanded and developed).
- It should not escape the local context. We want to forbid storing this in a global or static variable, for instance. This restriction also helps improve performance: "escape analysis" can be done entirely at compile time, avoiding any need for runtime checks.
- It should be possible to link the lifetime of this "view object" to the lifetime of the container that provided it.
We believe this ~Escapable
(non-escapable) concept will prove to be useful in many other contexts, so we're proposing it as a general addition to the core Swift language.
Our Proposal
The full details are in the two linked proposals, so I'll just provide a couple of quick examples here.
The recently-proposed StorageView<T>
type will provide a non-escapable reference to contiguously-stored typed data. In simplified form, it looks like this:
// A non-escapable reference to contiguously-stored typed data
struct StorageView<T>: ~Escapable {
private base: UnsafePointer<T>
private count: Int
}
We can then extend the Array
type (among others) to provide a StorageView
of its contents. The borrow(self)
notation here indicates that the return value has logically "borrowed" the contents of the array. The StorageView
value will maintain read access to those contents until it is destroyed. As a result, the compiler will ensure that the StorageView
value cannot outlive the Array
, and that the Array
is not mutated during that period of time:
extension Array {
borrowing func storageView() -> borrow(self) StorageView<Element> {
... construct a StorageView ...
}
}
Since any data type that stores contiguous data can provide this view, it forms a universal "currency type" for working with contiguous data. In particular, this enables the following idiom, which uses a very thin inlineable generic adapter to provide a flexible API while maintaining an efficient concrete ABI to the core logic.
@inlineable
func parse<T: StorageViewable>(_ store: T) {
return parse(storageView: store.storageView())
}
// A fully concrete implementation of the core parsing logic
func parse(storageView: StorageView<UInt8>) {
... fast concrete implementation ...
}
We've split the non-escapable types proposal into two parts:
Non-Escapable Types provides the basic definition of the proposed ~Escapable
type constraint.
Lifetime Dependency Annotations for Non-escapable Types describes a set of lifetime annotations that can be used to link the lifetime of a particular ~Escapable
value to some other value.
In addition, the StorageView
pitch describes how this can be used to provide safe, performant access to a wide variety of containers.
Related Discussions
- SE-0390: Non-copyable types
- Vision: Language Support for BufferView
- Roadmap for improving Swift Performance Predictability
Edited: Changed BufferView
to StorageView
to match the recently-posted pitch.