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

2

3

4

5

6

7

trait Foo {

fn foo (& self , f: impl Fn () -> ());

fn foo_twice (& self , f: impl Fn () -> () + Copy ) {

self .foo(f);

self .foo(f)

}

}



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

2

3

4

trait DynFoo {

fn dyn_foo (& self , f: & Fn () -> ());



}



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 to dyn DynFoo . dyn DynFoo implements Foo .

For 1, it can be implemented by implements DynFoo for all implementations of Foo .

1

2

3

4

5

impl <F: Foo> DynFoo for F {

fn dyn_foo (& self , f: & dyn Fn () -> ()) {

self .foo(f)

}

}



For 2, it can be implemented by implements Foo for dyn Foo .

1

2

3

4

5

impl Foo for dyn DynFoo {

fn foo (& self , f: impl Fn () -> ()) {

self .dyn_foo(&f)

}

}



Now the following code compiles.

1

2

3

let foo: & dyn DynFoo = &FooImpl::new();

foo.dyn_foo(&|| {});

foo.foo_twice(&|| {});



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

2

3

4

fn test_complex < 'a >() {

let a: Box <DynFoo + Send + 'a > = Box ::new(FooImpl::new());

a.foo_twice(&|| {});

}



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

2

3

4

5

6

7

8

9

10

11

12

13

macro_rules! impl_dyn_with_markers {

($($marker:ident),*) => {

impl < 'a > Foo for dyn DynFoo + 'a $( + $marker)* {

fn foo (& self , f: impl Fn () -> ()) {

self .dyn_foo(&f)

}

}

};

}

impl_dyn_with_markers! {}

impl_dyn_with_markers! { Send }

impl_dyn_with_markers! { Sync }

impl_dyn_with_markers! { Send , Sync }



You can find the example code here, this strategy is used in RecurFn.