How to store FnMut in Hashmap

Hi,

I am some what new to Rust and after looking at some opensource code, my understanding of implementing callbacks in Rust is to write a wrapper struct that holds function pointers (not sure if this is the suggested way of writing generic callbacks in Rust). so I am trying to do the same in my test project where I am trying to implement message router for incoming http requests and running into following issue.

Defined HttpHandler that saves the callback function pointer/closure

struct HttpHandler<F>
where
    F: FnMut(
            Request<Body>,
        ) -> Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>>
        + Send,
{
    handler: F,
}

impl<F> HttpHandler<F>
where
    F: FnMut(
            Request<Body>,
        ) -> Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>>
        + Send,
{
    fn new(handler: F) -> Self {
        Self { handler }
    }

    async fn handle(&mut self, req: Request<Body>) -> Result<Response<Body>, Infallible> {
        (self.handler)(req).await
    }
}

Now, challenge is how to define a HashMap that store values of type HttpHandler??

I tried some thing like below but it is failing

callback_map: Arc<RwLock<HashMap<uri: String, HttpHandler<dyn FnMut(Request<Body>) -> Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>> + Send>>>>>,

Error

error[E0277]: the size for values of type `(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)` cannot be known at compilation time
   --> src/http_server_mux.rs:55:19
    |
55  | ...p: Arc<RwLock<HashMap<String, HttpHandler<dyn FnMut(Request<Body>) -> Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>> + Send>>>...
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)`
note: required by a bound in `HttpHandler`
   --> src/http_server_mux.rs:227:20
    |
227 | struct HttpHandler<F>
    |                    ^ required by this bound in `HttpHandler`
help: you could relax the implicit `Sized` bound on `F` if it were used through indirection like `&F` or `Box<F>`
   --> src/http_server_mux.rs:227:20
    |
227 | struct HttpHandler<F>
    |                    ^ this could be changed to `F: ?Sized`...
...
234 |     handler: F,
    |              - ...if indirection were used here: `Box<F>`

I am not clear why the compiler is returning size error?? I tried changing &dyn FnMut but it is throwing some different errors.

Wondering what's the write way to solve the above requirement?? Appreciate any kind of help or pointers

Thanks!!

First, let's discuss what dyn FnMut, or more generally dyn Trait, is. It is a concrete type that uses dynamic dispatch (vtables) to implement the said Trait. Where do values of this type come from? You coerce some base type which implements the trait into dyn Trait, erasing its original type in the process.

Because there can be many different base types with different sizes, dyn Trait has no single statically known size. It is dynamically sized, or in Rust trait terms, it does not implement the Sized trait / it is unsized.

There's no support for passing unsized values or having unsized locals; moreover, the vtable pointer for an erased type must be stored somewhere. So when dealing with unsized values like dyn Trait, it will be behind some sort of pointer -- such as &dyn Trait or Box<dyn Trait>. &dyn Trait is a borrowed type, and you want to own these callbacks, so you're looking for Box<dyn FnMut(...)>.


You need to use dyn FnMut because you want to store many different F: FnMut closures in one place -- every closure has its own type, for example, and you want to erase those so you can store them all as the same type.

However, what dyn Trait is not is dynamically typed. It's a concrete, static type. Rust also doesn't have trait-based subtyping. So you can't dynamically treat an HttpHandler<F1> and HttpHandler<F2> as HttpHandler<dyn FnMut(...)> or the like; there would have to be some coercion or other conversion from HttpHandler<F> to HttpHandler<dyn FnMut(...)> or HttpHandler<Box<dyn FnMut(...)>>.

More likely, you'd just start out with HttpHandler<Box<dyn FnMut(...)>> in the first place.

But if you're only going to care about HttpHandler<F> for one possible type of F (dyn FnMut or Box<dyn FnMut>), there's no reason for HttpHandler itself to be generic.


With that in mind, here's a modification of your code.

  • I created some type aliases to make things more readable
  • I removed any bounds from the struct -- you have to repeat those everywhere so you normally don't want them. Instead put bounds on impl blocks or fn declarations where you need them.
  • I made HttpHandler non-generic; it stores a Box<dyn FnMut(...)> now
  • I moved the bounds on the impl to be on fn new
  • fn new boxes up the generic F to type erase it to Box<dyn FnMut(...)>

You might have noticed I added a 'static bound to F. The reason is that Box<dyn Trait> is short for Box<dyn Trait + 'static>, which means the erased base type in question must not contain any short-lived borrows. That's almost surely a requirement for this use case anyway.

8 Likes

Thanks for your time and detailed explanation @quinedot

So, is it safe to say Rust is not by default designed for polymorphism?? This is how the requirement for polymorphism will be handled or there are also any other popular patterns community follows?? (btw, your example will totally work for my requirement but asking for other patterns only for learning purpose). I will try your suggestions over the weekend.

I think Rust handles polymorphism just fine. Box<dyn FnMut> is functionally pretty similar to std::function in C++ although that typically uses the small object optimization (and there are some other caveats, C++ probably allows those to be null).

1 Like

dyn Trait is polymorphism.

The two main ways are generics and type erasure (dyn Trait). There are exceptions but in broad terms, one uses generics unless they need the type erasure. For example sort_by has no need to type erase the closure you supply, so it takes any closure that meets the bounds and not a Box<FnMut(...) + '_>.

In contrast, "I'm storing a dynamic number of some library-user provided closures" is a case where you need to erase the types so you can treat them the same. An alternative approach could be to ditch callbacks and make a trait that library users can implement which covers all the functionality, and to be generic over T: YourTrait.

So there's still many ways to do things, but perhaps not every way you're used to from whatever other languages you know due to the lack of object subtyping or whatever.

3 Likes

Agreed, don't think I can expect the same things I do with other programming languages can be done in Rust and vice versa.

(Posting below qns here because of existing pre-context in the thread, Please let me know if I need to open a new thread)

I tried the changes suggested above by @quinedot and had to make a bit of tweak for my requirements. The callback Hashmap that stores the HttpHandlers needs to be global variable and hence I am using lazy_static to initialize it. This is forcing the Request Handler to be warped inside Mutex to satisfy Sync like below

type RequestFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>>;
type RequestHandler = Arc<Mutex<Box<dyn FnMut(Request<Body>) -> RequestFuture + Send>>>;

#[derive(Clone)]
struct HttpHandler {
    handler: RequestHandler,
}

Now, my handle function which awaits internally is holding the lock across the awaiting point, I tried to work around it as suggested here - (Alan thinks he needs async locks - wg-async)

Challenge is how do I clone the object with FnMut trait from the mutex lock like below??

    async fn handle(&mut self, req: Request<Body>) -> Result<Response<Body>, Infallible> {
        // get lock on the handler
        let handler_clone = {
            let handler_lock = self.handler.lock().unwrap();
            handler_lock.as_ref().clone()
        };
        (handler_clone)(req).await
    }

Seeing following compilation errors

error[E0597]: `handler_lock` does not live long enough
   --> src/http_server_mux.rs:251:13
    |
251 |             handler_lock.as_ref().clone()
    |             ^^^^^^^^^^^^^^^^^^^^^--------
    |             |
    |             borrowed value does not live long enough
    |             borrow later used here
252 |         };
    |         - `handler_lock` dropped here while still borrowed

2nd error -- This I understand, handler_clone should have been as_mut instead as_ref but for cloning purpose I used as_ref

error[E0596]: cannot borrow `*handler_clone` as mutable, as it is behind a `&` reference
   --> src/http_server_mux.rs:253:9
    |
249 |         let handler_clone = {
    |             ------------- consider changing this binding's type to be: `&mut dyn FnMut(Request<Body>) -> Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send>> + Send`
...
253 |         (handler_clone)(req).await
    |         ^^^^^^^^^^^^^^^ `handler_clone` is a `&` reference, so the data it refers to cannot be borrowed as mutable

The most straight-forward way from where you're at is probably to use

-Arc<Mutex<Box<dyn FnMut(Request<Body>) -> RequestFuture + Send>>>;
+Arc<          dyn    Fn(Request<Body>) -> RequestFuture + Send  >;

After which you might also be able to do

    // n.b. doesn't capture lifetime of `&self`
    fn handle(&self, req: Request<Body>) 
        -> impl Future<Output = Result<Response<Body>, Infallible>> 
    {
        let handler_clone = self.handler.clone();
        async move {
            (handler_clone)(req).await
        }
    }

As for an explanation, you can't hold on to any references behind the mutex lock longer than you hold on to the mutex lock itself (that's the point of it). So you need to clone the entire closure if you need to drop that lock (not just clone a reference to it).

[1]

It's a pain to wire things up so you can actually clone type-erased objects, so the "quick" way is to go with wrapping it in an Arc, at which point you can't utilize FnMut and you don't need the Mutex.


But if you do need FnMut and the ability to actually clone, here's a sketch of how. With something cloneable, and if you never intend to call these in place, you could perhaps drop the Arc and Mutex.

But honestly I don't think it's worth it in this case. Because you're cloning, every instance starts from the same state, where as usually you allow FnMut for the ability to change state. If you need to clone these closures, whether directly or by cloning an Arc<Fn(...)>, the only way for the closures to carry state across the clones is if they contain shared ownership and synchronization themselves, in which case the Fn restriction isn't such a big deal.


  1. More generally, Rust references aren't automatically extended to be alive as long as they are used -- there is no garbage collector -- so when the thing that you took a reference to or got a reference out of goes away, your reference is dead too. â†Šī¸Ž

1 Like

Removing the Mutex is throwing following error

error[E0277]: `(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)` cannot be shared between threads safely
   --> src/http_server_mux.rs:28:1
    |
28  | / lazy_static! {
29  | |     static ref MSGROUTER: MsgRouter = MsgRouter::new();
30  | |     static ref SERVER_HANDLE: Arc<Mutex<ServerHandler>> =
31  | |         Arc::new(Mutex::new(ServerHandler::new()));
32  | | }
    | |_^ `(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)` cannot be shared between threads safely
    |
    = help: the trait `Sync` is not implemented for `(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)`
    = note: required for `Arc<(dyn FnMut(Request<Body>) -> Pin<Box<(dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static)>> + Send + 'static)>` to implement `Send`
note: required because it appears within the type `HttpHandler`
   --> src/http_server_mux.rs:237:8
    |
237 | struct HttpHandler {
    |        ^^^^^^^^^^^
    = note: required because it appears within the type `(String, HttpHandler)`
    = note: required for `hashbrown::raw::RawTable<(String, HttpHandler)>` to implement `Send`
    = note: required because it appears within the type `hashbrown::map::HashMap<String, HttpHandler, RandomState>`
    = note: required because it appears within the type `HashMap<String, HttpHandler>`
    = note: required for `std::sync::RwLock<HashMap<String, HttpHandler>>` to implement `Sync`
    = note: 1 redundant requirement hidden
    = note: required for `Arc<std::sync::RwLock<HashMap<String, HttpHandler>>>` to implement `Sync`
note: required because it appears within the type `MsgRouter`
   --> src/http_server_mux.rs:53:8
    |
53  | struct MsgRouter {
    |        ^^^^^^^^^
note: required by a bound in `Lazy`
   --> /home/sarathsa/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
    |
19  | pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
    |                    ^^^^ required by this bound in `Lazy`
    = note: this error originates in the macro `__lazy_static_create` which comes from the expansion of the macro `lazy_static` (in Nightly builds, run with -Z macro-backtrace for more info)

That's exactly what the previous answer was pointing out. You need Fn, not FnMut.

My bad, I was too excited and didn't read the comment carefully. I will try with Fn, but thinking this might cause limitations for the end user if the mutations are refrained on the callback or maybe if I am overthinking.

I wrote more about it above, but if you're going to clone the closures, the end user probably needs their own Fn-amenable synchronization to do sensible mutations anyway.

If you want to give the end user FnMut ability without cloning, you'll have to hold on to the mutex lock. It's what grants the ability to use &mut in-place, where others can still "see" it.

1 Like
previous post details here

You might have noticed I added a 'static bound to F . The reason is that Box<dyn Trait> is short for Box<dyn Trait + 'static> , which means the erased base type in question must not contain any short-lived borrows. That's almost surely a requirement for this use case anyway. - How to store FnMut in Hashmap - #2 by quinedot

Hi @quinedot - Here you suggested adding the "static" life time for the Fn closure which makes sense. And everything works fine as long as the references passed to the closure are static.

But, what if I want to pass dynamic variables to the static closure or handler?? Do we have a workaround for this?? For example if I want to pass a channel sender to the closure to update the request data to some other thread - How can I do it??

I have added my requirement as sudo code in this link that you shared the example earlier - Rust Playground

Appreciate your help.

There's so many ambiguous functions and types in your example I don't have any way of writing a good response.

E.g. channel, Sender, Receiver, httpserver...

@quinedot Thanks for taking a look at the example.

What I want - How to pass a non static reference variable to the static closure trait (HttpHandler in this case)?? Is it possible ??

You can't, you'd have to make HttpHandler able to accept lifetime-limited closures, maybe something like this.


But I can't tell if that's actually going to be compatible with your code or not because I don't know what channel we're talking about (a dozen candidates available in the playground alone), I don't know what Sender we're talking about (17 candidates), I don't know what Receiver we're talking about (23 candidates)...

The non-'static version may or may not be compatible with your frameworks.

Thanks!!

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.