Adding a @lifetime annotation to a struct or enum declaration adds a lifetime requirement to the type:
@lifetime
struct A: ~Escapable {...}A type declared with a lifetime requirement cannot conform to Escapable:
@lifetime
struct A<T: ~Escapable>: ~Escapable { ... }
extension A: Escapable where T: Escapable {}
// ERROR: 'A' requires a lifetime. It cannot conform to 'Escapable'.
A non-Escapable type that does not have a lifetime requirement must conditionally conform to Escapable:
struct A<T: ~Escapable>: ~Escapable { ... }
extension A: Escapable where T: Escapable {} // OK
struct B<T: ~Escapable>: ~Escapable { ... }
// ERROR: must conditionally conform to Escapable.
// NOTE: either add a `@lifetime` requirement or an extension for `B: Escapable`.
This makes the distinction betwen types that intrinsically require a lifetime dependency vs. types that inherit a lifetime requirements from from generic parameters. This distinction so fundamental to the type's behavior that it should be evident in the type's declaration. Forcing the explicit annotation also catches easy mistakes in both directions: (1) forgetting to add the extension A: Escapable where clause to conditionally escapable types, and (2) adding a extension A: Escapable where clause to types that intrinsically require a lifetime dependency.
As explained in the following section, an explicit annotation also makes it possible to assume a lifetime requirements in a generic context.
Declarating a protocol with a lifetime requirement allows lifetimes to be required in a generic context. All conforming types must also declare a lifetime requirement.
@lifetime
protocol P: ~Escapable {...}
@lifetime
struct A: P & ~Escapable {...}
struct B: P & ~Escapable {...}
// ERROR: 'B' does not conform to 'P'. It lacks a '@lifetime' requirement.This is useful for generic programming over any set of unconditionally non-Escapable types. For example:
@lifetime
protocol HasRawSpan: ~Escapable {
var rawSpan: RawSpan
}
@lifetime
struct ViewA: HasRawSpan & ~Escapable {
var rawSpan: RawSpan
}
@lifetime
struct ViewB: HasRawSpan & ~Escapable {
var rawSpan: RawSpan
}
@lifetime
struct SubView {
var span: RawSpan
@_lifetime(copy view)
init<View: HasRawSpan & ~Escapable>(view: View) {
span = view.rawSpan
}
}Note that the initializer above cannot be written without lifetime requirements because it is impossible to copy a lifetime dependency from a potentially Escapable type.
@lifetime is incompatible with the current experimental Lifetimes feature, but can staged in under another experimental flag.
Failing to support the explicit @lifetime annotation before enabling the experimental Lifetimes feature will make it impossible to add later without breaking source.
The unqualified @lifetime annotation proposed here will be source compatible with more with more precise lifetime requirements that may be supported later. An unqualified @lifetime effectively lumps all potential lifetime requirements under a single anonymous lifetime.
Note, however, that once a protocol is declared with a @lifetime requirement, it cannot be refined with more precise requirements in the future without breaking conforming types.
Future extensions to the lifetime requirement will allow multiple requirements which can be named and associated with a another type. Lifetime requirements will typically be associated with a type that represents storage owned by the outer type:
@lifetime(span: Span<E>)
struct Outer<E>: ~Escapable {
var span: Span<E>
}