strum
strum copied to clipboard
Impl back from EnumDiscriminant
The derive for EnumDiscriminant is helpful, but it doesn't provide a way to convert back to the original enum whose variants it enumerates. Is this something that can be implemented?
Thanks for the issue. EnumDiscriminant
is a brand new feature merged earlier this week actually so feedback is welcome. The primary use-case for EnumDiscriminant
is times when the original enum's variants don't implement the Default
trait. FromString and EnumIter can't create the variants if the values don't have default values so you can use EnumDiscriminant
as an alternative.
I would love to have a simple conversion trait from the discriminant back to the original enum, but we're going to run into the same problem again. What do we set the default value to of types that don't implement default?
If you have ideas about how to solve that issue, or another use-case that I'm missing, please let me know :)
I think it'd be sensible for the initial implementation to require every variant to either be empty or be composed entirely of Default types for this to be implemented. This should be enough to be useful (and for types that don't impl it, users can always use newtype wrappers until the functionality comes to specify non-default values).
A couple of thoughts:
-
Short-term, you shouldn't need to use
EnumDiscriminants
if the type's all implement default. You can just throw the normal strum attributes on your type. EnumDiscriminants is really designed for types that don't havedefault
. The only reason this might not be a good idea is ifdefault()
is expensive to call (which it shouldn't be as a rule of thumb), or if the type is very large and it will allocate a large chunk of memory. -
Long-term: Strum doesn't have access to type information outside of the immediate context. It can see the names of types and their variants, but it doesn't know anything about what traits are implemented on what types. In all the other strum traits, they assume the trait implements
Default
and let the compiler fail later if the type actually doesn't. Since we can't assume a type implementsDefault
we'll need the user to add an additional tag. It's doable, and useful, but only in the 2 cases mentioned above.
I will keep this in the backlog. You're more than welcome to implement it if you'd like :), but you should be unblocked by adding the attributes directly to the original enum.
This would definitely be helpful for cases where you have some variants with no value and/or those with types that happen to implement Default
but need to use the string or other data to dynamically generate values for some of the other variants. As you mentioned, it would be impossible to create instances of those variants so the implementation would have to return Err
for those cases, making TryFrom
/TryInto
a good potential candidate here. The simplest example I can think of would be handling strings that either are a mathematical operator or a number for calculators, etc.:
#[derive(EnumDiscriminants)]
#[strum_discriminants(derive(EnumString, TryInto))]
enum Operator {
#[strum(serialize = "+")]
Add,
#[strum(serialize = "-")]
Sub,
Integer(i64),
}
impl FromStr for Operator {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(discriminant) = OperatorDiscriminants::from_str(s) {
discriminant.try_into().unwrap()
} else {
Operator::Integer(str::parse(s)?)
}
}
}
#[test]
fn from_str() {
assert_eq!(Operator::Add, Operator::from_str("+");
assert_eq!(Operator::Integer(1), Operator::from_str("1");
}
discriminant.try_into().unwrap()
would never panic in this case because numbers wouldn't match any variants with OperatorDiscriminants::from_str(s)
either. If #[strum(default)]
(or similar) were to also be used by EnumString
in the Discriminants
enum that would make it a bit cleaner to use as well, but that would only apply when there's exactly one variant that needs special handling.
As far as I can tell, the only way to implement this right now is to manually map each discriminant to its variant in the parent enum in a match
block or similar.
I completely agree. I think this would be a super useful feature. The hard part of implementing it currently is that procedural macros don't have access to semantic type information. That means we don't know if a type implements Default
or not. Users would need to annotate variants with whether or not they can be instantiated or only allow the reverse translation to unit like variants.