# Rationale behind the generic lifetime notation

**URL:** <https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191>\
**Category:** language design\
**Created:** [July 8, 2025, 3:39pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191 "2025-07-08T15:39:08Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![yn1](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/yn1/32/13408_2.png) [@yn1](https://internals.rust-lang.org/u/yn1)\
**Post date:** [July 8, 2025, 3:39pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/1 "2025-07-08T15:39:08Z")

</div>

Given how lifetime works, I would expect what is now written as

```rust
fn f<'a> (x: &'a str, y: &'a str) -> &'a str

```

to be instead written as something like

```rust
fn f<'a, 'b> (x: &'a str, y: &'b str) -> &'a∧'b str

```

Here `'a∧'b` is supposed to mean the meet of `'a` and `'b`.

What is the rationale for the current notation? For example, is there aspect that the current notation is conveying naturally or effectively?

---

<div class="post-metadata">

**Author:** ![sahnehaeubchen](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/sahnehaeubchen/32/9709_2.png) [@sahnehaeubchen](https://internals.rust-lang.org/u/sahnehaeubchen)\
**Post date:** [July 8, 2025, 3:56pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/2 "2025-07-08T15:56:22Z")

</div>

What do you mean by the "meet of 'a and 'b"?

The coercions you are allowed to do depend on the variance of the type in the lifetime. How would you write

```rust
fn f<'a>(x: fn(&'a str), y: fn(&'a str)) -> fn(&'a str)

```

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [July 8, 2025, 4:35pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/3 "2025-07-08T16:35:45Z")

</div>

**These are not simply different notations; these are actually different function signatures.** When you call the first `f()`, if the two references you supply have different lifetimes, then they are [**coerced**](https://doc.rust-lang.org/reference/type-coercions.html) to have shorter, equal lifetimes (which is possible because `&'x str` is covariant in `x`, i.e. `&'short str` is a subtype of `&'long str` when `'long: 'short`). This coercion is an operation performed by the _calling code_, not by `f`.

If `-> &'a∧'b str` were a thing that is possible to write, then the lifetime shortening would be happening within the function itself. This is arguably superior to what we have because it might allow more cases of higher-order function usage, but it is different and would require a quite different implementation in the compiler.

---

<div class="post-metadata">

**Author:** ![yn1](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/yn1/32/13408_2.png) [@yn1](https://internals.rust-lang.org/u/yn1)\
**Post date:** [July 8, 2025, 5:10pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/4 "2025-07-08T17:10:58Z")

</div>

This is enlightening. I didn't know there is coercion happening. So I may paraphrase the current notation as follows:

- for any lifetime `'a` where the lifetime of the first argument is at least as long and the lifetime of the second argument is also at least as long, the function guarantees a result whose lifetime is at least as long.

Under that statement, if the caller uses the result at a time that exceeds either argument's lifetime, then the result is not guaranteed to be alive, thus rejected by the compiler.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [July 8, 2025, 6:47pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/5 "2025-07-08T18:47:29Z")

</div>

> [@yn1](#):
>
> ```rust
> fn f<'a, 'b> (x: &'a str, y: &'b str) -> &'a∧'b str
> 
> ```
> 
> Here `'a∧'b` is supposed to mean the meet of `'a` and `'b`.

You _can_ say essentially that; it's written

```rust
fn f<'a: 'r, 'b: 'r, 'r> (x: &'a str, y: &'b str) -> &'r str

```

It's just not useful to do so, because the variance in `&` means that, in effect, does the same thing as just

```rust
fn f<'a> (x: &'a str, y: &'a str) -> &'a str

```

* * *

> [@yn1](#):
>
> I didn't know there is coercion happening.

Note that it's not a coercion, it's full subtyping, which is much more powerful.

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [July 8, 2025, 9:36pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/6 "2025-07-08T21:36:00Z")

</div>

> [@yn1](#):
>
> Here `'a∧'b` is supposed to mean the meet of `'a` and `'b`.

[That's this concept, pretty much.](https://internals.rust-lang.org/t/proposal-t-lifetime/22977)

---

<div class="post-metadata">

**Author:** ![jdahlstrom](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jdahlstrom/32/3351_2.png) [@jdahlstrom](https://internals.rust-lang.org/u/jdahlstrom)\
**Post date:** [July 8, 2025, 9:46pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/7 "2025-07-08T21:46:06Z")

</div>

> [@sahnehaeubchen](#):
>
> What do you mean by the "meet of 'a and 'b"?

[Join and meet](https://en.m.wikipedia.org/wiki/Join_and_meet)

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [July 8, 2025, 10:22pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/8 "2025-07-08T22:22:32Z")

</div>

Like all mathematics article on Wikipedia, it is reference material assuming you already know the field. I tried reading it, and it was nonsense to me (with a bachelor in computer engineering and master in robotics).

If you want to explain to people who know set theory but never even heard of order theory you need to find some other reference.

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [July 8, 2025, 10:33pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/9 "2025-07-08T22:33:04Z")

</div>

> [@yn1](#):
>
> This is enlightening. I didn't know there is coercion happening. So I may paraphrase the current notation as follows:
> 
> - for any lifetime `'a` where the lifetime of the first argument is at least as long and the lifetime of the second argument is also at least as long, the function guarantees a result whose lifetime is at least as long.

No, that is not correct. The function **does not** accept arguments with two different lifetimes. It accepts two arguments with **one** lifetime, and returns a value with the **same** lifetime as the inputs. The function is generic over exactly one lifetime parameter `<'a>`; it never sees more than one lifetime involved in any call to it.

The coercion of longer lifetimes to a suitable single lifetime `'a` is the (implicit) responsibility of the code _calling_ the function, not the function itself. The coercion happens to the references _before_ they are passed to the function.

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [July 8, 2025, 11:33pm UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/10 "2025-07-08T23:33:20Z")

</div>

> [@yn1](#):
>
> Under that statement, if the caller uses the result at a time that exceeds either argument's lifetime, then the result is not guaranteed to be alive, thus rejected by the compiler.

My preferred way to explain this signature

```rust
fn f<'a>(x: &'a str, y: &'a str) -> &'a str

```

is that uses of the returned value keep the associated input borrows active. Both `*x` and `*y` remain borrowed so long as the return value is in use, because they are both associated with the same lifetime. If any use of the referents conflicts with being borrowed for that duration, or if the arguments at the call site cannot be coerced to that lifetime, you get a borrow checker error.

Due to variance, the lifetime is not determined by what you pass in.\[1\] The lifetime in the type of the arguments at the call site do not determine what the lifetime in the return type will be. Instead, the use of the returned value determines what the lifetime will be. I.e. how long the referents must remain borrowed.

By framing it this way, we are no longer tempted to say things like

- "at the call site, the compiler/caller chooses a lifetime to coerce to"
- " `'a` will get the concrete lifetime that is equal to the smaller of the lifetimes of `x` and `y`"\[2\]

And instead can reason in a way that more closely matches how the borrow checker actually works.

* * *

1. though what you pass in may result in a borrow check error 

2. [which is how the Book (inaccurately) puts it](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotations-in-function-signatures)

---

<div class="post-metadata">

**Author:** ![yn1](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/yn1/32/13408_2.png) [@yn1](https://internals.rust-lang.org/u/yn1)\
**Post date:** [July 9, 2025, 1:43am UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/11 "2025-07-09T01:43:01Z")

</div>

If I understand the explanation correctly, then it suggests that we understand lifetime annotation as a notation of _dependence_. A returning reference marked by `'a` may at most depend on arguments marked by the same name. I was happily reading the lifetime annotation that way, but ran into difficulty reading `'b: 'a`. Under the dependence idea, is there a way to read `'b: 'a`, such that for

```rust
fn g<'a, 'b> (x: &'a str, y: &'b str)
  -> &'a str where 'b: 'a

```

we can see that returning a reference of lifetime `'b` is sound?

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [July 9, 2025, 2:21am UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/12 "2025-07-09T02:21:28Z")

</div>

> [@yn1](#):
>
> we can see that returning a reference of lifetime `'b` is sound?

The function does not return a reference type with a lifetime `'b`. It returns a reference type with a lifetime `'a`. But it is sound for the function body to coerce a `&'b str` to an `&'a str`, for example by returning the `y` value.

So perhaps your real question is, how can we read `'b: 'a` such that it is clear that `*y` remains soundly borrowed, should the function body happen to return the `y` value?

One possibility is to read it as "if `'a` is active, `'b` is active".

Using the returned value keeps `'a` active, and `'a` being active keeps `'b` active, so the borrows of both referents are still kept active by uses of the returned value.

---

<div class="post-metadata">

**Author:** ![yn1](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/yn1/32/13408_2.png) [@yn1](https://internals.rust-lang.org/u/yn1)\
**Post date:** [July 9, 2025, 11:40am UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/13 "2025-07-09T11:40:47Z")

</div>

Thanks for everyone who tries to help me navigate through the problem. I now see how to understand the current notation with subtyping, and I also feel that the dependence reading may still have the potential to be made precise.

Possibly as a final comment, I would like to add a few words about the second lifetime annotation idea in the initial post. Although there may be implementation subtleties, the idea may still has some merits. For example, we may write

```rust
fn h<'a,'b> (x: &'a str, y: &'b str) -> &'a+'b str

```

to express that the result has a lifetime of either that of `'a`, or that of `'b`, exclusively. Or we may write

```rust
fn k<'a,'b> (x: &'a str, y: &'b str) -> &'a∨'b str

```

to express that the result has a lifetime as the union of `'a` and `'b`. For the moment this annotation idea may be far away from implementation, but it probably doesn't harm to think about them.

---

<div class="post-metadata">

**Author:** ![Serhii](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/serhii/32/12808_2.png) [@Serhii](https://internals.rust-lang.org/u/Serhii)\
**Post date:** [July 10, 2025, 11:10am UTC](https://internals.rust-lang.org/t/rationale-behind-the-generic-lifetime-notation/23191/14 "2025-07-10T11:10:11Z")

</div>

What you are proposing is essentially replacing lifetimes with the origins, which are definitely way easier to reason about because they represent a very concrete thing (a place expression) in contrast to lifetimes (some abstract regions of code). You can find more details on this here:

- [Borrow checking without lifetimes](https://smallcultfollowing.com/babysteps/blog/2024/03/04/borrow-checking-without-lifetimes/)
- [The borrow checker within](https://smallcultfollowing.com/babysteps/blog/2024/06/02/the-borrow-checker-within/)
