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 features described here for incremental adoption.
Each section below describes one of the four kinds of property lifetimes; each increasingly restricts the context in which the property's value can be used. The semantics are demonstrated in terms of self-explanatory ContiguousArray.span and Span.subscript properties. Examples show explicit lifetime annotation for clarity, but lifetimes would easily be inferred. Each case includes a corresponding Rust declaration as a way to restate the semantics in terms of an established formal model.
Return an owned value with no lifetime restrictions. Conceptually: copy or move the underlying value.
Swift 6.0: The property semantics for Copyable accessors using get.
-
Copyablegetters can always be implemented as a_readaccessors; the getter is trivially generated by copying the yielded value. -
~Copyableproperties requireconsuming get; they cannot be implemented with_read.
Example:
class Inner {}
struct Outer: ~Copyable {
let inner: Inner
}
// the copy of outer.inner is used after consuming outer
takeOuterAndInner(outer.inner, consume outer)Swift ~Escapable: N/A -- requires Escapable
Rust: N/A
Return an owned value with a lifetime dependency. Conceptually: copy any underlying reference.
Swift 6.0: N/A
Swift ~Escapable:
-
Requires a parameter (
selffor true properties) that has an abstract lifetime associated with its type:@lifetime(param.lifetime). -
Both the parameter type (
Self) and property type may be conditionally escapable. Enforcement of lifetime dependencies in the property's implementation ensures that the parameter is nonescapable whenever the returned property is nonescapable. This implies that an unconditionally escapable property can never depend on a conditionally escapableSelf. -
Copyablegetters can always be implemented as a_readaccessors; the getter is trivially generated by copying the yielded value. -
~Copyableproperties requireconsuming get; they cannot be implemented with_read.
struct Span<T>: ~Escapable {
lifetime storage: Self
@lifetime(self.storage)
subscript(range) -> Span<T> { get { ... } }
}
let span2: Span<T>
do {
let contiguousArray = ...
do {
let span1 = contiguousArray.span
span2 = span1[range]
}
_ = span2[0] // OK
}
_ = span2[0] // ERROR: outside of 'contiguousArray' access scopeRust:
Same-lifetime dependency on a non-reference:
impl<'a, T> Span<'a, T> {
fn subscript(self: Span<'a, T>, _:Range<i32>) -> Span<'a, T>
}
// No need to take a reference to 'span1' here.
let span2 = span1.subscript(range)Return a borrowed value that depends on the current access to self. Conceptually: create a reference.
Swift 6.0: N/A
Swift ~Escapable:
-
Requires a borrowed parameter:
@borrow(param) -
Requires
borroworunsafeAddress;_readis illegal. -
~Copyablerequires a new feature: "borrowed returns". -
Allows unconditionally nonescapable properties to depend on an access of an escapable or conditionally escapable
Self.
struct ContiguousArray<T> {
@borrow(self)
var span: Span<T> { borrow { ... } }
}
let span: Span<T>
do {
let contiguousArray = ...
span = contiguousArray.span
_ = span[0] // OK
}
_ = span[0] // ERROR: outside of 'contiguousArray' access scopeRust:
Lifetime dependence on a reference:
impl<T> ContiguousArray<T> {
fn span<'a>(&'a self) -> Span<'a, T>
}
// Need to take a reference to 'contiguousArray' here.
let span = &contiguousArray.spanReturn a borrowed value that depends on a new access to self. Conceptually: Create a locally scoped lifetime.
Swift 6.0:
- Current property semantics for
~Copyableimplemented with_read.
Swift ~Escapable:
- Requires
_readfor~Escapable
struct DiscontiguousArray<T> {
var span: Span<T> { _read { ... } }
}
let discontiguousArray = ...
let span: Span<T>
do {
span = discontiguousArray.span
_ = span[0] // OK
}
_ = span[0] // ERROR: outside of 'discontiguousArray.span' access scopeRust:
Higher-ranked lifetime:
for <'b> fn span(&'a Array) -> Span<'b> where 'a: 'b
| Access kind: | Copied | Dependent | Projected | Yielded |
|---|---|---|---|---|
| property syntax | : R { get } |
: R { get } |
: R { borrow } |
: R { _read }1 |
| mutable syntax | : R { set } |
: R { set }2 |
: R { mutate } |
: R { _modify }1 |
| function syntax | -> R |
@lifetime(param.lifetime)34 |
@borrow(param)5+-> borrow R6 |
-> yield R |
| conceptually | copy/move value | copy reference | create reference | create access |
| ownership | owned | owned | borrowed | borrowed |
| scope | ∞ | access of param.lifetime |
access of param |
new access |
| Copyable | OK | OK | Exclusive access to self |
auto-getter/setter |
| ~Copyable | consuming | consuming | Exclusive access to self |
Exclusive access to self |
| Escapable | OK | copying or consuming | Exclusive access to self |
auto-getter/setter |
| ~Escapable | Invalid | OK | Exclusive access to self |
Exclusive access to self |
Escapable self |
OK | Requires Escapable property | OK | OK |
Footnotes
-
The current
_read/_modifysyntax is temporary. I suggest replacing it withyieldandmutatable yield. ↩ ↩2 -
setreverses the dependency.selfmust have a lifetime associated with the property type.selfbecomes dependent on the new value's with respect to that lifetime. ↩ -
The
@lifetime(param)annotation will typically be inferred as a dependency onselfor on a parameter of the same type as the result. ↩ -
In the syntax
@lifetime(param.lifetime), the.lifetimecomponent will be inferred~Escapabletypes that have a lifetime associated withSelf. ↩ -
The
@borrow(param)annotation will typically be inferred as a dependency onself. ↩ -
The
borrowreturn type modifier is only needed for~Copyableproperties. ↩