Chained vs function calls using intermediate variables

I'm new to Rust and I have a hard time understanding all the concepts of ownership / borrowing. ... even after reading all the official guides.

Why does the following code compile without any problem?

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 isn't it?

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 examples do the same thing. The only difference is that the former uses some intermediate variables, while the latter uses chaining of function calls.

When compiling the second, he yells at me with a lot

 - 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 lifelong problem. But why?

What's the difference between code samples? Why and how does this affect the lifetime?

+3


source to share


3 answers


Defining intermediate variables increases the lifetime of intermediate values. Time values (such as io::stdin()

and io::stdin().lock()

in io::stdin().lock().lines()

) cease to exist at the end of the instructions, unless they are moved (this applies to io::stdin().lock()

).

In let mut lines_iter = io::stdin().lock().lines();

:



The lifetime parameter in the return type .lock()

indicates that the lock was borrowed from the object Stdin

. When you borrow from an object, that object must live as long as at least as long as the loan. However, you are trying to get a variable that continues to the end of the function, but which borrows from an object that will be removed at the end of the statement (because it io::stdin()

is a temporary value).

Historical note. When this question was originally asked, .lines()

would have borrowed from the castle. Now .lines()

takes responsibility for blocking. This means that now you only io::stdin()

need to bind to a variable; there is no longer any need to link the result input.lock()

.

+6


source


                                             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 would not allow it

+1


source


I just thought I'd be revisiting this question since some of the details are different now. (To be honest, I just don't understand this myself, so I decided to delve into this material and write down my results.)

Let's start with this code:

use std::io::{stdin, BufRead};

fn main() {
    for l in stdin().lock().lines() {
        println!("{}", l.unwrap());
    }
}

      

Here's 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();
}

      

It still doesn't work and the error is very similar. The fact that it doesn't work suggests that the problem is with the call stdin()

.

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 take a look at the types. stdin

returns stdin

which has <method href = "https://doc.rust-lang.org/std/io/struct.Stdin.html#method.lock" rel = "nofollow"> .lock

:

fn lock(&self) -> StdinLock

      

The return type of the method StdinLock

, but if you look at its declaration, you can see that it uses the lifetime parameter:

pub struct StdinLock<'a> {
    // some fields omitted
}

      

The missing fields are the reason for this parameter, and by consulting the sources we find out what existsMutexGuard

inside, and the lifetime binding is applied to the type of value stored inside the guard. But it doesn't matter at all. The fact is that there is this lifetime parameter, which means that there was a personality of life , and the method declaration lock

is actually like this:

fn lock<'a>(&'a self) -> StdinLock<'a>  /* Self = Stdin */

      

. lock

StdinLock<'a>

. 'a

, StdinLock

, 'a

. , , lock

, , , , StdinLock<'a>

, , 'a

, .

At this point, when we realize that for the call to .lock()

be valid, the argument self

that is passed to it must be live for the entire function body, since the types tell us that the by return value .lock()

contains some references to parts of it. But it will be destroyed immediately upon completion of the instruction, unless we explicitly use let

it to help it live longer.

As a result, we get:

use std::io::{stdin, BufRead};

fn main() {
    let stdin = stdin();
    for l in stdin.lock().lines() {
        println!("{}", l.unwrap());
    }
}

      

which is working.

This, as always, comes down to property issues. The value returned from .lock()

doesn't get ownership of what it's called on (the result stdin()

in our case), but it does reference it. This means that there must be someone who takes responsibility for saving the result of the call stdin()

and destroying it at some point, and that someone must be you (and me), since there are no other options;).

On the other hand, the type .lines()

is:

fn lines(self) -> Lines<Self>  /* Self = StdinLock */

      

As you can see, it consumes self

, so we don't need to explicitly bind the result .lock()

, as the value returned from .lines()

becomes the owner of the lock, so it takes responsibility for keeping it for as long as needed and then destroying it.

+1


source







All Articles