# Lifetime dependencies and conditionals

**URL:** <https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594>\
**Category:** Using Swift\
**Tags:** ownership, lifetime\
**Created:** [December 7, 2025, 5:51am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594 "2025-12-07T05:51:54Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![zackbach](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/zackbach/32/43682_2.png) [@zackbach](https://forums.swift.org/u/zackbach)\
**Post date:** [December 7, 2025, 5:51am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/1 "2025-12-07T05:51:54Z")

</div>

Hello! I have three mostly-unrelated questions that all pertain to the interactions between lifetime dependencies / conditionals / reassignment / inout argument dependence. I thought that I would group them here, rather than make separate posts. Any help is appreciated—feel no pressure to respond to all three questions simultaneously.

## (1) `inout` argument dependence behaving like conditional reassignment

In the [WIP Swift Evolution proposal](https://github.com/jckarter/swift-evolution/blob/tbkka-lifetime-dependency/proposals/NNNN-lifetime-dependency.md) for lifetimes, it is stated that

> `inout` argument dependence behaves like a conditional reassignment

I am curious to know why `inout` argument dependence always behaves like _conditional_ reassignment. Specifically, there are some functions where the reassignment always happens, meaning the conjoined dependence is an unnecessary overapproximation. As an example,

```swift
@_lifetime(x: copy y)
func reassign(_ x: inout Span<Int>, _ y: Span<Int>) {
  x = y;
}

```

The `inout` argument dependence here could act like unconditional reassignment, instead of implicitly acting more like `@_lifetime(x: copy x, copy y)`.

I am also not quite sure if this is changing—when trying things out with a nightly build of the compiler, this behavior was _not_ the case (i.e. this in fact behaved like `@_lifetime(x: copy y)` _without_ the implicit `copy x`). But this behavior also appeared to be [buggy](https://github.com/swiftlang/swift/issues/85886), so I am not sure what the intended design here is.

Is this a language design question, or is the documented behavior necessary? I would imagine that the non-overapproximating alternative would be technically sound, possible to implement, just maybe not particularly useful. But my perspective here is not the most informed.

## (2) Lifetime dependent variables escaping their scope

When reassigning lifetime-dependent variables in branches of a conditional, errors about escaping are raised inconsistently. I am curious about what the sound, intended behavior is.

```swift
func test() {
  var arr = [1, 2, 3];
  var span: Span<Int>! = nil;

  // this will always work, regardless of whether `arr` is `let` or `var`
  do {
    span = arr.span
  }

  // this will give an error when arr is `var` but NOT when it is `let`
  // error: lifetime-dependent variable `span` escapes its scope.
  if (Bool.random()) {
    span = arr.span;
  } else {
    span = arr.span;
  }

  // ensure that span is not implicitly dropped
  print(span[0]);
}

```

I would expect the example above to be accepted, regardless of whether `arr` is `let` or `var`. If this is the case, I am more than happy to file an issue on GitHub with this behavior.

More generally, I would expect that lifetimes to be adjusted by taking the least upper bound of the lifetime information in the two branches. For instance, I would imagine that in the following example, usage of `span` after the conditional would require read access to _both_ `arr1` and `arr2`.

```swift
  if (Bool.random()) {
    span = arr1.span;
  } else {
    span = arr2.span;
  }

```

Does this seem reasonable? If so, does the implementation align with this? I tried testing it out, but I couldn’t find a way to test this behavior without running into the `var` behavior noted above.

## (3) Ternary expressions and borrowing

It appears that the “branches” of a ternary expression will consume those expressions. As an example,

```swift
func bor(it: borrowing MutableSpan<Int>) { }

func test(s1: borrowing MutableSpan<Int>,
          s2: borrowing MutableSpan<Int>,
          b: Bool) {
  // works
  if (b) {
    bor(it: s1)
  } else {
    bor(it: s2)
  }

  // error: s1 is borrowed and cannot be consumed
  bor(it: b ? s1 : s2)
}

```

My (evidently faulty) mental model for this is that the `bor` function requests its argument to be able to be borrowed, and that either branch of the ternary can be borrowed, so this should be okay. But, I’d imagine that the reason that this does not work is that the ternary operator itself consumes both of the expressions in its branches, making this get rejected.

Can expressions just not be borrowed in this way? I believe that once the `Borrow` type + accessors land, this should be able to work as follows (using syntax I’m mostly making up):

```swift
bor(it: borrow (b ? Borrow(s1) : Borrow(s2))

```

Does this seem plausible? Will there be language-level support (rather than library level through a `Borrow` type) for borrowed expressions like this? Am I just totally misunderstanding this situation?

Again, any and all thoughts on one or many of these questions would be appreciated.

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 1:00am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/2 "2025-12-10T01:00:09Z")

</div>

> (1) inout argument dependence behaving like conditional reassignment

Given:

```swift
func reassign(_ x: inout Span<Int>, _ y: Span<Int>)

```

`@_lifetime(x: copy x)` means that `x` can be mutated in place but cannot be reassigned to anything other than itself (unless the reassigned value inherits the lifetime of incoming `x`).

`@_lifetime(x: copy y)` means that `x` must be reassigned to `y` (or the equivalent from a lifetime dependence perspective)

`@_lifetime(x: copy x, copy y)` means that `x` may either be mutated in-place or (conditionally) reasigned to `y`.

The current proposal draft seems to be referring to the default dependency _without_ annotation. This is an interesting question that I want feedback on...

According to the current _in-tree_ documentation:  
[Same-type default lifetime (unimplemented)](https://github.com/swiftlang/swift/blob/main/docs/ReferenceGuides/LifetimeAnnotation.md#same-type-default-lifetime-unimplemented)

Without annotation, the default dependency would be:

```swift
@_lifetime(x: copy x, x: copy y)

```

That default assumes possible reassignment to any other same-type parameter.

In my current (unmerged) implementation, however, I chose to revise the same-type rule to:

`Where `R: ~Escapable`, `A == R`, and `a`is not an`inout`parameter, default to`@\_lifetime(copy a)`.`

Meaning that the default dependency in this case would follow the normal `inout` rule:

```swift
@_lifetime(x: copy x)

```

This creates a clear distinction between the `same-type` rule vs. the `inout` rule without any confusing overlap. Furthermore, it biases the default dependency of the `inout` result to its incoming value. If the API may reassign the `inout` argument to a different argument, then that needs to be explicit.

The counter-argument to my change is that the author of a public API can easily forget to write that annotation if the initial implementation does not reassign. If they decide to reassign the `inout` in some future implementation, they've broken source. But that's generally true of any API that produces a non-Escapable value. I don't think it warrants extra consideration here.

I'm about to land the same-type default rule with this change, so it's a good time to air any opinions on this choice of defaults.

[EDIT] To be clear, I’m asking developers to think about APIs in which the same non-Escapable type occurs in multiple parameter positions, one of which is mutating:

```swift
func reassign(_ x: inout Span<Int>, _ y: Span<Int>)

```

or

```swift
extension MutableSpan {
  mutating func foo(_ y: MutableSpan<Element>)
}

```

Does this indicate likely reassignment, or does it indicate normal mutation of the mutable parameter while simply accessing the contents of the other parameter. I suspect the later.

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 1:06am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/3 "2025-12-10T01:06:55Z")

</div>

> [@zackbach](#):
>
> 1. Lifetime dependent variables escaping their scope

```swift
  var span: Span<Int>! = nil;
  // this will give an error when arr is `var` but NOT when it is `let`
  // error: lifetime-dependent variable `span` escapes its scope.
  if (z) {
    span = arr.span;
  }

```

It is intentional that dependence on a `var` is different than dependence on a `let`. Depending on a mutable variable means depending on the access to that variable. Since the access is conditional, it's expected that the dependency is conditional.

This will, however, drive everyone crazy. It is possible for the compiler to hoist the access to `var` in most simple cases. This is a well-known feature request that simply needs to be prioritized.

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 1:12am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/4 "2025-12-10T01:12:51Z")

</div>

> [@zackbach](#):
>
> (3) Ternary expressions and borrowing

```swift
  // error: s1 is borrowed and cannot be consumed
  bor(it: b ? s1 : s2)

```

The move checker doesn't know how to borrow over a conditional. It's an implementation deficiency. I suspect we have a bug related to this, but it wouldn't hurt to raise a github issue.

---

<div class="post-metadata">

**Author:** ![zackbach](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/zackbach/32/43682_2.png) [@zackbach](https://forums.swift.org/u/zackbach)\
**Post date:** [December 10, 2025, 4:20am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/5 "2025-12-10T04:20:25Z")

</div>

Thanks for the detailed responses, Andy!

> [@Andrew\_Trick](#):
>
> `@_lifetime(x: copy y)` means that `x` must be reassigned to `y` (or the equivalent from a lifetime dependence perspective)

I see. I think that the part of the lifetime documentation that confused me was this:

> `inout` argument dependence behaves like a conditional reassignment. After the call, the variable passed to the `inout` argument has both its original dependence along with a new dependence on the argument that is the source of the argument dependence.

If having the lifetime dependency `@_lifetime(x: copy y)` means that the function _must_ reassign `x` to `y`, is it true that after a call to that function, `x` will have its original dependence as well? It would surprise me if that was the case, but it seems to be what the quoted (potentially outdated) documentation implies (if I understand correctly).

> [@Andrew\_Trick](#):
>
> According to the current _in-tree_ documentation:  
> [Same-type default lifetime (unimplemented)](https://github.com/swiftlang/swift/blob/main/docs/ReferenceGuides/LifetimeAnnotation.md#same-type-default-lifetime-unimplemented)

Thanks for sharing this documentation! This will definitely be useful to me as I continue playing with these lifetime features. I don’t write enough real Swift code to have a very valuable opinion on the same-type defaulting behavior; I think that your proposal sounds very reasonable.

> [@Andrew\_Trick](#):
>
> Where `R: ~Escapable`, `A == R`, and `a` is not an `inout` parameter, default to `@_lifetime(copy a)`.

One quick confusion I have from this description: is this procedure to determine default lifetime dependencies applied to `inout` arguments in addition to the function’s return? I’d imagine so given the context.

---

<div class="post-metadata">

**Author:** ![zackbach](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/zackbach/32/43682_2.png) [@zackbach](https://forums.swift.org/u/zackbach)\
**Post date:** [December 10, 2025, 4:41am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/6 "2025-12-10T04:41:23Z")

</div>

> [@Andrew\_Trick](#):
>
> It is intentional that dependence on a `var` is different than dependence on a `let`. Depending on a mutable variable means depending on the access to that variable. Since the access is conditional, it's expected that the dependency is conditional.

Sorry, I’m confused by this. I was expecting dependence on both a `var` and a `let` to depend on access to that variable. Why isn’t this the case for `let`? But then again, with that mental model, I still expected the code above to work (in both cases, not in neither case). Which indicates that I’m still missing something here.

I think that I was expecting for uses of `span` to depend on the access to `arr`, but that the access to `arr` would be extended until after the final usage of `span`. Is it that such extensions to accesses be made across a scope? Meaning if the access started in the outer scope, the extension would be possible? Or maybe my understanding here is entirely off.

Above, I assume that the hoisting you mention is taking place at the SIL level, and not something that can really be influenced at the source code level. Is this accurate?

> [@Andrew\_Trick](#):
>
> The move checker doesn't know how to borrow over a conditional. It's an implementation deficiency. I suspect we have a bug related to this, but it wouldn't hurt to raise a github issue.

Ack. I have opened a [GitHub issue](https://github.com/swiftlang/swift/issues/85941) just in case, and linked it to this thread.

Are there any language constructs that the move checker _does_ know how to borrow over?

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 6:15am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/7 "2025-12-10T06:15:34Z")

</div>

> [@zackbach](#):
>
> If having the lifetime dependency `@_lifetime(x: copy y)` means that the function _must_ reassign `x` to `y`, is it true that after a call to that function, `x` will have its original dependence as well? It would surprise me if that was the case, but it seems to be what the quoted (potentially outdated) documentation implies (if I understand correctly).

Your intuition is correct. The draft proposal is wrong. It might have reflected an implementation of default lifetimes before it was fixed. When you specify `@_lifetime(target: …)` that needs to suppress any usual lifetime defaults for `target`. Otherwise you just wouldn’t be able to override the defaults.

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 7:04am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/8 "2025-12-10T07:04:12Z")

</div>

> [@zackbach](#):
>
> > [@Andrew\_Trick](#):
> >
> > Where `R: ~Escapable`, `A == R`, and `a` is not an `inout` parameter, default to `@_lifetime(copy a)`.
> 
> One quick confusion I have from this description: is this procedure to determine default lifetime dependencies applied to `inout` arguments in addition to the function’s return? I’d imagine so given the context.

I propose that an argument only participates in either the inout default rule or the same-type default rule but never both.

_inout rule_

Default to `@_lifetime(a: copy a)` for all `inout` parameters where `a` is `~Escapable`.

_same-type rule_

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

Where `R: ~Escapable`, `A == R`, and `a` is not an `inout` parameter, default to `@_lifetime(copy a)`.

```swift
// inout default: @_lifetime(a: copy a)
// same-type default: @_lifetime(copy b)
func foo(a: inout T, b: T) -> T

```

So, by default, the result does not depend on mutable parameter `a`, and the mutated value of `a` does not depend on `b`.

* * *

The alternative is that we have no `inout` rule at all, and `inout` dependencies are just a variant of the `same-type` rule. While a single rule may seem simpler, it leads to confusing and typically wrong defaults:

```swift
// same-type default: @_lifetime(a: copy a, copy b)
// same-type default: @_lifetime(copy a, copy b)
func foo(a: inout T, b: T) -> T

```

I think it will be more natural for programmers to think about `inout` dependencies separately from result dependencies.

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [December 10, 2025, 5:45pm UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/9 "2025-12-10T17:45:24Z")

</div>

> [@zackbach](#):
>
> Sorry, I’m confused by this. I was expecting dependence on both a `var` and a `let` to depend on access to that variable. Why isn’t this the case for `let`? But then again, with that mental model, I still expected the code above to work (in both cases, not in neither case). Which indicates that I’m still missing something here.
> 
> I think that I was expecting for uses of `span` to depend on the access to `arr`, but that the access to `arr` would be extended until after the final usage of `span`. Is it that such extensions to accesses be made across a scope? Meaning if the access started in the outer scope, the extension would be possible? Or maybe my understanding here is entirely off.
> 
> Above, I assume that the hoisting you mention is taking place at the SIL level, and not something that can really be influenced at the source code level. Is this accurate?

This is about Span’s exclusivity requirement. Depending on a variable depends on an exclusive access scope. For `let`, you can think of the entire variable scope as a single access. For `var` each reference to the variable is a separate access. Yes, that access will be extended to the last dependent use (that’s a SIL transformation). If, however, the access is inside a conditional statement, extension isn’t sufficient. Accesses are scoped, so you can’t “extend” into an outer scope. We also need to hoist an access (into the outer scope), and sometimes combine multiple accesses into a single access. That’s reasonably straightforward to do at the SIL level but unimplemented.

> Meaning if the access started in the outer scope, the extension would be possible?

Correct

> [@zackbach](#):
>
> Above, I assume that the hoisting you mention is taking place at the SIL level, and not something that can really be influenced at the source code level. Is this accurate?

Yep, I’m talking about a SIL transformation. You might be able to hoist the access at the source level, create two spans, and conditional reassign one, but the compiler should do this for you.

---

<div class="post-metadata">

**Author:** ![zackbach](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/zackbach/32/43682_2.png) [@zackbach](https://forums.swift.org/u/zackbach)\
**Post date:** [January 7, 2026, 2:51am UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/10 "2026-01-07T02:51:06Z")

</div>

Awesome, these responses were just what I was looking for! Especially the bits on extending accesses across scopes. Thank you for your time. I have just two last comments (now that I am revisiting this much-appreciated response):

> [@Andrew\_Trick](#):
>
> For `let`, you can think of the entire variable scope as a single access.

This was somewhat surprising to me. In my current model, accessing `let` and `var` (and function parameters too) are handled more similarly to each other than this suggests. I don’t expect you to have a concrete answer here, but does my approach sound suspicious? Is the behavior described in the quote above more of an implementation detail, or something more fundamental about the programming model? Even just a rough vibe here could be useful, to orient my future considerations (especially since I lack some intuition here).

> [@Andrew\_Trick](#):
>
> Accesses are scoped, so you can’t “extend” into an outer scope.

I understand what it is that you are saying, and this helps me understand the behavior above. Perhaps I should know better, but why is this the case? If the newly started accesses don’t depend on things which go out of scope, couldn’t this be relaxed? (Or, maybe this criterion for “relaxing” is really just the criterion for being able to do the hoisting you discuss at length).

And now I’m a little confused as to why the `do` version of this example works. Is the compiler able to hoist the access in that case?

---

<div class="post-metadata">

**Author:** ![Andrew\_Trick](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andrew_trick/32/323_2.png) [@Andrew\_Trick](https://forums.swift.org/u/Andrew_Trick)\
**Post date:** [January 25, 2026, 9:42pm UTC](https://forums.swift.org/t/lifetime-dependencies-and-conditionals/83594/11 "2026-01-25T21:42:47Z")

</div>

Looks like I failed to follow up on the question...

> I don’t expect you to have a concrete answer here, but does my approach sound suspicious? Is the behavior described in the quote above more of an implementation detail, or something more fundamental about the programming model? Even just a rough vibe here could be useful, to orient my future considerations (especially since I lack some intuition here).

Overall, your expectations in the original post all seem reasonable. `let` and `var` are fundamentally different of course because of the potential for mutation. A `let` has guaranteed exclusive access after initialization. The compiler needs to do more work to prove that a `var` won't be mutated in any given region. If you simply replace `let` with `var`, then the compiler can, in theory, guarantee the same behavior, given that you haven't added any mutation. But, in practice, you'll see more conservative lifetime restrictions because of implementation deficiencies.

> If the newly started accesses don’t depend on things which go out of scope, couldn’t this be relaxed? (Or, maybe this criterion for “relaxing” is really just the criterion for being able to do the hoisting you discuss at length).

Right, if the access does not depend on something that goes out of scope, then the begining of the access can be hoisted, which allows the end of the access to be extended.

```swift
  // this will always work, regardless of whether `arr` is `let` or `var`
  do {
    span = arr.span
  }

```

This happens to work because of an artifact of the SIL CFG. The `do` scope effectively disappears because none of the code depends on control flow.
