erip
erip

Reputation: 16935

How do I capture variables outside the scope of a closure in Rust?

I'm writing a case-insensitive anagram finder given a word and a list of words. I have the following code:

pub fn anagrams_for(s: &'static str, v: &[&'static str]) -> Vec<&'static str> {
    let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
    s_sorted.sort();

    v.iter().filter_map(move |word: &str| {
        let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
        word_sorted.sort();

        if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
            Some(word)
        } else {
            None
        }
    }).collect()
}

The logic of this is to sort the lowercase given word, and for each word in the vector, do the same. If the words are different pre-sort (to eliminate self-anagrams) but are the same post-sort, add it to the output.

The above seems to have problems capturing s and s_sorted from the surrounding scope, though, because when I compile I get the following error:

error: type mismatch: the type [closure@src/lib.rs:23:25: 32:6 s_sorted:_, s:_] implements the trait for<'r> core::ops::FnMut<(&'r str,)>, but the trait core::ops::FnMut<(&&str,)> is required (expected &-ptr, found str)

When I looked at the description of this error type ([E0281]), I found the following TL;DR:

The issue in this case is that foo is defined as accepting a Fn with no arguments, but the closure we attempted to pass to it requires one argument.

This is confusing because I thought move closures capture variables from the surrounding scope.

What am I missing?

Upvotes: 1

Views: 2256

Answers (2)

Shepmaster
Shepmaster

Reputation: 430358

This doesn't have anything to do with capturing variables in the closure. Let's check out the error message again, reformatted a bit:

type mismatch:
    the type `[closure@<anon>:5:25: 14:6 s_sorted:_, s:_]`
    implements the trait `for<'r> core::ops::FnMut<(&'r str,)>`,
    but the trait `core::ops::FnMut<(&&str,)>` is required
    (expected &-ptr, found str)

And more clearly:

found:    for<'r> core::ops::FnMut<(&'r str,)>
expected:         core::ops::FnMut<(&&str,)>

And zooming in even further:

found:    &'r str
expected: &&str

The culprit is this: |word: &str|.

You have declared that your closure accepts a string slice, but that's not what the iterator yields. v is a slice of &str, and an iterator over a slice returns references to the items in the slice. Each iterator element is a &&str.

Change your closure to |&word| and it will work. This uses pattern matching to dereference the closure argument once before the value is bound to word. Equivalently (but less idiomatically), you could use |word| and then *word inside the closure.


Additionally...

  1. You don't need to restrict yourself to 'static strings:

    pub fn anagrams_for<'a>(s: &str, v: &[&'a str]) -> Vec<&'a str> {
    
  2. It doesn't need to be a move closure.

  3. Extract the logic of creating the vector of sorted chars. This helps ensure that the logic stays consistent between the two and means you don't have to declare those vectors as mutable for longer than needed.
fn sorted_chars(s: &str) -> Vec<char> { 
    let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
    s_sorted.sort();
    s_sorted
}

pub fn anagrams_for<'a>(s: &str, v: &[&'a str]) -> Vec<&'a str> {
    let s_sorted = sorted_chars(s);

    v.iter().filter_map(|&word| {
        let word_sorted = sorted_chars(word);

        if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
            Some(word)
        } else {
            None
        }
    }).collect()
}

fn main() {}

Upvotes: 3

A.B.
A.B.

Reputation: 16630

The issue here is that you are attempting to create a Vec<&'static str> from a sequence of &&'static str.

pub fn anagrams_for(s: &'static str, v: &[&'static str]) -> Vec<&'static str> {
    let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
    s_sorted.sort();

    v.iter().cloned().filter_map(|word: &'static str| {
        let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
        word_sorted.sort();

        if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
            Some(word)
        } else {
            None
        }
    }).collect()
}

The cloned call is necessary to go from a &&'static str to a &'static str. This is an inexpensive operation since a &str is just a pointer to some utf8 sequence, plus a length.

Edit: actually an even better solution is to clone as late as possible

v.iter().filter_map(move |word: &&'static str| { // <--- adapted the type to what is actually received
    let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
    word_sorted.sort();

    if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
        Some(word.clone()) // <--- moved clone operation here
    } else {
        None
    }
}).collect()

Upvotes: 3

Related Questions