Trait object vs. trait bounds for a closure

I've been using trait bounds like the following that works:

   pub async fn serve<F, S>(&self, handler: F) -> Result<(), Error>
   where
       F: Fn(Request<MyBody>) -> S + Send + Clone + 'static,
       S: Future<Output = Response<MyBody>>,

and I'm able to call handler later like this:

let response = handler(req).await;

Now, I am trying to refactor and store handler in the struct. I'm using Arc with trait object (as trait object cannot be cloned).

pub struct Foo {
<snip>
     handler: Arc<dyn (Fn(Request<MyBody>) -> dyn Future<Output = Response<MyBody>>) + Send + Sync + 'static>,
}

now, the compile fails and complains that Future is unsized:

521 |                         let response = handler(req).await;
    |                                        ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time

Is this a known limitation, or did I miss something when using Arc<dyn Fn() -> Trait > ?

Thanks.

I think the problem is the dyn Future return type of the closure which would be an maybe sized return type which isn’t allowed. Maybe you want impl Future for the return type. Can’t remember if that’s allowed off hand for closure returns though.

I knew that wouldn’t work as I was sending it. This seems to work though. Obviously it’s a stripped down example so it might not work when you add in all your code.

playground link

Alternatively without generics you can return a boxed future.

pub struct Foo {
     handler: Arc<dyn Fn(Request<MyBody>) -> Pin<Box<dyn Future<Output = Response<MyBody>> + Send>> + Send + Sync>,
}

or using the BoxFuture alias

use futures::future::BoxFuture;

pub struct Foo {
     handler: Arc<dyn Fn(Request<MyBody>) -> BoxFuture<Response<MyBody>>> + Send + Sync>,
}

It's impossible to return a bare trait object from a function.

1 Like

Thanks. This is why you use the alias.

1 Like

Thanks for all replies. Appreciated.

It seems BoxFuture requires a lifetime annotation. I tried 'static but got errors like this:

type ServiceFn = Arc<dyn (Fn(Request<MyBody>) -> BoxFuture<'static, Response<MyBody>>) + Send + Sync + 'static>;

<snip>

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds

async fn event_handler(
    <snip>
    handler: ServiceFn,  // type alias for the whole `Arc<...>` thing
)
{ 
^
note: hidden type `impl core::future::future::Future` captures the scope of call-site for function at 497:1

Also, the input handler of the above fn event_handler will eventually be cloned and then passed to a new async task.

Do I need to use 'a for the lifetime of BoxFuture and propagate to Foo<'a> ? I'm trying to limit the complexity if possible.

You should use 'static and also add 'static as the bound on your impl Trait parameter.

You don't want your service function to store references to stack data.

1 Like

Just to follow up, I ended up using generics with trait bounds without Box or dyn or Arc. It turns out easier than I expected, as it is very much same with the current trait bounds with function parameters. The code looks like this:

pub struct Foo<F, S>
where
    F: Fn(Request<MyBody>) -> S + Send + Clone + 'static,
    S: Future<Output = Response<MyBody>>,
{
 <snip>
    handler: F,
}

impl<F, S> Foo<F, S>
where
    F: Fn(Request<MyBody>) -> S + Send + Clone + 'static,
    S: Future<Output = Response<MyBody>>,
{
    pub fn new(other_params: u16, handler: F) -> Self {
        <snip>
         Foo {
              other_params,
              handler,
         }
    }
}

What I didn't realized was that I don't need to specify the concrete type of <F> and <S> when calling Foo::new() to create the struct:

let bar = Foo::new(
               other_params, 
               move |req| my_handler(other_state, req));

Thanks again for helping me understand more about trait object and trait bounds.

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.