Customized trait object in Rust
I love the traits in Rust language. Rust uses one mechanism for both compile-time polymorphism and runtime polymorphism, which is elegant. However, the runtime polymorphism in Rust is not very flexible. Some traits cannot be used as a trait object, and it's impossible to specify whether a method is resolved dynamically or statically. This shortage is reflected in my recursive function library RecurFn, and I develop a workaround, calling it customized trait object.
For example, assume we have a trait, Foo
.
1 | trait Foo { |
In which method foo
represents a polymorphic action, while method foo_twice
represents a static action and should not be overridden.
This trait cannot be used as a trait object, because both methods have a impl Fn() -> ()
parameter. But we can turn it into a trait object in the following way: we can replace impl Fn() -> ()
by &dyn Fn() -> ()
, and we can statically resolve the method foo_twice
.
So we can create a dynamic version of the trait Foo
.
1 | trait DynFoo { |
The trait object dyn DynFoo
is the customized trait object. We hope that dyn DynFoo
can be treated as the trait object of Foo
. We need to implement:
- For any pointer to the implementation of
Foo
, it can be converted to the pointer todyn DynFoo
. dyn DynFoo
implementsFoo
.
For 1, it can be implemented by implements DynFoo
for all implementations of Foo
.
1 | impl<F: Foo> DynFoo for F { |
For 2, it can be implemented by implements Foo
for dyn Foo
.
1 | impl Foo for dyn DynFoo { |
Now the following code compiles.
1 | let foo: &dyn DynFoo = &FooImpl::new(); // FooImpl::new() returns an implementation of Foo. |
The problem seems to be solved. However, Rust has a weird rule: in impl Foo for dyn DynFoo
, DynFoo
has 'static
lifetime, and DynFoo
cannot has Send
or Sync
flag. The following code:
1 | fn test_complex<'a>() { |
cannot compile.
To solve this problem, we need to make four copies of the code, and change the impl
part of each copy into impl<'a> Foo for DynFoo + 'a
, impl<'a> Foo for DynFoo + 'a + Send
, impl<'a> Foo for DynFoo + 'a + Sync
, impl<'a> Foo for DynFoo + 'a + Send + Sync
.
To reduce this duplication, we can create a macro to do it.
1 | macro_rules! impl_dyn_with_markers { |
You can find the example code here, this strategy is used in RecurFn.