Reputation: 16935
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 traitfor<'r> core::ops::FnMut<(&'r str,)>
, but the traitcore::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 aFn
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
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...
You don't need to restrict yourself to 'static
strings:
pub fn anagrams_for<'a>(s: &str, v: &[&'a str]) -> Vec<&'a str> {
It doesn't need to be a move
closure.
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
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