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
}
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)
}
The current design of lifetimes relies on "value lifetimes" and "conditionally non-escapable types". Values of Escapable
type have no lifetime scope. Values of ~Escapable
type have a single lifetime scope. A nonescapable value cannot live beyond the current scope unless the scope's function interface provides value-based lifetime propagation via @dependsOn
annotations.
E ≡ some T
NE ≡ some T: ~Escapable
Without a lifetime annotation, functions cannot return nonescapable values:
Table of Contents