Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing from inside a loop a borrowed value to container in outer scope?

Tags:

rust

I set myself a little task to acquire some basic Rust knowledge. The task was:

Read some key-value pairs from stdin and put them into a hashmap.

This, however, turned out to be a trickier challenge than expected. Mainly due to the understanding of lifetimes. The following code is what I currently have after a few experiments, but the compiler just doesn't stop yelling at me.

use std::io;
use std::collections::HashMap;

fn main() {
    let mut input       = io::stdin(); 
    let mut lock        = input.lock(); 
    let mut lines_iter  = lock.lines();

    let mut map         = HashMap::new();

    for line in lines_iter {
        let text                = line.ok().unwrap();
        let kv_pair: Vec<&str>  = text.words().take(2).collect();

        map.insert(kv_pair[0], kv_pair[1]);
    }

    println!("{}", map.len());
}

The compiler basically says:

`text` does not live long enough

As far as I understand, this is because the lifetime of 'text' is limited to the scope of the loop. The key-value pair that I'm extracting within the loop is therefore also bound to the loops boundaries. Thus, inserting them to the outer map would lead to a dangling pointer since 'text' will be destroyed after each iteration. (Please tell me if I'm wrong)

The big question is: How to solve this issue?

My intuition says:

Make an "owned copy" of the key value pair and "expand" it's lifetime to the outer scope .... but I have no idea how to achieve this.

like image 966
forgemo Avatar asked Dec 14 '14 20:12

forgemo


2 Answers

The lifetime of 'text' is limited to the scope of the loop. The key-value pair that I'm extracting within the loop is therefore also bound to the loops boundaries. Thus, inserting them to the outer map would lead to an dangling pointer since 'text' will be destroyed after each iteration.

Sounds right to me.

Make an "owned copy" of the key value pair.

An owned &str is a String:

map.insert(kv_pair[0].to_string(), kv_pair[1].to_string());

Edit

The original code is below, but I've updated the answer above to be more idiomatic

map.insert(String::from_str(kv_pair[0]), String::from_str(kv_pair[1]));
like image 163
Shepmaster Avatar answered Oct 20 '22 19:10

Shepmaster


In Rust 1.1 the function words was marked as deprecated. Now you should use split_whitespace.

Here is an alternative solution which is a bit more functional and idiomatic (works with 1.3).

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

fn main() {
    let stdin = io::stdin();

    // iterate over all lines, "change" the lines and collect into `HashMap`
    let map: HashMap<_, _> = stdin.lock().lines().filter_map(|line_res| {
        // convert `Result` to `Option` and map the `Some`-value to a pair of
        // `String`s
        line_res.ok().map(|line| {
            let kv: Vec<_> = line.split_whitespace().take(2).collect();
            (kv[0].to_owned(), kv[1].to_owned())
        })
    }).collect();

    println!("{}", map.len());
}
like image 41
Lukas Kalbertodt Avatar answered Oct 20 '22 17:10

Lukas Kalbertodt