Lifetimes un-elided, it looks like:
which translates to the following:
- The returned
StdinLockis valid for the lifetime
- The lifetime
'aoriginates from a borrow of a
It also implies that the
StdinLockmust be dropped before mutably borrowing the
Stdinstruct, and that we can't drop the
Stdinstruct before dropping the
So what happens when we use
pub fn stdin() -> Stdin, btw.
A wild compile error appears!
error[E0716]: temporary value dropped while borrowed --> src/main.rs:3:14 | 3 | let locked = io::stdin().lock(); | ^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary which is freed while still in use 4 | } | - borrow might be used here, when `locked` is dropped and runs the destructor for type `StdinLock<'_>` | = note: consider using a `let` binding to create a longer lived value
But where did the borrow come from? Well, when calling a method with an
&selfparameter with an owned value, the Rust compiler can implicitly borrow the owned value to call the
- A borrow is against an owned value. That is, borrows can't be conjured from thin air. Borrows aren't pointers. You can't hold a value with a borrow, you need an owner backing it.
- A borrow can only last as long as the owned value. It can't live after the owned value is dropped.
- Temporaries are dropped at the end of a statement (except when they're not), while values stored in
lets drop at the end of a block.
For more information on dropping behaviour, see Drop Order.
For a much more concise explanation of the problem, I defer to the original issue for changing
The explanation is that the lock behaves as if it borrows the original handle from stdin(), and the temporary value created for the call to the lock() method is dropped at the end of the statement, invalidating the borrow.
Reproducing the problem§
Why don't you encounter the same problem when borrowing in other places? Most reference parameters to functions don't have their lifetimes tied to the output in the same way. This compiles just fine:
while the de-elided (delided?) signature of
pub fn as_str(&self) -> &str, which also returns a reference with the same lifetime as the input..
However! If we add but a return argument to
What. I totally thought I was going to prove my point there. What if I...
error[E0716]: temporary value dropped while borrowed --> src/main.rs:7:20 | 7 | let s = print_str(String::from("abc").as_str()); | ^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary which is freed while still in use 8 | dbg!(s); | - borrow later used here | help: consider using a `let` binding to create a longer lived value | 7 ~ let binding = String::from("abc"); 8 ~ let s = print_str(binding.as_str()); |
Our borrowed value (reference) can only live as long as the value we borrow against, and in this case, that temporary is dropped at the end of the statement. Previously, the Rust compiler didn't give us an error because we weren't using the borrow later, and the lifetime of the borrow could be the same as the temporary.
But in this case, the reference that
print_str returns has the sameish lifetime as the one that
as_str returns, which is limited by the temporary
Once you start learning about all the weird tricks the Rust compiler has in order to make lifetimes convenient, you get uncomfortable calling two lifetimes "equal" or the "same".
The solution is exactly what the compiler suggests: as soon as we put our owned value in a
let statement, it will be dropped at the end of scope and we can freely borrow against it until then.
So how did the Rust community change
Stdin::lock so that users no longer faced this error? They changed the signature to
pub fn lock(&self) -> StdinLock<'static>, which decouples the lifetime relationship, allowing the returned
StdinLock to outlive the
&self reference. How they did that is left to reader.
- Variance and Covariance, and how it relates to Rust lifetimes - they can shrink.
- Pretzelhammer's article on Common Rust Lifetime Misconceptions will blow your mind if you've only read the Book so far.
- fasterthanlime's "A Rust match made in hell" explores a similar problem related to how match expression scrutinees can extend temporary lifetimes, and how that conflicts with locks.
As always, feel free to write to me to point out an error, suggest a topic, or just say hi!