The most common use-case for non-Escapable adoption is to wrap an unsafe pointer in an non-Escapable and/or non-Copyable type:
struct MutRef: ~Escapable & ~Copyable {
let _pointer: UnsafeRawPointer
}
Building generic collections of non-Escapable values requires tracking multiple lifetime dependencies per value. To support this, we propose a language feature: lifetime members.
The syntax described here is provisional. The goal is to show that a simple syntax for declaring lifetime members provides sufficient information for the type checker to infer and enforce lifetime dependencies.
We start with a container whose element may be either Copyable or Escapable, which will be determined by the calling context:
struct Container<Element: ~Escapable, ~Copyable>: ~Escapable, ~Copyable {
let _element: Element
}
Lifetime definitions are an indisputably complicated feature. At least three aspects of the Swift language make the semantics challenging to express syntactically:
Swift lacks first-class references. This leads to a dichotomy between concrete vs. generic lifetimes. Concrete lifetimes imply a required lifetime dependence. Generic lifetimes represent potential lifetime dependence.
Unconditionally nonescapable types have fundamentally different lifetime constraints than conditionally escapable types, but the difference between these two flavors of lifetime dependent types is not syntactically apparent.
With ~Copyable
and ~Escapable
types, Swift properties can, in theory, be accessed in different ways that affect where the property value can be used. Here we describe four kinds of property access that each have different lifetime semantics. These lifetime semantics generalize to function results and coroutine yields; in this respect, there's no need to distinguish between "properties" and functions. Each kind of lifetime does, however, describe a relationship between the result and some outer value that the result is derived from. For this reason, property access is a helpful conceptual framing. This document suggests possible ways that the semantics could be expressed in the language. Our goal is not for programmers to think in terms of these four kinds of access lifetimes; natural usage of ~Copyable
and ~Escapable
types should lead to common-sense behavior. Instead, this formalization is meant to guide future Swift evolution proposals, which may propose some useful subsets of
The following additional sections have been added to the original pitch.
Normally, lifetime dependence is required when a nonescapable function result depends on an argument to that function. In some rare cases, however, a nonescapable function parameter may depend on another argument to that function. Consider a function with an inout
parameter. The function body may reassign that parameter to a value that depends on another parameter. This is similar in principle to a result dependence.
This pitch is an active brainstorm. It may eventually be converted into an SE proposal.
This proposal introduces a nonescapable, noncopyable UniqueSpan<T>
type. Like Span<T>
, which is also nonescapable, it does not manage the underlying storage. But unlike Span<T>
, it is noncopyable and takes ownership of all the elements within the span, allowing them to be moved, consumed, or replaced.
In some cases, a nonescapable value must be constructed without any object that can stand in as the source of a dependence. Consider extending the standard library Optional
or Result
types to be conditionally escapable:
enum Optional<Wrapped: ~Escapable>: ~Escapable {
case none, some(Wrapped)
}
In some cases, a nonescapable value must be constructed without any object that can stand in as the source of a dependence. Consider extending the standard library Optional
or Result
types to be conditionally escapable:
enum Optional<Wrapped: ~Escapable>: ~Escapable {
case none, some(Wrapped)
}