How to use boxed closure or trait object

#1

I’m trying to write a web framework to learn rust, I wrote a simple example at https://github.com/xcaptain/voyager.rs

but this example doesn’t run.

joey@voyager-pc ~/P/voyager> cargo run --example hello
   Compiling voyager v0.1.0 (/home/joey/Projects/voyager)
error[E0308]: mismatched types
  --> examples/hello.rs:20:49
   |
20 |     m.handle("/world".to_string(), Handler::new(world_handler));
   |                                                 ^^^^^^^^^^^^^ expected closure, found a different closure
   |
   = note: expected type `[closure@examples/hello.rs:10:25: 13:6]`
              found type `[closure@examples/hello.rs:14:25: 17:6]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: Could not compile `voyager`.

To learn more, run the command again with --verbose.

Since my Handler struct is a generic struct, why these 2 lines doesn’t compile

m.handle("/hello".to_string(), Handler::new(hello_handler));
m.handle("/world".to_string(), Handler::new(world_handler));
#2

This is because, as the error explains, the closures are not ambiguous in the compiler’s eyes. Meaning that they are different functions/closures defined at different places, and therefore require using some kind of interface to interact with it so that it can be changes. The first closure has a type, technically named [closure@examples/hello.rs:10:25: 13:6] but we’ll call it A and the second one technically has a name, [closure@examples/hello.rs:14:25: 17:6] but we’ll call it B. The Mux<T> object requires that its T impls Fn(&ResponseWriter, &Request) + Clone. This is true for both A and B. But this doesn’t mean you can put both A and B into Mux<T>. I.E. if I had this case:

trait Foo {}
struct A;
impl Foo for A {}
struct B;
impl Foo for B {}

struct Cont<T: Foo> {_a: std::marker::PhantomData<T>}

In this case, we would be able initialize Cont with any generic type that implements Foo, but not put any other type in there. This is the problem, so I’d solve it by changing your handler to look like this:

pub struct Handler
{
    f: Box<dyn Fn(&ResponseWriter, &Request) + Clone>,
}

This uses dynamic dispatch, meaning that it’s the type equivalent of HRTBs (Or in other words, it allows _any type that impls Fn(&ResponseWriter, &Request) + Clone in there)

#3

Thanks for the reply, I think your answer the boxed closure way. I changed my code here https://github.com/xcaptain/voyager.rs/commit/8715f715d878a16026f78ba156fd7bf45e2af394, now this example runs and it’s much simple than the old version, no need to write trait bound everywhere.

This line doesn’t work, because Fn(&ResponseWriter, &Request) is a closure but Clone is a trait

pub struct Handler
{
    f: Box<dyn Fn(&ResponseWriter, &Request) + Clone>,
}

I changed to

pub struct Handler {
    f: Box<Fn(&ResponseWriter, &Request)>,
}

It’s boxed closure, so no need to write dyn

I’m planning to integrate tokio to handle http connection, and then construct real request and response struct.

#4

Actually dyn is good practice, as a box is just a heap allocated pointer.

#5

It’s a trait for the closure(s), it will be using dynamic dispatch. Closures are unnamed types so if your specifying one type then you have to use generics / impl Trait.
Compile with #![warn(bare_trait_objects)] to see where code should use dyn. (No idea why warning hasn’t been turned on in 2018 edition.)

1 Like
#6

Thank you for the help, in https://github.com/xcaptain/voyager.rs/commit/ce15b256ad49e9e853a80064c9bf788d02043866 this commit, I added tokio, now this example can handle http connections

I spend a lot of time to figure out the lifetime of the Mux struct but failed, so I’m using clone everywhere,

process(socket, m.clone());
let mm = m.clone();

To make my Handler struct cloneable, I changed

Box<Fn(&mut Builder, &Request<()>) -> Response<String> + Sync + Send>

to

Arc<Fn(&mut Builder, &Request<()>) -> Response<String> + Sync + Send>