0
votes

I'm trying to write some code that will generate a random struct with a random value. I have the following trait and helper macros for the structs:

use rand::{thread_rng, Rng};
use std::fmt;

pub trait DataType {
    /// generate a new instance of the type with a random value
    fn random() -> Box<Self>;
    /// generate a new instance of the same type with a random value
    fn gen_another(&self) -> Box<Self>;
}

macro_rules! impl_data_type_for_num {
    ($x:ident) => {
        impl DataType for $x {
            fn random() -> Box<Self> {
                Box::new(Self {
                    value: thread_rng().gen()
                })
            }

            fn gen_another(&self) -> Box<Self> {
                Self::random()
            }
        }
    };
}

macro_rules! impl_formatting {
    ($x:ident, $s:expr) => {
        impl fmt::Debug for $x {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, $s)
            }
        }

        impl fmt::Display for $x {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "{}", self.value)
            }
        }
    };
}

Then I use the macros to implement the needed traits on a bunch of structs (heres a few for example):

pub struct VirtBool {
    value: bool
}
impl_data_type_for_num!(VirtBool);
impl_formatting!(VirtBool, "bool");

pub struct VirtU8 {
    value: u8
}
impl_data_type_for_num!(VirtU8);
impl_formatting!(VirtU8, "u8");

pub struct VirtU16 {
    value: u16
}
impl_data_type_for_num!(VirtU16);
impl_formatting!(VirtU16, "u16");

So far it all works fine, but then an issue arises when I try to implement the same traits on a struct with unsized fields:

pub struct VirtArray {
    _type: Box<dyn DataType>,
    value: Vec<Box<dyn DataType>>
}
impl DataType for VirtArray {
    fn random() -> Box<Self> {
        let t = random_var();
        let s = thread_rng().gen_range(0, 10);
        Box::new(Self {
            _type: *t,
            value: (0..s).map(|_| t.gen_another()).collect()
        })
    }

    fn gen_another(&self) -> Box<Self> {
        Box::new(Self {
            _type: self._type,
            value: self.value.iter().map(|t| t.gen_another()).collect::<Vec<Box<dyn DataType>>>()
        })
    }
}
impl fmt::Debug for VirtArray {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{:?}; {}]", self._type, self.value.len())
    }
}
impl fmt::Display for VirtArray {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut s = self.value.iter().map(|v| format!("{}, ",v)).collect::<String>();
        s.truncate(s.len().checked_sub(2).unwrap_or(s.len()));
        write!(f, "[{}]", s)
    }
}

This throws the error the trait 'DataType' cannot be made into an object on several occasions. I then have a function to create a random struct but that also throws the same error:

/// generate a random type with random value
fn random_var() -> Box<dyn DataType> {
    match thread_rng().gen_range(0,4) {
        0  => Box::new(VirtBool::random()),
        1  => Box::new(VirtU8::random()),
        2  => Box::new(VirtU16::random()),
        3  => Box::new(VirtArray::random()),
        _  => panic!("invalid")
    }
}

I was originally using enums to do all of this but I'm trying to switch it over to structs and traits to help with scaling/use-ability. Does anyone have any idea how to fix the code above? I've been at a roadblock here for quite a while now.

Also, I'm aware I could use type_name_of_val to print the types, but I'm trying to keep from using unstable/nightly features.

1
I think that part of the problem may be that you are using your trait for different things: as a random value and as a random factory. Anyway with a bit of fiddling it can be made to compile (playground). Probably not the cleanest code in the world, though.rodrigo

1 Answers

0
votes

DataType cannot be made into a trait object because it uses Self and because it has a static method.

I realize it might seem like returning Box<Self> may be reasonable to call on a dyn DataType, since if you call it on dyn DataType you want a Box<dyn DataType>, but Rust doesn't try to modify methods for you to turn methods that return e.g. Box<VirtArray> into ones that return Box<dyn DataType> if they are called on a dyn DataType value. You can work around this by having the methods return Box<dyn DataType> instead.

Static methods are not allowed for trait objects because there is no implementation for the trait object type. Remember, dyn Foo implements Foo. What would (dyn DataType)::random() be? (You can use a where clause, as in the example above, to make sure dyn DataType isn't expected to have this method in a way that can be detected ahead of time; this means you can't use it on your dyn DataType objects, but it sounds like you might not want to.)