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