Reputation: 4884
I'm new to Rust and I find it quite hard to understand the whole ownership/borrowing concepts. ... even after reading all the official guides.
Why does the following code compile without any issues?
use std::io;
fn main() {
let mut input = io::stdin();
let mut lock = input.lock();
let mut lines_iter = lock.lines();
for line in lines_iter {
let ok = line.ok();
let unwrap = ok.unwrap();
let slice = unwrap.as_slice();
println!("{}", slice);
}
}
... but this does not?
use std::io;
fn main() {
let mut lines_iter = io::stdin().lock().lines();
for line in lines_iter {
let slice = line.ok().unwrap().as_slice();
println!("{}", slice);
}
}
From my naive point of view, both code samples are doing exactly the same. The only difference is that the first one uses some intermediate variables while the second one is chaining the function calls.
When compiling the second one, it yells at me with a lot of
- error: borrowed value does not live long enough
- note: reference must be valid for the block at
- note:...but borrowed value is only valid for the statement
- help: consider using a `let` binding to increase its lifetime
But to be honest, I have no idea what the compiler is trying to tell me. All I understand is that I have a lifetime issue. But why?
What is the difference between both code samples? Why and how is it affecting the lifetime of what?
Upvotes: 5
Views: 1107
Reputation: 13616
I just thought I’d revisit this question, since some details are different nowadays. (To be honest, I just don’t understand this myself, so I decided to dig into this stuff and record my findings.)
We start with this code:
use std::io::{stdin, BufRead};
fn main() {
for l in stdin().lock().lines() {
println!("{}", l.unwrap());
}
}
Here is what the compiler has to say:
t.rs:4:14: 4:21 error: borrowed value does not live long enough
t.rs:4 for l in stdin().lock().lines() {
^~~~~~~
t.rs:4:5: 6:6 note: reference must be valid for the destruction scope surrounding statement at 4:4...
t.rs:4 for l in stdin().lock().lines() {
t.rs:5 println!("{}", l.unwrap());
t.rs:6 }
t.rs:4:5: 6:6 note: ...but borrowed value is only valid for the statement at 4:4
t.rs:4 for l in stdin().lock().lines() {
t.rs:5 println!("{}", l.unwrap());
t.rs:6 }
t.rs:4:5: 6:6 help: consider using a `let` binding to increase its lifetime
Let’s try something simpler:
fn main() {
let lock = stdin().lock();
}
This still doesn’t work and the error is very similar. The fact that this does not work tells us that the problem is with the stdin()
call.
t.rs:4:16: 4:23 error: borrowed value does not live long enough
t.rs:4 let lock = stdin().lock();
^~~~~~~
t.rs:3:11: 5:2 note: reference must be valid for the block at 3:10...
t.rs:3 fn main() {
t.rs:4 let lock = stdin().lock();
t.rs:5 }
t.rs:4:5: 4:31 note: ...but borrowed value is only valid for the statement at 4:4
t.rs:4 let lock = stdin().lock();
^~~~~~~~~~~~~~~~~~~~~~~~~~
t.rs:4:5: 4:31 help: consider using a `let` binding to increase its lifetime
Let’s have a look at the types. stdin
returns Stdin
, which has the .lock
method:
fn lock(&self) -> StdinLock
The return type of the method is StdinLock
, but if you look at its declaration, you’ll see that it uses a lifetime parameter:
pub struct StdinLock<'a> {
// some fields omitted
}
The omitted fields are the reason for this parameter, and by consulting the sources we learn that there is a MutexGuard
inside, and the lifetime bound is applied to the type of the value stored inside the guard. But that’s actually not important at all. The point is that there is this lifetime parameter, which means that there was lifetime elision involved and the declaration of the lock
method is actually this:
fn lock<'a>(&'a self) -> StdinLock<'a> /* Self = Stdin */
So. Our local lock
variable has type StdinLock<'a>
. This 'a
parameter of the type means that there is some reference inside StdinLock
that has to be valid for at least 'a
. On the other hand, from the fact that lock
is a local variable of our function we know that its range is the body of this function, and from the fact that its type is StdinLock<'a>
the compiler concludes that 'a
is the range corresponding to the body of the function.
It’s at this point when we realise that for the call to .lock()
to be valid the self
argument that gets passed into it has to be alive for the entire function body, since the types tell us that the value returned by .lock()
keeps some references to parts of it. But it will get destroyed right when the statement ends, unless we explicitly use let
to help it live longer.
We end up having:
use std::io::{stdin, BufRead};
fn main() {
let stdin = stdin();
for l in stdin.lock().lines() {
println!("{}", l.unwrap());
}
}
which happens to work.
That’s it, as always, everything boils down to ownership issues. The value returned from .lock()
does not take ownership of what it gets called on (the result of stdin()
in our case), but it keeps references to it. This means that there has to be someone who takes the responsibility for keeping the result of stdin()
call alive and destroying it at some point, and that someone has to be you (and me), since there are no other options ;).
On the other hand, the type of .lines()
is this:
fn lines(self) -> Lines<Self> /* Self = StdinLock */
As you can see it consumes self
, so we don’t have to explicitly bind the result of .lock()
, as the value returned from .lines()
takes ownership of the lock, therefore assumes the responsibility for keeping it alive for as long as it needs and then destroying it.
Upvotes: 1
Reputation: 65732
Defining intermediate variables extends the lifetime of the intermediate values. Temporary values (such as io::stdin()
and io::stdin().lock()
in io::stdin().lock().lines()
) cease to exist at the end of the statement, unless they're moved (which is the case of io::stdin().lock()
).
In let mut lines_iter = io::stdin().lock().lines();
:
io::stdin()
returns a new Stdin
.lock()
returns a new StdinLock<'a>
(which references the Stdin
; you don't see the <'a>
in the documentation because the lifetime was elided in the source) .lines()
returns a new Lines<StdinLock<'a>>
(which takes ownership of the lock).The lifetime parameter on the return type of .lock()
indicates that the lock borrows from the Stdin
object. When you borrow from some object, that object must live until at least as long as the borrow. However, you're trying to have a variable that lasts until the end of the function but that borrows from an object that will be dropped at the end of the statement (since io::stdin()
is a temporary value).
Historical note: When this question was originally asked, .lines()
would borrow from the lock. Now, .lines()
takes ownership of the lock instead. This means that now, only io::stdin()
needs to be bound to a variable; it's no longer necessary to bind the result of input.lock()
.
Upvotes: 6
Reputation: 5300
XXXXXXXXXXX
XXXXXXXX XXXXXXX
XX Gets destroyed after X
X end of statement XX
XX if not binded XX
+-----+ XXXXXX XXXXXXXXXX
| XXXXXXXX
v
+-------------+ +-------------+ +----------------+
| .lock() | | io::stdin()| | |
| +--------> +--------> Global |
| Lock | |StdinReader |Stdin Buffer |
| | | | | |
| | | | | |
+------^------+ +-------------+ +----------------+
|
|
|
|
+------+-------+
| .lines() |
| |
| Iterator |
| |
| |
+--------------+
So Rust wouldn't allow this
Upvotes: 1