Reputation: 1813
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())
^
Upvotes: 1
Views: 1760
Reputation: 430514
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.
Upvotes: 2
Reputation: 65692
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();
Upvotes: 3