Alex
Alex

Reputation: 2402

Getting temporary value out of map

I am trying to achieve the following: Given an array slice of strs:

The error returned is that candidate_graphemes references a temporary value. I kind of understand why, but I would appreciate an explanation, and I'm not entirely sure how to fix it. I'm fairly new to rust...

use std::collections::HashSet;
use unicode_segmentation::UnicodeSegmentation;

pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> HashSet<&'a str> {
    let sorted_possible_anagrams = possible_anagrams
        .iter()
        .map(|candidate| {
            let mut candidate_graphemes = candidate
                .to_lowercase()
                .graphemes(true)
                .collect::<Vec<&str>>();
             candidate_graphemes.sort();
             candidate_graphemes
        })
        .collect::<Vec<Vec<&str>>>();
    let mut results = HashSet::new();
    results
}

Upvotes: 1

Views: 596

Answers (1)

Gymore
Gymore

Reputation: 605

If you look in your map closure, you can see that you are using the .to_lowercase() method on a &str which returns a String.

This string belongs to the closure.

The allocation is needed because the &str is a shared reference therefor you cannot mutate the str directly through it.

When you call the .graphemes method, you get back an iterator of &str but those references are actually different form the first one: their lifetime is constrained inside of the closure while the lifetime of your original references (the &strs in possible_anagrams) have the lifetime 'a (defined in your function's generic parameters).

/* ... */ .map(|candidate| {
    let lowercase_string = candidate.to_lowercase(); // here is the `String`
    let graphemes = lowercase_string.graphemes(true); // Here is the `Iterator<Item = &'b str>` where 'b is the lifetime of `lowercase_string`
    let mut vec = graphemes.collect::<Vec<&str>>()
    vec.sort();
    vec // The lifetime 'b ends here (so `lowercase_string` gets dropped) but you're trying to move references to this `String` out of the closure.
}) /* ... */

This is why your code doesn't compile.

A possible fix would be to return Strings

let mut candidate_graphemes = candidate
    .to_lowercase()
    .graphemes(true)
    .map(|s| String::from(s)) // add this
    .collect::<Vec<String>>(); // `String`s do not hold any lifetimes, they are simply moved out of the closure

And all the returned strings would be lowercased. A downside to this is that all those Strings would need to be allocated on the heap. This would make your function much slower.

Another fix would be to ask possible_anagrams to always contain lowercased &strs. This would prevent your map from needing to allocate Strings for each candidate/grapheme.

let mut candidate_graphemes = candidate
    // .to_lowercase() we know candidate is already lowercase
    .graphemes(true)
    .collect::<Vec<&str>>(); // now, the `&str`s have the correct lifetime of `'a`
candidate_graphemes.sort();
candidate_graphemes

Upvotes: 3

Related Questions