Lifetime of variable in map/flat_map in Rust

时光总嘲笑我的痴心妄想 提交于 2021-01-28 20:05:00

问题


I think I have a good understanding of lifetimes, but what I've read doesn't tick with what the compiler says when it comes to closures ;)

I have a function with the signature:

fn split_to_words(content: &str) -> Vec<String>

With for loops it looks like this:

let mut acc: Vec<String> = Vec::new();
for line in content.lines() {
    if line.is_empty() { continue };
    for word in line.split_whitespace() {
        acc.push(word.to_lowercase());
    }
}

and using iterators:

let acc: Vec<String> = content.lines()
      .filter(|x| !x.is_empty())
      .map(|x| x.to_lowercase())
      .flat_map(|x: String| x.split_whitespace())
      .map(|x| x.to_string())
      .collect();

but I end up with an error:

error: `x` does not live long enough

.flat_map(|x: String| x.split_whitespace())
                      ^

回答1:


Your iterator-style code isn't exactly the same as your imperative-style code: in the imperative code, you do split_whitespace before to_lowercase, whereas in the iterator code, you do the opposite. It turns out that doing split_whitespace before is more efficient, since split_whitespace doesn't need to allocate Strings; it only returns slices into the given string slice. On the other hand, to_lowercase needs to allocate a new string, so by doing it last, we can save an allocation.

let acc: Vec<String> = content.lines()
      .filter(|x| !x.is_empty())
      .flat_map(|x| x.split_whitespace())
      .map(|x| x.to_lowercase())
      .collect();



回答2:


It's helpful to review the entire error message:

error: `x` does not live long enough
note: reference must be valid for the method call at ...
note: ...but borrowed value is only valid for the scope
      of parameters for function at ...

When flat_map is called, you pass ownership of the String to the closure. However, the closure tries to return an iterator that contains references to the String. Since each String will be deallocated after the closure call ends, the reference into it would be invalid, so the compiler prevents you from doing this.

I'd probably use the for-loop variant or a similar version:

fn split_to_words(content: &str) -> Vec<String> {
    let lines = content.lines()
           .filter(|x| !x.is_empty())
           .map(|x| x.to_lowercase());

    let mut result = Vec::new();

    for line in lines {
        for word in line.split_whitespace() {
            result.push(word.to_string());
        }
    }

    result
}

fn main() -> () {
    println!("{:?}", split_to_words("hello world"));
}

In this particular case though, I think you could write it as:

fn split_to_words(content: &str) -> Vec<String> {
    content.split_whitespace().map(str::to_lowercase).collect()
}

Newlines are whitespace, so there's no need to use lines. This also removes the possibility of blank values.



来源:https://stackoverflow.com/questions/36967681/lifetime-of-variable-in-map-flat-map-in-rust

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!