首页 > 解决方案 > How to write a function that takes a slice of functions?

问题描述

I am trying to write a function that takes a slice of functions. Consider the following simple illustration:

fn g<P: Fn(&str) -> usize>(ps: &[P]) { }

fn f1() -> impl Fn(&str) -> usize { |s: &str| s.len() }
fn f2() -> impl Fn(&str) -> usize { |s: &str| s.len() }

fn main() {
    g(&[f1(), f2()][..]);
}

It fails to compile:

error[E0308]: mismatched types
 --> src/main.rs:6:15
  |
6 |     g(&[f1(), f2()][..]);
  |               ^^^^ expected opaque type, found a different opaque type
  |
  = note: expected type `impl for<'r> std::ops::Fn<(&'r str,)>` (opaque type)
             found type `impl for<'r> std::ops::Fn<(&'r str,)>` (opaque type)

Is there any way to do this?

标签: functionrusthigher-order-functions

解决方案


Your problem is that every element of the array must be of the same type, but the return of a function declared as returning impl Trait is an opaque type, that is an unspecified, unnamed type, that you can only use by means of the given trait.

You have two functions that return the same impl Trait but that does not mean that they return the same type. In fact, as your compiler shows, they are different opaque types, so they cannot be part of the same array. If you were to write an array of values of the same type, such as:

    g(&[f1(), f1(), f1()]);

then it would work. But with different functions, there will be different types and the array is impossible to build.

Does that mean there is no solution for your problem? Of course not! You just have to invoke dynamic dispatch. That is you have to make your slice of type &[&dyn Fn(&str) -> usize]. For that you need to do two things:

  1. Add a level of indirection: dynamic dispatching is always done via references or pointers (&dyn Trait or Box<dyn Trait> instead of Trait).
  2. Do an explicit cast to the &dyn Trait to avoid ambiguities in the conversion.

There are many ways to do the cast: you can cast the first element of the array, or you can declare the temporary variables, or give the slice a type. I prefer the latter, because it is more symmetric. Something like this:

fn main() {
    let fns: &[&dyn Fn(&str) -> usize] = 
        &[&f1(), &f2()];
    g(fns);
}

Link to a playground with this solution.


推荐阅读