rust-lang / reference

The Rust Reference
https://doc.rust-lang.org/nightly/reference/
Apache License 2.0
1.25k stars 491 forks source link

Documentation on lifetime extension is confusing (and in a confusing location) #1605

Open RalfJung opened 2 months ago

RalfJung commented 2 months ago

See here for some context:

The reference says about lifetime extension

If a borrow, dereference, field, or tuple indexing expression has an extended temporary scope then so does its operand. If an indexing expression has an extended temporary scope then the indexed expression also has an extended temporary scope.

I read this 5 times and still didn't know what it was supposed to tell me. Thankfully, @m-ou-se came to the rescue. :)

Temporary lifetime extension today always involves: a let statement, a & borrow expression in there, a temporary (e.g. temp()) inside of that.

trivial example is let a = &temp();

There is a set of operations that you can put between the let and &: borrow, cast, tuple expression, braced struct expression, array expression, or block expression. e.g. let a = {[(&temp() as _,)]}; will extend temp(). There is a set of operations that you can put between the & and the temporary: borrow, dereference, field, tuple index, or index expression. e.g. let a = &temp().field.0[1]; will extend temp().

combined example is let a = Some { 0: &temp().field };

So the algorithm is: from the initializer expression, traverse through borrows, cast, tuple, braced struct, array, block until we hit an &. Then traverse further through place projections. Then extend that.

The docs currently describe the inner part first. Also it is not clear what it means that "if a field expression has extended scope then so does its operand" -- really it's the operand that gets lifetime extended, and then the field of course inherits the lifetime from its base place.

And finally I think the location in the docs where this is discussed is confusing: this is explained in the page about destructors, but lifetime extension is relevant even without drop -- like when one creates a raw pointer to some memory and then later needs to be sure the memory the pointer points to is still live.