0
votes

Is there a simple way to create an enum that provides the ability to coerce or otherwise cast instances from one variant to another, as long as the underlying types match?

For instance, given this enum:

enum CanConvert {
    Vec1(Vec<String>),
    Vec2(Vec<String>),
    NotVec(i32),
}

I would like to be able to easily convert between the Vec1 and Vec2 variants:

let v1 = CanConvert::Vec1(["one", "two", "three", "uno", "dos", "tres"]
                          .iter()
                          .map(|&s| s.into())
                          .collect());

let v2 = v1 as CanConvert::Vec2;

The code above currently gives the error not a type, since enum variants are not (currently) considered types. And even if it compiled, it's not clear how the case where v1 is the NotVec variant should be handled (panic?).

This works, but it's pretty verbose, and this seems like it should be a reasonably common pattern:

let v2 = match v1 {
    CanConvert::Vec1(ref underlying) |
    CanConvert::Vec2(ref underlying) => CanConvert::Vec2(underlying.clone()),
    _ => panic!("v1 did not have appropriate underlying type!"),
};

So is there a simple way to provide this kind of simple type-transformation between variants where possible? I haven't written any of my own Deref coercions, but that seems like it might be what I'm looking for here. (A coercion wrapped in some kind of custom derive would be ideal.)

EDIT: Note that as far as I can tell, there cannot be any safe way of obtaining a reference to the original, with the desired variant type; a full clone is required, because the reference would necessarily have the wrong variant tag. However, it seems like it should be possible to convert the original enum by simply changing the tag appropriately.

1
...I suppose it's also possible that having multiple variants with the same underlying type isn't really rustic, in which case this whole issue should probably be avoided.Kyle Strand
and this seems like it should be a reasonably common pattern — I disagree. The main reason I would use two enum variants with the same wrapped type is because they have different meanings. Since they have different meanings, converting from one to the other would usually require some extra processing, which wouldn't be abstractable.Shepmaster
@Shepmaster But all different types have different meanings, so your argument seems to apply to all forms of type coercion.Kyle Strand
@KyleStrand: Indeed, and you'll note that in Rust said conversions are explicit.Matthieu M.
@MatthieuM. Isn't var as NewType explicit?Kyle Strand

1 Answers

2
votes

First of all, since &T is an immutable reference, an implementation of Deref cannot change a type. It can merely point inside (in this case, it could point to the underlying Vec).

Regarding conversion, ref is introducing an inefficiency here as it forces you to clone, if you take ownership instead you can simply move the underlying vector:

fn to_v2(v: CanConvert) -> CanConvert {
    match v {
        CanConvert::Vec1(u) | CanConvert::Vec2(u) => CanConvert::Vec2(u),
        _ => panic!("Inappropriate type");
    }
}