0

Considering this piece of valid Rust code

fn f(pt: &[f64 ; 3]) -> f64 {
    pt[0] + pt[1] + pt[2]   
}

fn main(){
    let f1d_array: [_; 3] = std::array::from_fn(|dir| {
        move |pt: [f64; 3]| {
            move |x: f64| -> f64 {
                let mut pt_new = pt;
                pt_new[dir] = x;
    
                f(&pt_new)
            }
        }
    });
    
    let pt = [0., 1., 2.];
    let f1d_0 = f1d_array[0](pt);
    assert_eq!(f1d_0(0.),3.);
    assert_eq!(f1d_0(1.),4.);
    
    let f1d_1 = f1d_array[1](pt);
    assert_eq!(f1d_1(0.),2.);
    assert_eq!(f1d_1(1.),3.);
    
    let f1d_2 = f1d_array[2](pt);
    assert_eq!(f1d_2(0.),1.);
    assert_eq!(f1d_2(1.),2.);
}

which is the type of the variable f1d_array? I need to know it in order to correctly define a function that uses the type of f1d_array as input argument.

Here the link to Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cabc0d9a5f496e3da0b467b9c46f2535

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77

2 Answers2

4

You can just let the compiler tell you it's [[closure@main.rs:9:9]; 3] but you can't name that type.

So to create a function that takes your closures as parameter you have to resort to generics:

fn takes_closures<F, G, const N: usize>(a: [F;N])
where
    F: Fn([f64; 3]) -> G,
    G: Fn(f64) -> f64,
{
    todo!()
}

Playground

cafce25
  • 15,907
  • 4
  • 25
  • 31
1

@cafce25's answer is correct; The type is currently unnameable. And since you'd likely want your function to take any type that "is like" your closure, generics are the way to go for most cases when accepting closures.

However, if you are sure you only need the function to take in exactly your closure type, and none others, there is a way to name it's type in unstable rust:

#![feature(type_alias_impl_trait)]

type F = impl Fn([f64; 3]) -> G;
type G = impl Fn(f64) -> f64;


fn takes_closures(a: [F;3]) {}

(Playground)

This uses the type_alias_impl_trait feature, which lets you give another name to a type (even those without any name, such as your closure) by specifying it's properties instead.

This is very similar to generics, but in the other direction. When using generics in the function, it's essentially saying "I accept any value with these properties". However, when using these type alias, the function is instead saying: "There exists a type with these properties, I can take it and only it".

It is similar to the difference between fn(value: impl Trait) and fn() -> impl Trait. Both fn() -> impl Trait and type = impl Trait define what are called existential types, i.e. types that exist, but you don't want to / can't refer to them by their actual name, only by their properties.

There is a catch, however. Notice how I say "There exists a type"? When using type alias, you must use the alias somewhere so the compiler knows what the actual underlying type is. If you don't, the compiler will yell at you:

error: unconstrained opaque type
  --> src/main.rs:40:10
   |
40 | type H = impl Fn(f64) -> f64;
   |          ^^^^^^^^^^^^^^^^^^^
   |
   = note: `H` must be used in combination with a concrete type within the same module

All in all however, these are usually used when returning values, not when accepting them. Since you can't create a fn() -> impl Fn() -> impl Fn(), these are the workaround for that, since you can define fn() -> F; type F = impl Fn() -> G; type G = impl Fn();.

There might be use cases for accepting existential types, such as FFI functions that can't take generics.

Filipe Rodrigues
  • 1,843
  • 2
  • 12
  • 21