Using a `let` binding to increase a values lifetime

10,477

Solution 1

In your second version, the type of ss is Split<'a, char>. The lifetime parameter in the type tells us that the object contains a reference. In order for the assignment to be valid, the reference must point to an object that exists after that statement. However, unwrap() consumes line; in other words, it moves Ok variant's data out of the Result object. Therefore, the reference doesn't point inside the original line, but rather on a temporary object.

In your first version, you consume the temporary by the end of the long expression, though the call to map. To fix your second version, you need to bind the result of unwrap() to keep the value living long enough:

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

fn main() {
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let line = line.unwrap();
        let ss = line.trim().split(' ');
        let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();

        println!("{:?}", xs);
    }
}

Solution 2

It's about the unwrap() call, it's getting the contained object but this reference should outlive the container object, which goes out of scope in the next line (there is no local binding to it).

If you want to get cleaner code, a very common way to write it is:

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

fn main() {
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let xs: Vec<i32> = line.unwrap()
            .trim()
            .split(' ')
            .map(|s| s.parse().unwrap())
            .collect();

        println!("{:?}", xs);
    }
}

If not, you can create the binding to the "unwrapped" result and use it.

Share:
10,477
Thomas Ahle
Author by

Thomas Ahle

Updated on June 05, 2022

Comments

  • Thomas Ahle
    Thomas Ahle almost 2 years

    I wrote the following code to read an array of integers from stdin:

    use std::io::{self, BufRead};
    
    fn main() {
        let stdin = io::stdin();
        for line in stdin.lock().lines() {
            let xs: Vec<i32> = line.unwrap()
                .trim()
                .split(' ')
                .map(|s| s.parse().unwrap())
                .collect();
    
            println!("{:?}", xs);
        }
    }
    

    This worked fine, however, I felt the let xs line was a bit long, so I split it into two:

    use std::io::{self, BufRead};
    
    fn main() {
        let stdin = io::stdin();
        for line in stdin.lock().lines() {
            let ss = line.unwrap().trim().split(' ');
            let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();
    
            println!("{:?}", xs);
        }
    }
    

    This didn't work! Rust replied with the following error:

    error[E0597]: borrowed value does not live long enough
      --> src/main.rs:6:18
       |
    6  |         let ss = line.unwrap().trim().split(' ');
       |                  ^^^^^^^^^^^^^                  - temporary value dropped here while still borrowed
       |                  |
       |                  temporary value does not live long enough
    ...
    10 |     }
       |     - temporary value needs to live until here
       |
       = note: consider using a `let` binding to increase its lifetime
    

    This confuses me. Is it line or ss that doesn't live long enough? And how can I use a let binding to increase their lifetime? I thought I was already using a let?

    I've read through the lifetime guide, but I still can't quite figure it out. Can anyone give me a hint?

  • Thomas Ahle
    Thomas Ahle over 9 years
    So you are saying that the line is out of scope in the let xs line, but not in the let ss line, even though they are in the same block?
  • snf
    snf over 9 years
    It's the line.unwrap() result that goes out of scope.
  • Thomas Ahle
    Thomas Ahle over 9 years
    Just to make sure I understand you correctly, CharSplits references the value inside line. But actually it references a copy taken from Ok, which is thrown away as soon as the let ss line ends. Instead of just keeping all values alive till the end of the block?
  • Francis Gagné
    Francis Gagné over 9 years
    Yes. Temporary values are only valid for the statement in which the expression that produces that value is located.
  • Thomas Ahle
    Thomas Ahle over 9 years
    Thank you I think I'm starting to get it.
  • Francis Gagné
    Francis Gagné over 9 years
    Watch out, there are 2 variables named line here: the first one is an IoResult<String> (= Result<String, IoError>), the second is a String. unwrap() moves the String out of the IoResult, and the IoResult is unusable after that (which is also why I'm reusing the name line: you wouldn't be able to use the first line anyway). The String is not copied at all.
  • Thomas Ahle
    Thomas Ahle over 9 years
    That's very interesting. How is a move like that done? Is the data copied and the original destroyed? Or was the String on heap and the original pointer to it was destroyed?
  • Francis Gagné
    Francis Gagné over 9 years
    A String struct contains a Vec, which contains a pointer to the data (stored on the heap), a length and a capacity. When a move occurs, the struct's members are copied, but the referenced data is not copied and no destructor is run. The original copy is then made unusable by the compiler (you'll get an error if you try to use the value) so you don't access data whose ownership has been transferred.