Using UnsafeRawPointer to access in-memory ~Escapable types

I want to provide some context for a just-merged change to the standard library: [stdlib] ~Escapable raw pointer access: API adoption in URP, UMRP, URBP, UMRBP
#84701
.

This PR is the minimal support we need in the standard library to experiment with ~Escapable storage. It simply adds a way to store ~Escapable types to memory via the raw pointer APIs: UnsafeRawPointer and UnsafeMutableRawPointer.

[SE-0446] introduced nonescapable types in Swift making it possible to build lifetime-dependent data types directly on top of unsafe compiler builtins using the experimental Lifetimes feature. This allowed the standard library to provide Span<T>, Borrow<T>, and Inout<T>. Developers have also been building their own ~Escapable types under the experimental Lifetimes feature. And yet it remains impossible to store these types to memory even using experimental features and unsafe constructs.

We're now at a point where new data types are being proposed that either fundamentally rely on Span or that simply want to support generic ~Escapable elements early on in the design. Any type with ~Escapable elements that uses heap-allocated storage under the hood needs basic pointer support for loading and storing its element. Most notably that includes Span itself. Proposals for "Span<~Escapable>" and BorrowingSequence are in the making. I expect that to be followed later by proposals for data types that take ownership of their elements, such as "InlineArray<~Escapable>", "UniqueArray<~Escapable>", etc.

Accessing a ~Escapable value via a raw pointer provides no lifetime safety. It's up to the library author to override the lifetime of any loaded value before it can be returned from an API:

@safe
struct Wrapper<T: ~Escapable & BitwiseCopyable>: ~Escapable {
  let p: UnsafeMutableRawPointer

  public subscript() -> T {
    get {
      return unsafe _overrideLifetime(p.load(as: T.self), copying: self)
    }
    set {
      unsafe p.storeBytes(of: newValue, as: T.self)
    }
  }
}
extension Wrapper: Escapable where T: Escapable {}

Ultimately, we don't expect programmers to work directly with this raw pointer interface. It is initially required for bootstrapping basic container types and view types. We could have also added support for UnsafePointer<~Escapable>. That would add a lot of convenience for library authors, but it also raises a new question: typed pointers could preserve the lifetime of their non-Escapable element. It's worth considering because that would obviate the need for _overrideLifetime when accessing the elements. With raw pointers, there's simply no way around that. Those semantics should be discussed as a separate proposal. We'll defer that discussion for the time being to focus on other more urgent proposals which only require raw pointer support.

12 Likes