The general consensus seems to be that, when possible, you want to take slices as arguments rather than, say, Vec
s. I'm running into a problem where I can't seem to (easily) get that to work when I'm passing functions as arguments, because Rust doesn't "know" to (or that it can) auto-deref the argument to the function I'm passing in.
The core of the problem is illustrated here:
type Bitstring = Vec<bool>;
struct ScoredItem<T> {
item: T,
score: i64
}
// The "preferred" argument type, i.e., a slice
fn count_ones(bits: &[bool]) -> i64 {
bits.iter().filter(|&&bit| bit).count() as i64
}
fn make_scored_item<T>(item: T, scorer: impl Fn(&T) -> i64) -> ScoredItem<T> {
let score = scorer(&item);
ScoredItem {
item,
score
}
}
Given a some_bitstring: Bitstring
(i.e., Vec<bool>
), I'd like to be able to make calls like
make_scored_item(some_bitstring, count_ones)
These fail, however, because the compiler can't match the type of count_ones
(which takes the preferable &[bool]
argument type) to the actual type &Vec<bool>
.
Fix 1: Have count_ones
take &Vec<bool>
If I just change count_ones
to take &Vec<bool>
as its argument, everything is swell. But that runs counter to the prevailing suggestion that I want to take a slice (&[bool]
) instead.
Fix 2: A helper function that quietly converts
Another fix is to make a helper function that just passes the call through, "magically fixing things" by converting from &Vec<bool>
to &[bool]
along the way:
fn count_ones_vec(bits: &Bitstring) -> i64 {
count_ones(bits)
}
With this I can successfully call make_scored_item(some_bitstring, count_ones_vec)
. It's annoying to have this around, though, especially since it takes the argument type we were trying to avoid. (It doesn't look like it's actually a performance problem, though, as I think the compiler is inlining it straight away.)
Fix 3: Introduce a closure that quietly converts
Alternatively (and nearly identically), I can introduce a closure that will "magically" perform the necessary conversion:
make_scored_item(some_bitstring, |bits| count_ones(bits))
Having a closure of the form |x| f(x)
should in general be replaced by just f
, though, and in fact Clippy will usually complain about exactly this thing. It doesn't here, though, because I think it "realizes" that the conversion happening in the closure is important.
Full example
A full example is in the playground. If you uncomment the line:
// let scored_bits = make_scored_item(make_bitstring(20), count_ones);
the code will no longer compile:
13 | fn count_ones(bits: &[bool]) -> i64 {
| ----------------------------------- found signature of `for<'r> fn(&'r [bool]) -> _`
...
31 | let scored_bits = make_scored_bits(20, count_ones);
| ---------------- ^^^^^^^^^^ expected signature of `for<'r> fn(&'r Vec<bool>) -> _`
| |
| required by a bound introduced by this call
Thanks for suggestions
Anyone know a better way to deal with this? I'm fairly new to Rust, so I can totally imagine that there's a simple way to make all this better. I wasn't able to find anything in my searching, although that might have just been trouble figuring out exactly what to search for.
Many thanks – Nic