error: `line` does not live long enough but it'

2019-07-25 15:17发布

I can't figure it out why my local var line does not live long enough. You can see bellow my code. It work on the Rust's playground.

I may have an idea of the issue: I use a structure (load is a function of this structure). As I want to store the result of the line in a member of my struct, it could be the issue. But I don't see what should I do to resolve this problem.

pub struct Config<'a> {
    file: &'a str,
    params: HashMap<&'a str, &'a str>
}

impl<'a> Config<'a> {
    pub fn new(file: &str) -> Config {
        Config { file: file, params: HashMap::new() }
    }

    pub fn load(&mut self) -> () {
        let f = match fs::File::open(self.file) {
            Ok(e) => e,
            Err(e) => {
                println!("Failed to load {}, {}", self.file, e);
                return;
            }
        };
        let mut reader = io::BufReader::new(f);
        let mut buffer = String::new();

        loop {
            let result = reader.read_line(&mut buffer);

            if result.is_ok() && result.ok().unwrap() > 0 {
                let line: Vec<String> = buffer.split("=").map(String::from).collect();

                let key = line[0].trim();
                let value = line[1].trim();

                self.params.insert(key, value);
            }

            buffer.clear();
        }
    }
    ...
}

And I get this error:

src/conf.rs:33:27: 33:31 error: `line` does not live long enough
src/conf.rs:33                 let key = line[0].trim();
                                         ^~~~
src/conf.rs:16:34: 41:6 note: reference must be valid for the lifetime 'a as defined on the block at 16:33...
src/conf.rs:16     pub fn load(&mut self) -> () {
src/conf.rs:17         let f = match fs::File::open(self.file) {
src/conf.rs:18             Ok(e) => e,
src/conf.rs:19             Err(e) => {
src/conf.rs:20                 println!("Failed to load {}, {}", self.file, e);
src/conf.rs:21                 return;
               ...
src/conf.rs:31:87: 37:14 note: ...but borrowed value is only valid for the block suffix following statement 0 at 31:86
src/conf.rs:31                 let line: Vec<String> = buffer.split("=").map(String::from).collect();
src/conf.rs:32 
src/conf.rs:33                 let key = line[0].trim();
src/conf.rs:34                 let value = line[1].trim();
src/conf.rs:35 
src/conf.rs:36                 self.params.insert(key, value);
               ...

标签: rust lifetime
2条回答
聊天终结者
2楼-- · 2019-07-25 15:52

In this case you have to use String instead of &str. See this to understand the difference.

You can also eliminate the creation of the intermediate vector and use the iterator return by split direct

pub struct Config<'a> {
    file: &'a str,
    params: HashMap<String, String>
}

...

let mut line = buffer.split("=");
let key = line.next().unwrap().trim().to_string();
let value = line.next().unwrap().trim().to_string();
查看更多
虎瘦雄心在
3楼-- · 2019-07-25 15:58

There are three steps in realizing why this does not work.

let line: Vec<String> = buffer.split("=").map(String::from).collect();
let key = line[0].trim();
let value = line[1].trim();
self.params.insert(key, value);
  1. line is a Vec of Strings, meaning the vector owns the strings its containing. An effect of this is that when the vector is freed from memory, the elements, the strings, are also freed.
  2. If we look at string::trim here, we see that it takes and returns a &str. In other words, the function does not allocate anything, or transfer ownership - the string it returns is simply a slice of the original string. So if we were to free the original string, the trimmed string would not have valid data.

  3. The signature of HashMap::insert is fn insert(&mut self, k: K, v: V) -> Option<V>. The function moves both the key and the value, because these needs to be valid for as long as they may be in the hashmap. We would like to give the hashmap the two strings. However, both key and value are just references to strings which is owned by the vector - we are just borrowing them - so we can't give them away.

The solution is simple: copy the strings after they have been split.

let line: Vec<String> = buffer.split("=").map(String::from).collect();
let key = line[0].trim().to_string();
let value = line[1].trim().to_string();
self.params.insert(key, value);

This will allocate two new strings, and copy the trimmed slices into the new strings.


We could have moved the string out of the vector(ie. with Vec::remove), if we didn't trim the strings afterwards; I was unable to find a easy way of trimming a string without allocating a new one.

In addition, as malbarbo mentions, we can avoid the extra allocation that is done with map(String::from), and the creation of the vector with collect(), by simply omitting them.

查看更多
登录 后发表回答