How to store callables in a hash map?

So, I want to store functions which build my objects (like empty constructors) in a hash map like this:

use std::collections::HashMap;

pub trait Component {
    fn id(&self) -> &'static str;
}

#[derive(Debug, Copy, Clone)]
pub struct Position(pub u64);
impl Position {
    pub const ID: &'static str = "position";
}
impl Default for Position {
    fn default() -> Position {
        Position(0)
    }
}
impl Component for Position {
    fn id(&self) -> &'static str { Self::ID }
}

lazy_static::lazy_static! {
    static ref COMPONENTS: HashMap<&'static str, &'static dyn FnOnce() -> Box<dyn Component>> = {
        let mut m = HashMap::new();
        m.insert(Position::ID, &|| Position::default());
        m
    };
}


fn main() {
}

I have tried all the combinations I knew, nothing works: Fn, FnOnce, fn, references, static references, anything. In my opinion, lambda without environment should be able in certain (like this) conditions treated the same way as an ordinary function pointer, but it's not. :frowning:

So, how can I store functions which can create my object? In C++ this would be sort of a prototype pattern. Here I would like to know how to store callables like: trait functions without parameters, lambdas without environment, static methods of some structure, any way to store a callable without creating a separate function like:

function create_position_component() -> Box<dyn Component> {
    Position::default()
}

because it is pointless.

It looks like Rust just can't infer the right types, working version. Also, it looks like you forgot to box the return of the closure. Then you will need to explicitly coerce the Box<Position> to a Box<dyn Component>.

If you really want to use FnOnce() -> Box<dyn Component>, then you will need to change fn() -> Box<dyn Component> to Box<dyn FnOnce() -> Box<dyn Component> + Send + Sync> in order to put it into a static global.

Closures are just structs with some syntactic sugar to allow the function call syntax. So they can't be just function pointers (edit: see @H2CO3's comment below, if a closure doesn't capture anything from it's enviornment, then it can be trivially cast to a fn pointer). Also, when you use FnOnce you can't call it if you only have a shared reference (&T) to it.


You can read about how closures are desugared in my blog post here,

That's not correct in this case, and OP's expectations are justified here. A closure that doesn't actually capture any environment can (and does) coerce into a function pointer.

2 Likes

Yes, I know, I use that fact in my playground example, I was just making a general statement about the vast majority of closures.

@vityafx, it seems to me, however, that putting FnOnce functions in an immutable HashMap doesn't make a lot of sense: if you can't mutate the map, you can't get the values out of it by value, hence (as KrishnaSannasi mentioned above) you can't call them.

(A further stylistic comment is that if you have a 0-argument function and you want a 0-argument function, you don't need to create a closure around it at all. You could just provide the original function in the first place.)

Here's an updated playground.

3 Likes

FnOnce and other traits I tried to use only because I could not make fn() working. It works if we specify the hashmap type fully as you did and if we do this Box::<Position>::default() as _ magic. Thanks. However, such behaviour is annoying and confusing.

By the way, could you explain this magic to me as well? What does Box::<Position>::default() as _ do what Box::new(Position::default()) does not and why? Thanks!

The as _ cast triggers the coercion from Box<ConcreteType> to Box<dyn Trait>. Apparently, function return value position is not a coercion site.

To be honest, I long gave up remembering in exactly what positions and contexts coercions are automatic (the only thing I readily know is deref because that's used on like every second line), so I usually just try to act similarly and insert an as _ or an explicit type annotation when the compiler complains about a coercion to see if it helps.

2 Likes

In this case the closure's return type is inferred, so an implicit coercion won't happen. But if you put in the as cast, then you are explicitly asking for the coercion.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.