Better way of doing type conversion?

Hi all,

I have a kinda complex code structure here:

First, I have a struct:

pub struct Representation<W, S> {
    metadata: Option<Mdata>,
    data: D,
    words: W,
}

where:

    W: Words, // ```Words``` is a trait
    D: Data, // ```Data``` is a trait

And I have 2 struct and 1 enum that implement Words; 2 struct and 1 enum that implement Data:

pub struct WordType1 {
...
}

impl Words for WordType1 {
...
}
pub struct WordType2 {
...
}

impl Words for WordType2 {
...
}

WordWrap is a generalization of WordType1 and WordType2:

pub enum WordWrap {
    WordType1(WordType1),
    WordType2 (WordType2 ),
}

impl Words for WordWrap {
...
}

// ```WordType1``` and ```WordType2``` can both be converted into ```WordWrap```
impl From<WordType1> for WordWrap {
    fn from(v: WordType1) -> Self {
        WordWrap::WordType1(v)
    }
}
impl From<WordType2> for WordWrap {
    fn from(v: WordType2) -> Self {
        WordWrap::WordType2(v)
    }
}

Same structure holds for Data:

pub struct DataType1 {
...
}

impl Data for DataType1 {
...
}
pub struct DataType2 {
...
}

impl Data for DataType2 {
...
}

DataWrap is a generalization of DataType1 and DataType2 :

pub enum DataWrap {
    DataType1 (DataType1 ),
    DataType2 (DataType2 ),
}

impl Data for DataWrap{
...
}

// ```DataType1``` and ```DataType2``` can both be converted into ```DataWrap```
impl From<DataType1> for DataWrap{
    fn from(v: DataType1) -> Self {
        DataWrap::DataType1(v)
    }
}
impl From<DataType2> for DataWrap{
    fn from(v: DataType2) -> Self {
        DataWrap::DataType2(v)
    }
}

And finally, here is my question:
According to the definitions above, we have 4 combinations:
Representation<WordType1, DataType1>, Representation<WordType1, DataType2>, Representation<WordType2, DataType1>, Representation<WordType2, DataType2>,
and I want to write some functions that can convert the above 4 combinations of Representation into Representation<WordWrap, DataWrap>.

My current solution is using macro:

macro_rules! impl_representations_from(
    ($words:ty, $data:ty) => {
        impl From<Representation<$words, $data>> for Representation<WordWrap, DataWrap> {
            fn from(from: Representation<$words, $data>) -> Self {
                let (metadata, words, data) = from.into_parts(); // this function returns the inners of a ```Representation``` separately.
                Representation{
                    metadata,
                    words: words.into(),
                    data: data.into(),
                }
            }
        }
    }
);

and then implement it for every combination:

impl_embeddings_from!(WordType1, DataType1);
impl_embeddings_from!(WordType1, DataType2);
impl_embeddings_from!(WordType2, DataType1);
impl_embeddings_from!(WordType2, DataType2);

But I can see there are going to be more struct that will implement Data or Words, so that enum WordWrap and enum DataWrap will have more variants, meaning there will be more combinations when I try to convert a specific Representation type into Representation<WordWrap, DataWrap>. If so, there could be a huge list of impl_embeddings_from!(WordTypeX, DataTypeY);, which is very not elegant and could be problematic.

Is there a better way of doing so?

Thank you!

Best,
Tori Tylor

Is using Box<dyn Word> viable alternative to enum?

https://crates.io/crates/enum_derive has a lot of helpful derives for handling enums

1 Like

You can reuse your From impls:

impl<W, D> From<Representation<W, D>> for Representation<WordWrap, DataWrap>
where
    W: Into<WordWrap>,
    D: Into<DataWrap>,
{
    fn from(from: Representation<W, D>) -> Self {
        let (metadata, words, data) = from.into_parts();
        Representation{
            metadata,
            words: words.into(),
            data: data.into(),
        }
    }
}

Nah I don't think this would work, it has a conflict with impl<T> std::convert::From<T> for T since WordWrap and DataWrap both also have the implementation Into<#themselves#> :frowning:

Fair point. Perhaps I was a bit too quick here. Unless you want to make your own From-like trait to use as bounds in the impl, the macro is probably the easiest solution.

1 Like