Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining function calls vs using intermediate variables

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?

like image 546
forgemo Avatar asked Feb 12 '23 08:02

forgemo


1 Answers

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().

like image 192
Francis Gagné Avatar answered Feb 16 '23 03:02

Francis Gagné