Idiomatic Replacement for `Box<dyn Trait>` with Generics

Greetings,

I've seen a number of posts on StackOverflow (and perhaps here) dedicated to the trait cannot be made into an object... error for traits using generics.

Simple example:

use num::{Complex, Zero, One};

trait HasNumber {
    fn favorite<T: Zero + One> (&self) -> T;
}

struct Has1 {}
impl HasNumber for Has1 {
    fn favorite<T: Zero + One> (&self) -> T { T::zero() }
}

struct Has2 {}
impl HasNumber for Has2 {
    fn favorite<T: Zero + One> (&self) -> T { T::one() }
}

fn main(){
    let h1 = Has1{};
    h1.favorite::<f64>();
    h1.favorite::<Complex<f64>>();
    
    let h2 = Has2{};
    h2.favorite::<f64>();
    h2.favorite::<Complex<f64>>();
    
    let v: Vec<Box <dyn HasNumber>> = vec![  // <= problem's here 
        Box::new(Has1{}),
        Box::new(Has2{}),
    ];
}

Errors:

   |
4  | trait HasNumber {
   |       --------- this trait cannot be made into an object...
5  |     fn favorite<T: Zero + One> (&self) -> T;

Playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=292710263badd277e827b849f49fb3c0

My question: what is the idiomatic Rust version of this combination (generics + "dynamic" dispatch)?

Of course in my non-simplified-reality there are far more than two things like Has1 and Has2. And they have aways more elaborate behavior (hence the desired for Box<dyn>).

Thanks!

The root problem is that a dyn Trait has to know all of the methods it provides ahead of time in order to create the functions that can be called dynamically via virtual dispatch.

If you have generic parameters, then there are an infinite number of methods that need to be provided (and a set that can't possibly be known ahead of time; downstream crates can create new types and monomorphized for them).

The solution is to degenericize the methods with further virtualization. You can keep the nicer monomorphized API for known types by gating them with where Self: Sized (but this won't work for dyn of course).

For inputs, this is rather simple. Just take input as &dyn Trait or Box<dyn Trait> or whatever container as needed.

For outputs, you need to use out parameters. And if you need to use associated functions (like T::one(), rather than a method like x.one()), it becomes even more convoluted.

Thankfully, you can hide some of the complexity via inherent impls on the dyn Trait type itself, which are allowed to be generic and monomorphic.

So a final solution (untested) might look something like

trait HasNumber {
    fn dyn_favorite(&self, out: &mut dyn HasNumerFavoriteOut);
}

trait HasNumberFavoriteOut {
    fn one(&mut self);
    fn zero(&mut self);
}

impl<T: Zero + One> HasNumberFavoriteOut for Option<T> {
    fn one(&mut self) { *self = Some(Self::one()) }
    fn zero(&mut self) { *self = Some(Self::zero()) }
}

impl dyn HasNumber {
    fn favorite<T: Zero + One>(&self) -> T {
        let mut place: Option<T> = None;
        self.dyn_favorite(&mut place);
        place.unwrap()
    }
}

And here's another example that does `impl Trait for &dyn Trait, which "works" as well to use the same name for generic and dynamic dispatch.

1 Like

Congratz on nerd sniping me for a bit.

Here's a version using two traits, one for dynamic dispatch, and one for wrapping it in a nice generic interface.

use num::{Zero, One};
use rand::prelude::*;

trait CoinFlip {
    fn flip<T: Zero + One>(&self) -> T;
}

trait DynCoinFlip {
    fn dyn_flip(&self, place: &mut dyn DynCoinFlipPlace);
}

trait DynCoinFlipPlace {
    fn zero(&mut self);
    fn one(&mut self);
}

impl<T: Zero + One> DynCoinFlipPlace for Option<T> {
    fn zero(&mut self) { *self = Some(T::zero()) }
    fn one(&mut self) { *self = Some(T::one()) }
}

impl<S: DynCoinFlip + ?Sized> CoinFlip for S {
    fn flip<T: Zero + One>(&self) -> T {
        let mut place: Option<T> = None;
        self.dyn_flip(&mut place);
        place.unwrap()
    }
}

struct Fair;
impl DynCoinFlip for Fair {
    fn dyn_flip(&self, place: &mut dyn DynCoinFlipPlace) {
        if random() {
            place.zero()
        } else {
            place.one()
        }
    }
}

struct Xkcd;
impl DynCoinFlip for Xkcd {
    fn dyn_flip(&self, place: &mut dyn DynCoinFlipPlace) {
        // Chosen by fair coin flip
        place.one()
    }
}

fn make_strategy() -> &'static (impl CoinFlip + ?Sized) {
    if random() {
        static FAIR: Fair = Fair;
        (&FAIR) as &dyn DynCoinFlip
    } else {
        static XKCD: Xkcd = Xkcd;
        &XKCD as &dyn DynCoinFlip
    }
}

fn main() {
    let strategy = make_strategy();
    dbg!(strategy.flip::<f32>());
    dbg!(strategy.flip::<u32>());
    dbg!(Fair.flip::<f32>());
    dbg!(Xkcd.flip::<u32>());
}
2 Likes

enum-dispatch might be worth a look. It makes it much easier to define and work with enums that contain variants for structs that implement some trait. Assuming that works for you, you can then just have a Vec of that enum instead of using trait objects.

Thanks very much!
I'm going to give enum-dispatch a look.

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.