Skip to content

Instantly share code, notes, and snippets.

@atrick
Last active November 12, 2025 22:19
Show Gist options
  • Select an option

  • Save atrick/6807fa54e47bcbbdecfddbdf1e33baab to your computer and use it in GitHub Desktop.

Select an option

Save atrick/6807fa54e47bcbbdecfddbdf1e33baab to your computer and use it in GitHub Desktop.
Lifetime Members

Lifetime Members

Introduction

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.

Proposal

Lifetime member definitions

A non-Escapable struct may define lifetime members. To be concise, we may refer to these as a type's "lifetimes". A type's lifetimes are part its public interface. Each lifetime is associated with a type from the declaration's generic context.

For example, the current Span (LegacySpan) only tracks a single unnamed lifetime:

@lifetime(_: Self)
struct LegacySpan<Element>: ~Escapable {
    var storage: UnsafeMutablePointer<Element>
   
    @lifetime(borrow unsafeBytes)
    init(unsafeBytes: UnsafeRawBufferPointer) {...}
   
    subscript(position: Int) -> Element
}

@lifetime(copy span)
func forward<T>(span: LegacySpan<T>) -> LegacySpan<T>

Conceptually, LegacySpan's lifetime represents the memory pointed to by the span, not the values stored in that memory. Extending Span to hold non-Escapable values requires that each span track two lifetimes: storage and elements:

@lifetime(storage: Self)
@lifetime(elements: Element)
struct Span<Element: ~Escapable>: ~Escapable {
    var storage: UnsafeMutablePointer<Element>
   
    @lifetime(.storage: borrow unsafeBytes)
    @lifetime(.elements: borrow unsafeBytes)
    init(unsafeBytes: UnsafeRawBufferPointer) {...}
   
    @lifetime(copy self.elements)
    subscript(position: Int) -> Element
}

@lifetime(.storage: copy span.storage)
@lifetime(.elements: copy span.elements)
func forward<T: ~Escapable>(span: Span<T>) -> Span<T>

Note that the current form of Span's initializer provides no additional flexibility because the Span's elements are as constrained as its storage. A higher-level initializer could introduce flexibility by taking the element scope as a second argument and overriding initial unsafeBytes dependency.

Concrete vs. generic lifetime members

A conditionally Escapable type is marked by a conditional conformance, as follows:

struct A<T: ~Escapable>: ~Escapable { ... }
extension A: Escapable where T: Escapable {}

A lifetime member associated with a conditionally Escapable type is a generic lifetime. A generic lifetime represents zero or more type-erased lifetimes. It is only capable of propagating dependencies through values of the same type.

A lifetime member associated with unconditionally non-Escapable type is a concrete lifetime. A concrete lifetime represents a dependency on a single scope.

A conditionally Escapable type, such as Optional, cannot have any concrete lifetimes, because it might not carry any lifetime dependencies. Instead, is has a single generic lifetime associated with its Wrapped type:

// @lifetime(_: Wrapped) - implicit generic lifetime
public enum Optional<Wrapped: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
    case none
    case some(Wrapped)
}

An unconditionally non-Escapable type, on the other hand, always defines at least one concrete lifetime. And because its public interface contains a concrete lifetime, it can never be extended to be conditionally Escapable.

Span is an unconditionally non-Escapable type:

@lifetime(storage: Self)
@lifetime(elements: Element)
struct Span<Element: ~Escapable>: ~Escapable {...}

Span's storage lifetime is concrete because Self is unconditionally non-Escapable. Span's elements lifetime is generic because Element is conditionally Escapable.

Dependency requirements

A non-Escapable type's initializer must satisfy a lifetime dependency for each of its lifetime members:

@lifetime(storage: Self)
@lifetime(elements: Element)
struct Span<Element: ~Escapable>: ~Escapable {
    @lifetime(.storage: borrow unsafePointer)  // explicit borrow scope
    @lifetime(.elements: borrow unsafePointer) // explicit borrow scope
    init(unsafeBytes: UnsafeRawBufferPointer) { ... } 
}

Future forms of Span.init may take a typed pointer that carries any dependency on the Element type. That would allow the elements of the resulting Span to outlive the Span.

Similarly, a function that returns a non-Escapable type must satisy the lifetime dependency for each of the return type's lifetime members.

@lifetime(.storage: copy span.storage)
@lifetime(.elements: copy span.elements)
func forward<T: ~Escapable>(span: Span<T>) -> Span<T>

Inferring memberwise dependencies

Inferring lifetime dependencies is trivial for both lifetimes in the function above that forwards a span:

lifetime(.storage: copy span.storage) is inferred because .storage is associated with Span<T> which is the type of both the span argument and the result.

lifetime(.elements: copy span.elements) is inferred because the Element generic type parameter is bound to the same type for both the result and the span argument. The result's .elements lifetime and the span argument's .elements lifetime are therefore both associated with the same generic type parameter.

Memberwise same-type inference

Given a function declaration in which A and R are nominal types:

func foo(a: A) -> R { ... }

Each lifetime member declared in the function's result type R requires a lifetime dependency provided by the functions argument type A.

Say that R has a lifetime member associated with R.U. To infer the source of this lifetime's dependency, the type checker visits the lifetime members in A until it finds a lifetime associated with the same type as R.U in the function declaration's generic context.

These same-type requirements are evaluated only once when the function's declaration is independently type checked. It is independent of substitutions that may be applied when the function is invoked.

For example:

@lifetime(span: RawSpan)
struct A {...}

@lifetime(span: RawSpan)
struct R {...}

/* Infers @lifetime(result.span: a.span) */
func foo(a: A) -> R {...}

Generic same-type inference

The same-type inference rule described above is convenient for nominal types but essential for generic type parameters.

Consider the same example above, but instead of associating the lifetime with the unconditionally non-Escapable RawSpan type, we associate the lifetime with a conditionally Escapable generic type parameter:

@lifetime(t: T)
struct A<T: ~Escapable> {...}

@lifetime(u: U)
struct R<U: ~Escapable> {...}

/* Validate @lifetime(result.u: a.t) */
func foo<V>(a: A<V>) -> R<V> {...}

As, above a lifetime dependency can be inferred. But that dependency also must be validated even if the programmer writes it explicitly. If the same-type requirement fails, then type checking fails:

@lifetime(result.u: a.t)
func foo<T, U>(a: A<T>) -> R<U> {...} // 🛑 ERROR: R.U us not the same type as A.T

Because same-type requirements are evaluated independently for a function declaration, simply calling the function with substitutions of the same type would not cause any dependency to be inferred:

func bar<T: ~Escapable>(a: A<T>) -> R<T> {
    foo(a: a) // ERROR: no lifetime dependency
}

Generic lifetime substitution

Generic same-type inference requires recursive substitution of generic types.

@lifetime(v: V)
struct B<V: ~Escapable> {...}

func foo<X: ~Escapable>(a: A<B<X>>) -> R<B<X>> {}

The type checker initially finds the lifetime A.t: T. Since this lifetime is associated with a generic parameter T, it then applies substitutions from the original function. Because T is now substituted with nominal type B, it recursively continues looking for lifetime members within B. Ultimately, the typechecker determines that A.t.v is the same type as R.u.v.

Recursive same-type inference with concrete type substitution

Same-type inference can be applied recursively to handle substituting a lifetime's generic type with a concrete type:

@lifetime(y: Y)
struct B<Y: ~Escapable> {...}

@lifetime(z: Z)
struct C<Z: ~Escapable> {...}

@lifetime(t: T)
struct A<T> {...}

@lifetime(u: U)
struct R<U> {...}

/* infer: @lifetime(.u.z: a.t.y) */
func foo<X: ~Escapable>(a: A<B<X>>) -> R<C<X>> {}

The lifetime dependency lifetime(.u.z: a.t.y) is valid, but it cannot be inferred from the first level of lifetime variables u and t because U != T. The dependency can only be infered or checked by recursing through each lifetime variable's type substitution.

Recall from "Memberwise same-type" inference:

Each lifetime member declared in the function's result type R requires a lifetime dependency provided by the function's argument type A.

We could extend the algorithm to handle recursive cases as follows:

R has a lifetime member associated with R.U. To infer the source of this lifetime's dependency, the type checker recursively visits the lifetime members exposed by the generic substitution U := C<X>. The substituted lifetime is C.z: X. Now it searches the lifetime members in A until it finds a lifetime associated with X. This also requires recursion after substituting T := B<X>. Finally, it meets the same-type requirement: (B.u: X) matches (C.z: X).

Recursive same-type inference with generic type constraints

As with concrete type substitution, same-type inference can be applied recursively to generic constraints, but only for the argument type, not the result type:

@lifetime(x: X)
protocol P<X: ~Escapable> {...}

@lifetime(t: T)
struct A<T> {...}

@lifetime(u: U)
struct R<U> {...}

/* infer: @lifetime(.u: a.t.x) */
func foo<Y: ~Escapable & P<Z>, Z: ~Escapable>(a: A<Y>) -> R<Z> {}

R has a lifetime member associated with R.U := Z. The type checker searches the lifetime members in A to find a lifetime associated with Z. a.t does not match because its type substution is T := Y. Here, the type checker recursively searches lifetimes required by Y. Since Y conforms to P<Z>, it checkes the protocol P's lifetime x: X. The substitution X := Z gives us the matching same-type dependency P.x: Z matches R.u: Z

Unlike concrete substitution, the type checker cannot recurse into the results generic requirements because all the result's lifetime constraints must be satisfied, not only the ones that are required by generic constraints:

@lifetime(x: X)
protocol P<X: ~Escapable> {...}

@lifetime(x: X)
protocol Q<X: ~Escapable> {...}

@lifetime(t: T)
struct A<T> {...}

@lifetime(u: U)
struct R<U> {...}

/* ERROR: @lifetime(.u.x: a.t.x) */
func foo<T: ~Escapable & P, U: ~Escapable & Q>(a: A<T>, _: U.Type) -> R<U> {}

Recursion over both the result and argument lifetime members could determine that P.x: Z matches Q.x: Z. This is, however, incomplete, and the missing dependency must be flagged as an error. For example, an invocation of foo may substitute a result type with additional lifetime members:

@lifetime(x: X)
@lifetime(y: Y)
struct B<X>: Q<X> {...}

let r: R<B> = foo(a, B.self)

So, while the recursively inferred dependency @lifetime(r.u.x: a.t.x) is valid, it is incomplete because the result has an unsatisfied lifetime r.y.

Tuple dependencies

A function signature may consist of nominal types, generic type parameters, or structural types. So far, we've talked about resolving dependencies on nominal and generic type parameters. To review, for nominal types, the lifetime members are named in the type's interface. For generic type parameters, the lifetime members are type erased, so the type parameters is considered to have a single abstract lifetime.

Structural types include tuples and function types.

For tuples, the type checker simply handles each type component separately. The explicit dependency syntax can be expanded to identify a tuple component. For example:

@lifetime(.0: tuple.1)
@lifetime(.1: tuple.0)
func swap(tuple: (T, T)) -> (T, T)

Next we consider function types.

Closures

Function-type arguments

Higher-order functions have arguments that are themselves function types, each of which represents a closure. When the outer function's dependencies are resolved, each closure is treated like single value with a generic "capture" lifetime type representing all values captured by the closure. Here, foo's result depends on all values captured by f.

/* @lifetime(copy f) */
func foo(f: ()->RawSpan) -> RawSpan {
    f() // ✅ OK
}

/* @lifetime(copy span)*/
func bar(span: RawSpan) -> RawSpan {
    foo { span } // ✅ OK
}

Closure result dependencies

When closure's function type returns a non-Escapable result, it specifies the result's dependencies that apply to later invocations of the closure. The result of a closure invocation has two possible dependency sources:

(1) the closure's parameters

@lifetime(a0)
func foo(a0: A, f: @lifetime(a1) (a1: A)->RawSpan) -> RawSpan

(2) the closure's captures

@lifetime(f)
func foo(f: @lifetime(capture) ()->RawSpan) -> RawSpan

Most higher-order functions can be expressed with "localized" result dependencies. Consider extending Optional with a borrowing map() operation:

extension Optional {
  /* @lifetime(copy self, copy transform) */
  public borrowing func borrowingMap<U: ~Copyable & ~Escapable, E: Error>(
    _ transform: @lifetime(copy capture, copy wrapped)
                 (_ wrapped: borrowing Wrapped) throws(E) -> U
  ) throws(E) -> Optional<U> {
    switch self {
    case let .some(y):
      return .some(try transform(y))
    case .none:
      return .none
    }
  }
}

@lifetime(borrow value)
func foo<T: ~Escapable>(values: borrowing InlineArray<T>?) -> Span<T>? {
  values.borrowingMap { $0.span }
}

The result of borrowingMap nominally depends on both self and transform. The dependency on transform is, however, ignored because the closure does not capture any non-Escapable values. Substituting an Escapable value as the source of a copy dependency drops the dependency.

@nonescaping closures

With lifetime dependencies, a function can return a @nonescaping function type that depends on an argument lifetime:

extension Optional {

/* @lifetime(copy span) */
func foo(_ span: RawSpan) -> /*@nonescaping @lifetime(capture)*/ ()->RawSpan {
    return { span } // ✅ OK
}

/* @lifetime(copy span) */
func bar(span: RawSpan) -> RawSpan {
    return foo(span)()
}

Higher-order function dependencies (future)

A closure that accepts a non-Escapable argument may need more flexibility than localized result dependencies can provide. Instead, the closure may need to refer to lifetimes defined by the higher-order function. Consider a container's closure-taking method:

struct Container {
    func withSpan(operation: @lifetime(0: self) (Span<Element>) -> ())
}

@lifetime(0: self) indicates that the first parameter of operation (at index 0) depends on the self argument to withSpan. This is a higher-order dependency in the sense that a component of the nested function type depends on a component of the outer function definition. This allows span to potentially escape the closure as long as the escaped value respects the dependencies when the closure is passed as an argument. For example, imagine we have a Borrow type that can hold a non-Escapable element:

struct Borrow<T: ~Escapable> {...}
func foo(container: Container<Int>) -> Int {
    var ref: Borrow<Span<Int>()?
    container.withSpan { (span: Span<Int>) in
      // 'span' escapes this closure without any localized depedency.
      ref = span
    }
    return ref.value[0] // OK: 'container.elements' outlives 'temp'
}

Closure dependencies could be even further generalized. We could, hyopthetically, allow multiple closure invocations to depend on single scope defined by the same function that invokes all instances of the closure. Here, all invocations of operation would depend on a single scope defined inside withSpan:

func withSpan(self: SomeCollection<Element>,
              operation: @lifetime(0: withSpan) (Span<Element>) -> ())

For now, naming the outer function like this is invalid syntax, and we have no plans to support this form of dependency.

Enhancements

Implicit lifetime member declarations

Generic lifetimes do not need an explicit lifetime declaration because naming the lifetime typically serves no purpose. A separate generic lifetime could be inferred for each generic parameter:

// @lifetime(_: T) - implicit generic lifetime
// @lifetime(_: U) - implicit generic lifetime
public struct A<T: ~Escapable, U: ~Escapable>: ~Escapable {
    let t: T
}

Optional, for example, implicitly defines a single generic lifetime associated with its Wrapped type parameter:

// @lifetime(_: Wrapped) - implicit generic lifetime
public enum Optional<Wrapped: ~Copyable & ~Escapable>: ~Copyable & ~Escapable {
    case none
    case some(Wrapped)
}
extension Optional: Escapable where Wrapped: Escapable & ~Copyable {}

We will, however, want a way to suppress lifetime inference on a generic parameter when the type's implementation does not require the lifetime. For example, the type may have no stored properties of that generic type.

We might simply limit implicit lifetime members to generic parameters that appear in the where clause of a conditional conformance on Escapable.

Lifetime properties

Lifetime members may be related to a stored property. We can provide a convenient syntax such as:

struct Pair<T: ~Escapable, U: ~Escapable>: ~Escapable {
    @lifetime /* (t: T) */
    let t: T
   
    @lifetime /* (u: U) */
    let u: U
}

or:

struct enum Wrapper<T: ~Escapable>: ~Escapable {
    case none
   
    @lifetime /* (some: T) */
    case some(T)
}

Nested concrete lifetimes

Nested concrete lifetimes can be exposed simply by associating the outer lifetime with the inner type:

@lifetime(storage: RawSpan) // concrete nested lifetime
struct TrivialSpan<Element: BitwiseCopyable> {
    let rawSpan: RawSpan<Element>
}

The inner type may not be in the public interface, preventing the outer lifetime from being associated with the inner lifetime:

@lifetime(storage: Self)
public struct TrivialSpan<Element: BitwiseCopyable> {
    let rawSpan: InternalRawSpan<Element>
}

As a convenience--to avoid specifying lifetime dependencies on all the internal interfaces--lifetimes can be aliased. Lifetime aliases can be in the public interface but need not be:

@lifetime(storage: Self)
public struct TrivialSpan<Element: BitwiseCopyable> {
    lifetimealias storage = InternalRawSpan.storage

    let rawSpan: InternalRawSpan<Element>
}

Lifetime aliases may also be useful in associating a lifetime with multiple types.

Protocol requirements

A protocol may define both concrete and generic lifetimes. Each generic ~Escapable type in the protocol declaration, including Self will have a generic lifetime, just like other generic types:

// @lifetime(_: Self) - inferred
// @lifetime(_: Dependent) - inferred
protocol P: ~Escapable {
    associatedtype Dependent: ~Escapable
}

Same-type inference needs to apply the protocols lifetime constraints to recognize the lifetime dependency in cases such as:

func foo<T: P & ~Escapable, U: P & ~Escapable>(t: A<T>) -> R<U> where T.Dependent == U.Dependent {}

Additionally, a protocol can require concrete lifetimes via explicit lifetime annotation:

@lifetime(storage: Self)
// @lifetime(_: Element) - inferred
protocol NEContainer: ~Escapable {
    associatedtype Element: ~Escapable
}

LifetimeScope

Lifetime dependency declarations typically name the function argument that provides the source of the dependency. Some Swift APIs, however, are applicable over an arbitrary dependency source and scope. This comes up most often with unsafe APIs. To support such APIs, we introduce a non-Escapable LifetimeScope type. The compiler will implicitly construct a LifetimeScope whenever a lifetime member is named in argument position. For example:

@lifetime(elements: Element)
struct Container<Element> {...}

func f(scope: LifetimeScope) {...}

func foo() {
    f(container.elements)
}

Using this technique, we can update Span's unsafe initializer to accept an arbitrary lifetime dependency for its elements:

@lifetime(storage: Self)
@lifetime(elements: Element)
struct Span<Element: ~Escapable>: ~Escapable {
    @lifetime(.storage: borrow unsafePointer)
    @lifetime(.elements: copy scope)
    init(unsafeBytes: UnsafeRawBufferPointer, elementScope: LifetimeScope) { ... } 
}

func foo(container: Container) -> Span<Container.Element> {
    Span(unsafePointer: container.unsafePointer, elementScope: container.elements)
}

We often want to create a lifetime representing the borrow scope of an Escapable value. For this, we introduce a lifetime function that can produce a non-Escapable LifetimeScope for any value:

func lifetime<T: ~Copyable & ~Escapable>(borrowing: borrowing T) -> LifetimeScope
func lifetime<T: ~Copyable & ~Escapable>(inout: inout T) -> LifetimeScope

This nicely complements the unsafe overrideLifetime() API, which removes a value's lifetime dependencies and adds a new arbitrary dependency:

@unsafe
@lifetime(copy lifetime)
internal func overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
    _ dependent: consuming T, lifetime: LifetimeScope) -> T

Now we can create a new dependency on the borrow scope of an Escapable value as follows:

let span = overrideLifetime(Span(unsafeBytes: container.unsafeBytes), lifetime: lifetime(borrowing: container))

Alternatives

Default concrete lifetime

A single concrete lifetime on Self could be inferred as the default lifetime for an unconditionally nonescapable type:

// @lifetime(_: Self) - unnamed, concrete lifetime inferred
struct RawSpan: ~Escapable {
    let _pointer: UnsafeRawPointer
    //...
}

Instead, we choose to require explicit lifetime annotation on all unconditionally nonescapabe types:

  1. A lifetime annotation clearly distinguishes unconditionally nonescapable types from conditionally escapable types: an important distinction that is otherwise absent from the type definition.

  2. Forcing a lifetime annotation makes the irreversible library evolution requirement explicit. The author of SpanPair, for example, should deliberately decide whether each stored property should have a separate concrete lifetime before shipping the interface:

@lifetime(first: Span)
@lifetime(second: Span)
public struct SpanPair: ~Escapable {
    let first: Span
    let second: Span
}
  1. Each concrete lifetime creates a requirement that must be satisfied by all the type's intializers. An explicit annotation communicates that requirement to maintainers of the type.

Stored property constraints

Nonescapable stored properties impose constraints on their parent type. If the stored property is generic over a conditionally escapable type, then that lifetime naturally propagates to the parent's generic context:

// '@lifetime(_: T)' - implicit generic lifetime
public struct A<T: ~Escapable>: ~Escapable {
    var t: T
}

// '@lifetime(_: T)' - implicit generic lifetime
public struct B<T: ~Escapable>: ~Escapable {
    var t: A<T>
}

The implicit generic lifetimes of both A and B are associated with the same generic type parameter, so they are effectively aliases.

If the stored property has a concrete lifetime, or concrete lifetime requirement, then we might also require the parent to have the same concrete lifetime:

@lifetime(self: Self) // concrete lifetime
public struct A: ~Escapable { ... }

@lifetime(self: Self)
public struct B: ~Escapable {
    var a: A // 🛑 ERROR: lifetime A.self is not exposed in B's interface
}

@lifetime(a: A)
public struct C: ~Escapable {
    var a: A // ✅ OK: A.self is associated with the same type as `A`
}

@lifetime(self: Self)
public struct D: ~Escapable {
    lifetimealias self = A.self

    var a: A // ✅ OK: A.self is aliased with `self`
}

This concrete lifetime constraint is similar to Rust's constraints, but it isn't clearly needed in the declaration checker. We could be more flexible by ignoring this requirement and allowing depenendence checking on the initializers to catch any missing dependencies.

Rust comparison

Swift lifetime members are in many ways equivalent to Rust lifetime variables. The fundamental difference is that lifetime members cannot be used as a type discriminator.

Note: It isn't clear to me when such type discrimination is useful in Rust. The Rust model is also conceptually at odds with generic types. We would need to introduce another kind of generic parameter that could be bound to a generic lifetime member in its generic context, but it could never be bound to a concrete lifetime.

Consequently, Rust forces two values of the same type have the same lifetimes when they are both used in the same function invocation. Swift simply has no such fundamental restriction. Swift defers all lifetime resolution until function invocation. Same-type inference means that you should typically see the same effect as Rust for most APIs.

Lifetime variables in type declarations

Generic lifetimes:

Swift

@lifetime(t: T)
struct A<T: ~Escapable> {...}

@lifetime(u: U)
struct B<U: ~Escapable> {...}

/* @lifetime(result.u: a.t) */
foo<S>(a: A<S>) -> B<S>

Rust

struct A<T> {
    x: T // The type param must be used in the interface
}

struct B<U> {
    y: U
}

fn foo<V>(a: A<V>) -> B<V>

Swift's public interface is independent of stored properties. To recognize that a type parameter carries a lifetime dependency, the public interface declares a lifetime assocated with that type parameter. Rust does the same by requiring that the type parameter is used by field defined in the type's interface.

Concrete lifetimes:

Swift

/* @lifetime(span: Span<Int>) */
struct A: ~Escapable {
    let span: Span<Int>
}

/* @lifetime(span: Span<Int>) */
struct B: ~Escapable {
    let span: Span<Int>
}

/* @lifetime(result.span: a.span) */
foo<V>(a: A<V>) -> B<V>

Rust

struct A<'a> {
    slice: &'a [i64]
}

struct B<'a> {
    slice: &'a [i64]
}

fn foo<'v>(a: A<'v>) -> B<'v>

Dependency scopes

Rust syntax more explicitly represents scoped dependencies:

fn borrow(x: &'a S) -> Foo<'a>

As opposed to inherited dependencies:

fn carry<'a>(x: &'_ Foo<'a>) -> Bar<'a>

Swift makes a this important distinction using a subtle annotation modifier: borrow vs copy:

@lifetime(borrow x)
func borrow(x: borrowing S) -> dependsOn(/*scoped*/ x) Foo

@lifetime(copy x)
func carry(x: Foo) -> Bar

This syntactic disadvantage has do with Swift's lack of first-class references. It is unrelated to the topic of lifetime members and lifetime variables.

Reference

Preview of Lifetime Definitions; Andy; Oct '24

Rust comparison; Joe Groff; Aug '24

Private (quip) discussions:

"Lifetime restrictions and callbacks; John McCall Aug '24"

"Swift Lifetime Dependency Annotations; Gábor Horváth; Jul '24"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment