Variable does not live long enough


#1

For the code, if I comment out the commented lines, it does not compile. Could anyone give me some help to make it work? Thanks.

what I do in the code is to go through the file line by line, extract some key value pair in certain line, and put them into hashmap.

use std::io::prelude::*;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;

fn main () {

    let lines = BufReader::new(File::open("./data/hello.txt").unwrap()).lines();
  //  let mut data_map = HashMap::new();
    for line in lines {
            let line_content = line.unwrap();
            let v: Vec<&str> = line_content.split("\t").collect();
            if v.len() ==  6 {
//              data_map.insert(v[1], v[5]);
            }
    }   
        
 //   print!("\n{:?}\n", data_map);
}

#2

In the let v: Vec<&str> line, you’re collecting a vector of &str and each &str is a reference to line_content, which is a String. line_content goes out of scope at the end of each iterator of your for loop, but you’re trying to save a reference to that string beyond the end of the for loop by inserting it into a hashmap. (Which, in your code, lives until the end of main.) Rust disallows this because it would be a use-after-free bug.

One way to fix this is to convert your string slices to owned String values. e.g., data_map.insert(v[1].to_string(), v[5].to_string). That way, the strings will be put on the heap and owned by data_map.


#3

Thank for the explanation. I have a vague understanding by go through the rust book, which talk about ownership and transfer in fn call. I am lost in how to make the line_content live longer. your solution is what I never thinking of and works great.


#4

The trick is that Rust (and the standard library) make allocation very explicit. If an operation can be done without allocating, then that’s what will happen—like splitting a string. If you want the result of those operations to live beyond the data initially used to create them, then you’ll probably want to allocate, which is what to_string() is doing. This pattern will recur as you write more Rust. :smile:


#5

Using to_owned is the better way of converting &str to String.


#6

It’s faster, but requires an explicit import of std::borrow::ToOwned. Using to_string() is therefore quite a bit more convenient.


#7

Yeah, but going through the format machinery isn’t what you usually want for simply converting a &str to String, best practice is to use to_owned anyway – it’s one import for the whole file.


#8

One import is less convenient than zero imports. (I thought I saw something about ToOwned getting into the prelude, which would fix that.)


#9

Thanks. since I am learning, it is good to know all the possibility.


#10

yeah, I can image ‘var not live long enough situation’ is not rare. besides, ownership is unique feature in rust, it is reasonable to make it convenient to do so.


#11

I have another problem to get var out. if I comment out the commented line, compiler tell me ‘use of possibly uninitialized variable:’

use std::io::prelude::*;
use std::fs::File;
use std::io::BufReader;
use std::borrow::ToOwned;

fn main () {

    let lines = BufReader::new(File::open("./data/hello.txt").unwrap()).lines();

    let mut sample_name;

    for line in lines {
            let line_content = line.unwrap();
            if line_content.contains("Sample Name") {
               sample_name = line_content.split("\t").nth(1).unwrap().to_owned();
            }
    }   
 //   println!("{}", sample_name);
}

I can get it work by mimic the case of hashmap.(create it first and then put value to it)

let mut sample_name = String::new();

and then inside the block, push_str to sample_name

  sample_name.push_str(line_content.split("\t").nth(1).unwrap());

but it feel not natural. is it a better way to do this? Thanks.


#12

You can use Option:

fn main () {
    let lines = BufReader::new(File::open("./data/hello.txt").unwrap()).lines();

    let mut sample_name = None;

        for line in lines {
            let line_content = line.unwrap();
            if line_content.contains("Sample Name") {
                sample_name = Some(line_content.split("\t").nth(1).unwrap().to_owned());
            }
        }

        if let Some(name) = sample_name {
            println!("{}", name);
        }
        else {
            // panic?
        }

}

#13

I unwarped after nth already, so no option


#14

I think it can be better written as:

sample_name = line_content.split("\t").nth(1).map(|s| s.to_owned());

since .nth() returns option already.

Edit
Or even this way:

sample_name = line_content.split("\t").nth(1).map(ToOwned::to_owned);

#15

sorry, I did not saw your change in those two lines. I tried this, and it works. but I do not understand it.

why change the type of sample_name to Option and it works. what is wrong with my previous code? Thanks.


#16

You left your sample_name uninitialized in declaration, so rustc complained, as it could be still uninitialized when used in println!() (if there are no lines, or no lines matching if criterion, this variable would never get assigned).

In case of Option, sample_name is initialized from very beginning with None value, and when (or if) some new value is found for it, it becomes Some(value). You see, no more uninitialized variable.

Then before usage in println!() you unwrap it with if let ensuraring it’s not None, and then use it safely.

You can think of None as C’s NULL, but typesafe and working for all types, not just for pointers.


#17

I notice the ToOwned is in the prelude, so I tried it.

but I still can not use it without import. any idea?


#18

Is your Rust up to date?


#19

I update my rust in the morning, does not work. I update just now. still the same.

rustc 1.0.0-nightly (2baf34825 2015-04-21) (built 2015-04-22)

If I uncomment the use, it work.

//use std::borrow::ToOwned;


#20

Very odd. It landed five days ago: https://github.com/rust-lang/rust/commit/8f5b5f94dcdb9884737dfbc8efd893d1d70f0b14#diff-76de6a317ef548f8aa4e7dda5815a209