In relation to this question, Storing a closure in a HashMap, I learned that properly passing closures to a function requires the function to be generic and take any type that implements the Fn, FnMut, or FnOnce trait.
In implementing part of a library from C++ as a learning exercise, I require some type abstraction kind of like this.
use std::collections::HashMap;
struct Event;
trait IObject {
fn makeFunc<F : FnMut(&Event)>(&mut self, s : &str, f : F);
}
struct Object1<'a> {
m_funcs : HashMap<String, Box<FnMut(&Event) + 'a>>
}
impl <'a> Object1<'a> {
fn new() -> Object1<'a> {
Object1 {m_funcs : HashMap::new()}
}
}
impl <'a> IObject for Object1<'a> {
fn makeFunc<F : FnMut(&Event) + 'a>(&mut self, s: &str, f: F) {
self.m_funcs.insert(String::from_str(s), Box::new(f));
}
}
fn main() {
let obj : &IObject = &Object1::new();
println!("Hello, world!");
}
However, the error returned says that IObject cannot be a trait object because it contains a method with generic parameters. However, to pass a closure to a function at all, I require generics. Can someone show me how to achieve the abstraction I'm looking for while still being able to pass closures to functions?
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
To implement a trait, declare an impl block for the type you want to implement the trait for. The syntax is impl <trait> for <type> . You'll need to implement all the methods that don't have default implementations.
dyn is a prefix of a trait object's type. The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be 'object safe'. Unlike generic parameters or impl Trait , the compiler does not know the concrete type that is being passed.
In Rust, polymorphic functions are fully type-checked when they are declared, not when they are used. This means you can never call a Rust function and get a type error within the function because you gave it the wrong type.
You cannot get around this; static and dynamic dispatch do not mix. The monomorphisation that static dispatch (generics) does simply cannot work with the vtable used in dynamic dispatch (trait objects).
One of the two will have to go: either the usage of IObject
as a trait object, or the generic function argument, in favour of accepting a Box<FnMut(&Event) + 'a>
.
By the way, note how your IObject
implementation is not matching the trait—the trait gives no lifetime bound on F
, where your implementation does. You’d need to add 'a
as a generic on the trait definition anyway (generic lifetimes are OK with trait objects).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With