How to specify types on this generic partial application?

Is it possible to create make_adapter here? playground link
The basic idea is simple - I’m binding an argument, so I can then create multiple partial applications by calling the result of make_adapter. But I can't figure out how to specify the types.


fn make_adapter<F1, F2>(b: i32) -> impl Fn(F2) -> F1
    where F1: Fn(i32) -> (), F2: Fn(i32, i32) -> ()
{
    let adapt = |f2: F2| -> F1 {
        let f1 = move |a: i32| {
            f2(a, b);
        };
        f1
    };
    adapt
}

fn main() {
    let a: i32 = 123;
    let adapt = make_adapter(456);
    let f1: Box<dyn Fn(i32) -> ()> = Box::new(
        adapt(|a,b| println!("called: {}, {}", a, b))
    );
    f1(a);
}

Errors:

error[E0308]: mismatched types
 --> src/main.rs:9:9
  |
2 | fn make_adapter<F1, F2>(b: i32) -> impl Fn(F2) -> F1
  |                 -- expected this type parameter
...
5 |     let adapt = |f2: F2| -> F1 {
  |                             -- expected `F1` because of return type
6 |         let f1 = move |a: i32| {
  |                  ------------- the found closure
...
9 |         f1
  |         ^^ expected type parameter `F1`, found closure
  |
  = note: expected type parameter `F1`
                    found closure `{closure@src/main.rs:6:18: 6:31}`
  = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `F1`

error[E0283]: type annotations needed
  --> src/main.rs:17:38
   |
16 |     let adapt = make_adapter(456);
   |                 ----------------- type must be known at this point
17 |     let f1: Box<dyn Fn(i32) -> ()> = Box::new(
   |                                      ^^^^^^^^ cannot infer type of the type parameter `T` declared on the struct `Box`
   |

You cannot use a type parameter F1 to describe the type of a specific closure that your function returns, because parameters are always notionally chosen by the caller, and your closure type is not equal to what the caller chose. The “correct” signature would be

fn make_adapter<F>(b: i32) -> impl Fn(F) -> impl Fn(i32)
where
    F: Fn(i32, i32),

but the language does not support these nested anonymous types.


In the future (or now on nightly Rust) the type_alias_impl_trait feature will allow you to solve this by giving a name to the anonymous closure type:

#![feature(type_alias_impl_trait)]

type BoundSecond<F: Fn(i32, i32)> = impl Fn(i32);

#[define_opaque(BoundSecond)]
fn make_bound_second<F: Fn(i32, i32)>(f2: F, b: i32) -> BoundSecond<F> {
    move |a: i32| {
        f2(a, b);
    }
}

fn make_adapter<F: Fn(i32, i32)>(b: i32) -> impl Fn(F) -> BoundSecond<F> {
    move |f2: F| { make_bound_second(f2, b) }
}
2 Likes

Which caller do you mean here, that chose `F1`? The caller to make_adapter or the caller to adapt (which came from make_adapter)?

The caller of make_adapter chooses F1 and F2. Or phrased differently, make_adapter must support every combination of F1 and F2 that meet the stated bounds.

1 Like

Type parameters are inputs to your function, analogous to values of regular parameters.

When the caller calls make(7), the body of your function doesn’t decide what number it gets.

Similarly, the caller can call make::<Foo>(), and you can’t do anything about it any more than you can do about receiving the 7.

1 Like

So I guess the exception to this idea of the caller picking type params is the impl Trait return? Is that a kind of hidden type parameter? But as kpreid noted, syntax like → impl Fn(F) → impl Fn(i32) does not work.

The function body chooses impl Trait in return positions. It's not a type parameter. Under the hood, it uses same mechanisms as type_alias_impl_trait.

impl Trait in argument position is a hidden type parameter.[1] Yes, that can be confusing.


  1. but more limited ↩︎

1 Like
type BoundSecond<F: Fn(i32, i32)> = impl Fn(i32);

#[define_opaque(BoundSecond)]
fn make_bound_second<F: Fn(i32, i32)>(f2: F, b: i32) -> BoundSecond<F> {
    move |a: i32| {
        f2(a, b);
    }
}

What is the F doing in type BoundSecond<F>…? Usually when I see generics, the type parameters are used in the body of whatever it is.

It means that there's a distinct output type for every distinct input type F, like how a Vec<u32> and Vec<i32> are different types.

It's sort of like this happens:

trait DefineBoundSecond<F: Fn(i32, i32)> {
    type Ty: Fn(i32);
}

struct ImplBoundSecond;

#[define_opaque(BoundSecond)]
fn make_bound_second<F: Fn(i32, i32)>(f2: F, b: i32) 
    -> <ImplBoundSecond as DefineBoundSecond<F>>::Ty
{ ... }

// Automatically generated due to `define_opaque`
impl<F: Fn(i32, i32)> DefineBoundSecond<F> for ImplBoundSecond {
    type Ty = /* ... the unnameable closure type from `make_bound_second` ... */;
}

Every returned closure has to be a different type for different F passed to make_bound_second, because the returned closure captures the f2: F, as if the closure was a struct with a field of type F. The F on the TAIT is what makes it possible for every returned closure to be a different type for different Fs.

Here's what happens when you leave out the parameter.

error: type parameter `F` is part of concrete type but not used in
parameter list for the `impl Trait` type alias

Yes, return position impl Trait is special. It’s not a generic type. It’s a real type, but the caller is not allowed to see what type it really is.

In the value analogy, it's like returning 7 in a private struct field. There’s a real specific value there, chosen by the function body, but hidden.

If the function is that simple, can you consider using a macro instead? Or is it really a must to have a function? Because having a macro could be easier than dealing with complex generics, and - depending on your code - more efficient

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.