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
.
-
Copyable
getters can always be implemented as a_read
accessors; the getter is trivially generated by copying the yielded value. -
~Copyable
properties 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 (
self
for 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
. -
Copyable
getters can always be implemented as a_read
accessors; the getter is trivially generated by copying the yielded value. -
~Copyable
properties 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 scope
Rust:
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
borrow
orunsafeAddress
;_read
is illegal. -
~Copyable
requires 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 scope
Rust:
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.span
Return a borrowed value that depends on a new access to self
. Conceptually: Create a locally scoped lifetime.
Swift 6.0:
- Current property semantics for
~Copyable
implemented with_read
.
Swift ~Escapable:
- Requires
_read
for~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 scope
Rust:
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 R 6 |
-> 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/_modify
syntax is temporary. I suggest replacing it withyield
andmutatable yield
. ↩ ↩2 -
set
reverses the dependency.self
must have a lifetime associated with the property type.self
becomes dependent on the new value's with respect to that lifetime. ↩ -
The
@lifetime(param)
annotation will typically be inferred as a dependency onself
or on a parameter of the same type as the result. ↩ -
In the syntax
@lifetime(param.lifetime)
, the.lifetime
component will be inferred~Escapable
types that have a lifetime associated withSelf
. ↩ -
The
@borrow(param)
annotation will typically be inferred as a dependency onself
. ↩ -
The
borrow
return type modifier is only needed for~Copyable
properties. ↩