Using a `let` binding to increase a values lifetime

Thomas Ahle picture Thomas Ahle · Sep 28, 2014 · Viewed 7.3k times · Source

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?

Answer

Francis Gagn&#233; picture Francis Gagné · Sep 28, 2014

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);
    }
}