# Lifetimes are too obscure

**URL:** https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513
**Category:** language design
**Created:** [October 8, 2022, 2:06am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513 "2022-10-08T02:06:48Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![boohba](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/boohba/32/9906_2.png) [@boohba](https://internals.rust-lang.org/u/boohba)
#### Post date: [October 8, 2022, 2:06am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/1 "2022-10-08T02:06:48Z")

</div>

Consider the following example:

```rust
fn main() {
    a(&[1, 2, 3]);
}

fn a(b: &[u8]) {
    //
}

```

What holds an ownership of `[1, 2, 3]`? Is it dropped before execution of `a` or after?

Aforementioned example compiles. However, it is unclear what is actually happening. I would like to have an option in Cargo to visualize lifetimes just like it does when it encounters an error. Or, alternatively, some kind of strict mode which will make Rust more explicit.

---

<div class="post-metadata">

### Author: ![toc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/toc/32/6692_2.png) [@toc](https://internals.rust-lang.org/u/toc)
#### Post date: [October 8, 2022, 2:29am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/2 "2022-10-08T02:29:47Z")

</div>

`[1, 2, 3]` is dropped after the execution of `a`, within `main`. It's a temporary, so it will be freed just after the call to `a` ends. The thing being passed to `a` is a reference `&_`, and you can think of it as being passed to `a` and then dropped at the end of `a` (to little effect). You can actually make your own special type to trace drops:

```rust
use std::fmt::Debug;

#[derive(Debug)]
struct NoisyDrop<T: Debug>(T);

impl<T: Debug> Drop for NoisyDrop<T> {
    fn drop(&mut self) {
        println!("Dropping {:?}", self.0);
    }
}

fn main() {
    println!("starting main");
    a(NoisyDrop(&NoisyDrop([1, 2, 3])));
    println!("ending main");
}

fn a(_: NoisyDrop<&NoisyDrop<[u8; 3]>>) {
    println!("a");
}

```

which gives us

```rust
starting main
a
Dropping NoisyDrop([1, 2, 3])
Dropping [1, 2, 3]
ending main

```

This question is a better fit for the [Rust users forum](https://users.rust-lang.org/), internals is for discussion about compiler/language internals.

---

<div class="post-metadata">

### Author: ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)
#### Post date: [October 8, 2022, 2:35am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/3 "2022-10-08T02:35:39Z")

</div>

Values such as the array `[1, 2, 3]` that are results of parts of larger expressions because they are _used_ in a by-reference manner are being put in a so-called “temporary variable”, or more commonly shorter a “temporary”. The Rust Reference contains some explanations as to how temporaries work, but the rule of thumb would be that they all get dropped (in reverse order of creation) at the end of the enclosing _statement_. [-\> Paragraph about temporaries in the Rust Reference.](https://doc.rust-lang.org/reference/expressions.html#temporaries)

A _statement_ in Rust is often a single line, or a single multi-line thing terminated with semicolon, though not all statements need a semi-colon, since there’s exceptions for things like `if` or `for` expressions used as statements; a block (the thing enclosed with braces `{}` is a sequence of statements) of function body is a sequence of statements. For more details on such syntactical terminology also see the Rust Reference. “The enclosing statement” relevant for dropping temporaries, in case there’s multiple levels of statements, e.g. when you use a block, or some control-flow expression, is always the _innermost_ one.

To be more precise, for the scope of a temporary (i.e. for determining where it’s dropped) the _actual_ rules identify a few more syntactical constructs besides _just_ statements that are also used as places/boundaries where temporaries are dropped. The full list [can be found here](https://doc.rust-lang.org/reference/destructors.html#temporary-scopes).

---

<div class="post-metadata">

### Author: ![boohba](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/boohba/32/9906_2.png) [@boohba](https://internals.rust-lang.org/u/boohba)
#### Post date: [October 8, 2022, 2:48am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/4 "2022-10-08T02:48:59Z")

</div>

It's not a question. It's a language design issue. It becomes even more confusing in async context.

```rust
async fn a() {
    let b = Box::new([1, 2, 3]);

    let future = async {
        let b = &b;
    };

    future.await;
}

```

This code shouldn't compile, because the user could `await` on `future` after `b` is dropped. However, it does, because compiler is smart enough to look at the code and see how `future` is used. However, I am also pretty smart, that's why I **spent my time** to figure out what's going on.

```rust
async fn a() {
    let b = Box::new([1, 2, 3]);

    let future = async {
        let b = &b;
    };

    drop(b);

    future.await;
}

```

On the other hand, this code, in fact, does not compile, because `b` is dropped before `future.await`.

```rust
async fn a() {
    let b = [1, 2, 3];

    let future = async {
        let b = &b;
    };

    drop(b);

    future.await;
}

```

However, what happens if `b` is no longer boxed? The code compiles. Why? Probably because `b` implements `Copy`. This also adds to obscurity.

These are just simple cases, but what if I'm working with `unsafe` code? Will compiler be able to figure out what is still alive and what is dropped? I'm not sure about that. That's why I have to search for esoteric ways to initialize variables to make sure everything is valid and nothing is implicitly copied.

---

<div class="post-metadata">

### Author: ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)
#### Post date: [October 8, 2022, 2:58am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/5 "2022-10-08T02:58:53Z")

</div>

> [@boohba](#):
>
> This code shouldn't compile, because the user could `await` on `future` after `b` is dropped.

I don’t understand this statement. It isn’t made easier by the fact that you put two different variables called `b` into this example. Assuming you mean the first/outer `b`… I see `future.await;` and then the scope of the variable `b` ends, so in fact `future` is awaited _before_ `b` is dropped.

> [@boohba](#):
>
> However, what happens if `b` is no longer boxed? The code compiles. Why? Probably because `b` implements `Copy`.

Indeed, running `drop(b)` on a value `b: SomeType` where `SomeType: Copy` will _not_ invalidate `b`, so `b` can still be used after that. That’s the _very point_ (i.e. the _only_ point) of `Copy`, so I wouldn’t consider this as obscure.

---

<div class="post-metadata">

### Author: ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)
#### Post date: [October 8, 2022, 3:06am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/6 "2022-10-08T03:06:00Z")

</div>

> [@steffahn](#):
>
> > [@boohba](#):
> >
> > This code shouldn't compile, because the user could `await` on `future` after `b` is dropped.
> 
> I don’t understand this statement.

On second read, perhaps your statement was something along the lines of:

* * *

Look at this example

```rust
fn a() {
    let b = Box::new(42);
    let r = &b;
    println!("{r}");
}

```

this should not compile because `r` could be used after `b` is dropped. However it does, because the compiler is smart enough to see that we _don’t_ use `r` after `b` is dropped. In fact, if I write

```rust
fn a() {
    let b = Box::new(42);
    let r = &b;
    drop(b);
    println!("{r}");
}

```

then it doesn’t compile, but if I write

```rust
fn a() {
    let b = 42;
    let r = &b;
    drop(b);
    println!("{r}");
}

```

it does compile again. How _obscure_!

* * *

Honestly though, this kind of compiler behavior is the whole point of Rust and what makes it so successful. Of course the first code example has to compile, otherwise you essentially couldn’t use references at all, but then again, you must not do disallowed things like use-after-free so the analysis needs to be somewhat smart. Also, as demonstrated above, the behavior of the your example has little to do with `async`.

I agree that there’s a bit of a disparity in that the compiler will _give explanations_ if code _doesn’t_ compile, but _won’t give explanations_ if it _does_ compile. On the other hand, there’s a _lot, lot, lot_ of analysis going on during compilation, so I suppose it doesn’t seem easy to determine what exactly the output of a feature where you can ask the compiler “why does my code compile!?” should be.

---

<div class="post-metadata">

### Author: ![boohba](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/boohba/32/9906_2.png) [@boohba](https://internals.rust-lang.org/u/boohba)
#### Post date: [October 8, 2022, 3:24am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/7 "2022-10-08T03:24:10Z")

</div>

> this should not compile because `r` could be used after `b` is dropped. However it does, because the compiler is smart enough to see that we _don’t_ use `r` after `b` is dropped. In fact, if I write

Yes, you got that right. I apologize for not being clear enough.

> it doesn’t seem easy to determine what exactly the output of a feature where you can ask the compiler “why does my code compile!?” should be.

I believe it should be possible to implement a Cargo command that would output comments and visualize lifetimes just like `cargo check` does, even if code compiles.

Some kind of strict mode that makes compiler less smart (e.g. `#![disable(inspection_name])`) and disables implicit operations (like `Copy`) would be useful as well (not sure how the compiler works, so this might not make sense).

---

<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: [October 8, 2022, 6:13am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/8 "2022-10-08T06:13:16Z")

</div>

I’d certainly like to see an analysis tool that can highlight lifetime regions and even offer some explanation on why the lifetime is what it is. This would be very useful with elided lifetimes as well as unnameable lifetimes that you can’t write explicitly even if you want to.

---

<div class="post-metadata">

### Author: ![synek317](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/synek317/32/3371_2.png) [@synek317](https://internals.rust-lang.org/u/synek317)
#### Post date: [October 13, 2022, 7:04am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/9 "2022-10-13T07:04:20Z")

</div>

> I believe it should be possible to implement a Cargo command that would output comments and visualize lifetimes just like `cargo check` does, even if code compiles.

Do you think this could be helpful? [GitHub - rustviz/rustviz: Interactively Visualizing Ownership and Borrowing for Rust](https://github.com/rustviz/rustviz)

---

<div class="post-metadata">

### Author: ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)
#### Post date: [January 11, 2023, 7:04am UTC](https://internals.rust-lang.org/t/lifetimes-are-too-obscure/17513/10 "2023-01-11T07:04:50Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
