Impl Trait into a closure

I'm creating a public API for library users to pass into their own function to query database structures. If possible, I'd like them to only use the public APIs via impl Trait.

I created a simple example of passing an impl Trait to a closure and that worked fine. When I tried to use it in my API layout I get a compilation error, but I don't understand what the error is trying to tell me.

trait Api {
  fn test(&self);

  fn closure<A: Api, F: FnMut(A)>(&self, f: F);
}

trait IAccess {
  fn get(&self);
}

trait IApi : IAccess {
  fn api_test(&self) {
    self.get();
  }

  fn api_closure<A: Api, F: FnMut(A)>(&self, mut f: F) {
    let a = self.impl_api();
    f(a)
  }

  fn impl_api(&self) -> impl Api;
}

#[derive(Copy, Clone)]
struct IApiImpl {}

impl IAccess for IApiImpl {
  fn get(&self) {
    println!("Get called");
  }
}

impl IApi for IApiImpl {
  fn impl_api(&self) -> impl Api {
      ApiImpl{ iapi: *self }
  }
}

#[derive(Copy, Clone)]
struct ApiImpl {
  iapi: IApiImpl
}

impl Api for ApiImpl {
  fn test(&self) {
    self.iapi.api_test();
  }

  fn closure<A: Api, F: FnMut(A)>(&self, f: F) {
    self.iapi.api_closure(f)
  }
}

error[E0308]: mismatched types
  --> src/main.rs:18:7
   |
16 |   fn api_closure<A: Api, F: FnMut(A)>(&self, mut f: F) {
   |                  - expected this type parameter
17 |     let a = self.impl_api();
18 |     f(a)
   |     - ^ expected type parameter `A`, found associated type
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected type parameter `A`
             found associated type `impl Api`
note: callable defined here
  --> src/main.rs:16:29
   |
16 |   fn api_closure<A: Api, F: FnMut(A)>(&self, mut f: F) {
   |                             ^^^^^^^^

If someone can point me in the right direction I'd greatly appreciate it!

The type of a return-position impl Trait (RPIT) such as this:

fn impl_api(&self) -> impl Api;

is an opaque, but concrete, type (perhaps parameterized by input lifetime/types). In this particular case it's akin to something like this:

trait IApi: IAccess {
    // You could drop the lifetime generic if you never need it
    type OutputApi<'a>: Api where Self: 'a;
    fn impl_api(&self) -> Self::OutputApi<'_>;
}

But then here:

    fn api_closure<A: Api, F: FnMut(A)>(&self, mut f: F) {
        let a = self.impl_api(); // a: Self::OutputApi<'_>
        f(a)
    }

The caller chooses the types of A and F (such that they meet the bounds), and the method body has to work with any A and F that meets the bounds. So you have a F: FnMut(A) for some type A that is not chosen by you, but you're trying to pass it a Self::OutputApi<'_>.

In the concrete implementation, that would be trying to pass an ApiImpl specifically to F. But A might not be an ApiImpl.

I'd have to wrap my head around your goals a bit more to offer concrete advice on fixing it.

Well ok, there's one "fix"[1] that doesn't require wrapping my head around any particular design, and that's type erasing things into &mut FnMut(_) and &dyn Api until enough traits are object save (can be coerced to dyn Trait) and the default bodies can be made to work.

But I still can't say if I feel this is a good idea or not.


  1. that changes things semantically ↩︎

I am using impl trait to simplify public facing API access due to the myriad of type configurations a structure might have. It is easier than having to create additional wrappers to handle mutable and immutable API access to every struct to read-only and read/write transactions

The transaction API has a function to iterate over every bucket. A bucket is a dictionary that can hold kv pairs and other buckets. The goal is for the API user to pass in a closure and iterate over all of the buckets.

Just for my understanding then, why wouldn't Self::OutputApi<'_> implement Api as far as F is concerned?

The problem isn't that Self::OutputApi<'_> doesn't implement Api (as per the bounds on OutputApi<'_>, it does). The problem is that A doesn't have to be the same type as Self::OutputApi<'_>. Callers can call api_closure<TheirChoice, _>(...).

So as per the bounds, F only has to take one specific type as an argument: A. I can have a function that takes a String but doesn't take any other T: Display. Callers can pass in a closure that takes an A but doesn't take an ApiImpl.


Furthermore, Rust doesn't support bounds like this so far:

// "F can take any A that implements Api"
F: for<A: Api> FnMut(A)

And in fact due to how functions are monomorphized,[1] no such type exists.[2] (Example here.)

The closest thing would in fact be the type-erased-taking

F: FnMut(&dyn Api)

or similar.[3] But for this to work, Api has to be trait object safe.

In my second comment I mechanically[4] applied the type-erasure approach.


  1. and how closures work ↩︎

  2. on stable; you can implement one on unstable ↩︎

  3. &mut dyn Api or Box<dyn Api + '_> or something else that lets you call all the Api methods ↩︎

  4. i.e. without considering the use case or thinking much at all ↩︎

Unfortunately I didn't write my example correctly. Api has a function with RPIT which prevents it from being object safe.

Is the only option left to create an enum to wrap all of the possible outputs and return that?

Can you make Api object safe by replacing the RPIT with a trait object?

I can, but I was trying to avoid adding another pointer indirection. That being said, I did some tests and an enum implementation will remove a lot of unnecessary code complexity.

Based on your current code behavior where the implementation rather than the caller chooses the argument type in closure and the requirement of impl Api as the public interface, this compiles.

trait IApi: IAccess {
    ...
    type A: Api;
    fn impl_api(&self) -> Self::A;
}
impl IApi for IApiImpl {
    type A = impl Api; // #![feature(impl_trait_in_assoc_type)]
    fn impl_api(&self) -> Self::A { ... }
}

trait Api {
    ...
    type Arg;
    fn closure<F: FnMut(Self::Arg)>(&self, f: F);
}
impl Api for ApiImpl {
    ...
    type Arg = <IApiImpl as IApi>::A;
    fn closure<F: FnMut(Self::Arg)>(&self, f: F) {
        self.iapi.api_closure(f)
    }
}
1 Like