I have an enum with over 100 variants. and I have to get each of its variants from a string. Something like this.
enum VeryLongEnum {
A,
B,
C,
D,
E,
}
impl From<&'static str > for VeryLongEnum {
fn from(s: &'static str) -> VeryLongEnum {
match s {
"A" => VeryLongEnum::A,
"B" => VeryLongEnum::B,
"C" => VeryLongEnum::C,
"D" => VeryLongEnum::D,
"E" => VeryLongEnum::E,
}
}
}
But I don't want to write all the variants one by one, it's crazy.
I'm trying to create a mini macro to simplify this, I did something like this.
macro_rules! from_str {
( $s:expr ) => {{
let t: VeryLongEnum = VeryLongEnum::$s;
t
}};
}
to use like this.
let variant = "A";
let en = from_str!(variant);
But I'm having an error which says expected a identifier.
I understand that identifiers and expresions are different types of token trees, but the question is how can I "force" this to generate the enum variant with the literal?
let variant = "A";
let en = from_str!(variant);
variant is a string that exists at runtime, you cannot pass it to a macro like that.
An alternative is to define a macro that defines the enum as well as the string conversion. This macro can use the stringify! macro in Rust to convert at identifier to a static string that can be passed to pattern match. Since the conversion is fallible, you should define a TryFrom instead of From (or FromStr which allows you to call "A".parse()).
macro_rules! go {
($($ident:ident)+) => {
#[derive(Debug)]
enum VeryLongEnum {
$($ident,)+
}
impl TryFrom<&'static str> for VeryLongEnum {
type Error = &'static str;
fn try_from(s: &'static str) -> Result<VeryLongEnum, &'static str> {
match s {
$(stringify!($ident) => Ok(VeryLongEnum::$ident),)+
_ => Err("Invalid String")
}
}
}
}
}
go! { A B C D E }
fn main() {
let _ = dbg!(VeryLongEnum::try_from("A"));
let _ = dbg!(VeryLongEnum::try_from("E"));
let _ = dbg!(VeryLongEnum::try_from("F"));
}
Output:
[src/main.rs:24] VeryLongEnum::try_from("A") = Ok(
A,
)
[src/main.rs:25] VeryLongEnum::try_from("E") = Ok(
E,
)
[src/main.rs:26] VeryLongEnum::try_from("F") = Err(
"Invalid String",
)
Playground
Related
I have this next_expected_kind method that return the next item of an Iterable<Kind> if it is the expected type, or an error if not.
It works fine for non parameterized types like Kind1, but I don't know how to use it if the type that needs parameters like Kind2.
Something like:
let _val = match s.next_expected_kind(Kind::Kind2(str)) {
Ok(k) => str,
_ => panic!("error"),
};
Is there any tricky to make it?
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d21d5cff42fcca633e95b4915ce2bf1d
#[derive(PartialEq, Eq)]
enum Kind {
Kind1,
Kind2(String),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
impl S {
fn next_expected_kind(&mut self, expected: Kind) -> Result<Kind, &str> {
match self.kinds.next() {
Some(k) if k == expected => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1, Kind::Kind2(String::from("2"))].into_iter(),
};
_ = s.next_expected_kind(Kind::Kind1);
// let _val = s.next_expected_kind(Kind::Kind2(str));
let _val = match s.kinds.next() {
Some(Kind::Kind2(str)) => str,
_ => panic!("not expected"),
};
}
You could use std::mem::discriminant() like this:
use std::mem::{Discriminant, discriminant};
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Kind1,
Kind2(String),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
impl S {
fn next_expected_kind(&mut self, expected: Discriminant<Kind>) -> Result<Kind, &str> {
match self.kinds.next() {
Some(k) if discriminant(&k) == expected => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1, Kind::Kind2(String::from("2"))].into_iter(),
};
_ = dbg!(s.next_expected_kind(discriminant(&Kind::Kind1)));
let _val = dbg!(s.next_expected_kind(discriminant(&Kind::Kind2(String::new()))));
}
The obvious drawback being that you'll have to create an instance with "empty" or default data wherever you want to call it.
The only other way I can think of would be to write a macro since you can't pass just the "variant" of an enum around.
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Kind1(String),
Kind2(i32, i32),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
macro_rules! next_expected_kind {
($self:expr, $expected:path) => {
match $self.kinds.next() {
Some(k) if matches!(k, $expected(..)) => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1(String::from("1")), Kind::Kind2(2,3)].into_iter(),
};
_ = dbg!(next_expected_kind!(&mut s, Kind::Kind1));
let _val = dbg!(next_expected_kind!(&mut s, Kind::Kind2));
}
Note: this has the limitation that all variants have to be tuple variants or struct variants and it's a bit clunky to use.
I'm new to rust.
I'm trying to follow the example for implementing the from_str trait here
https://doc.rust-lang.org/std/str/trait.FromStr.html
But I keep getting this error pointing at 'return Err(Self::Err)'
variant or associated item not found in `black_jack_tools::PlayerDifficulty`
I have an idea of why, Self::Err isn't defined in my enum But I don't get why rust cares in this scenario since I'm returning an Err of my Err object which is inline with the Result<Self,Self::Err> type.
Here's my FromStr is below here's a link to the rust playground with an MRE
impl FromStr for PlayerDifficulty {
type Err = ParseError;
fn from_str(s:&str) -> Result<Self,Self::Err>{
let result = match s {
"Player" => Ok(PlayerDifficulty::Player),
"Dealer" => Ok(PlayerDifficulty::Dealer),
"Normal" => Ok(PlayerDifficulty::Normal),
"Perfect"=> Ok(PlayerDifficulty::Perfect),
"Micky" => Ok(PlayerDifficulty::Micky),
"Elliot" => Ok(PlayerDifficulty::Elliot),
"Cultist"=> Ok(PlayerDifficulty::Cultist),
_ => return Err(Self::Err)
};
}
}
What Am I doing wrong?
Is there a better way to do this?
There are three issues with your code. The first is that you need to use <Self as FromStr>::Err if you want to refer to the Err type in your FromStr implementation:
impl FromStr for PlayerDifficulty {
type Err = ParseError;
fn from_str(s:&str) -> Result<Self,Self::Err>{
let result = match s {
"Player" => Ok(PlayerDifficulty::Player),
/* ... */
_ => return Err(<Self as FromStr>::Err)
};
}
}
Self::Err tries to look for an Err variant in the PlayerDifficulty enum but there is no such variant.
The second issue is that std::string::ParseError is in fact an alias for std::convert::Infallible, which is an error that can never happen and cannot be instantiated. Since your conversion may fail, you need to use an error that can be instantiated or define your own:
struct UnknownDifficultyError;
impl FromStr for PlayerDifficulty {
type Err = UnknownDifficultyError;
fn from_str(s:&str) -> Result<Self,Self::Err>{
let result = match s {
"Player" => Ok(PlayerDifficulty::Player),
/* ... */
_ => return Err(UnknownDifficultyError),
};
}
}
Finally, you need to return the result even when conversion succeeds, by removing the let result = and the semicolon:
struct UnknownDifficultyError;
impl FromStr for PlayerDifficulty {
type Err = UnknownDifficultyError;
fn from_str(s:&str) -> Result<Self,Self::Err>{
match s {
"Player" => Ok(PlayerDifficulty::Player),
/* ... */
_ => return Err(UnknownDifficultyError),
}
}
}
Playground
The function will return it last statement. Remove the last semicolon, and you could also remove the internal return statement, the result of the match statement will be returned.
Is there a better way? It looks like you are parsing a string to a enum, the create enum-utils does that. Instead of implementing the parser with boilerplate code you just derive it.
#[derive(Debug, PartialEq, enum_utils::FromStr)]
enum PlayerDifficulty {
Player,
Dealer,
Cultist,
Normal,
}
fn main() {
let _x:PlayerDifficulty= "Player".parse().unwrap();
}
And in your cargo.toml
[dependencies]
enum-utils = "0.1.2"
You should define a custom error
#[derive(Debug)]
struct PlayerError;
impl std::fmt::Display for PlayerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not parse player")
}
}
impl std::error::Error for PlayerError{}
Then change the match always return the Result in the same path
use std::str::FromStr;
impl FromStr for PlayerDifficulty {
type Err = PlayerError;
fn from_str(s:&str) -> Result<Self,Self::Err>{
match s {
"Player" => Ok(PlayerDifficulty::Player),
"Dealer" => Ok(PlayerDifficulty::Dealer),
"Normal" => Ok(PlayerDifficulty::Normal),
"Perfect"=> Ok(PlayerDifficulty::Perfect),
"Micky" => Ok(PlayerDifficulty::Micky),
"Elliot" => Ok(PlayerDifficulty::Elliot),
"Cultist"=> Ok(PlayerDifficulty::Cultist),
_ => Err(PlayerError)
}
}
}
And use it with ? to propagate error.
fn main() -> (Result<(),Box<dyn std::error::Error>>) {
let _x = PlayerDifficulty::from_str("Player")?;
let _x = PlayerDifficulty::from_str("PlayerPlayer")?;
Ok(())
}
Is there a succinct way to deserialize a variant of a fieldless enum from either its name or discriminant value? e.g. given this enum—
enum Foo {
A = 1,
B = 2,
C = 3,
}
—I’d like any of these strings or numbers to represent it:
{
"example-a": "a", // Foo::A
"example-b": "b", // Foo::B
"example-c": "c", // Foo::C
"example-1": 1, // Foo::A
"example-2": 2, // Foo::B
"example-3": 3, // Foo::C
}
I’ve seen that deriving Deserialize accommodates the former group and Deserialize_repr the latter, but I’m not sure how to accommodate both simultaneously.
I expected that a shorthand like #[serde(alias = …)] might exist to cover this scenario.
There is not a built-in shortcut that supports this directly. You will have to implement Deserialize manually. It's neither trivial nor super complicated:
impl<'de> serde::Deserialize<'de> for Foo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>
{
struct FooVisitor;
impl<'de> serde::de::Visitor<'de> for FooVisitor {
type Value = Foo;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "an integer or string representing a Foo")
}
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Foo, E> {
Ok(match s {
"a" => Foo::A,
"b" => Foo::B,
"c" => Foo::C,
_ => return Err(E::invalid_value(serde::de::Unexpected::Str(s), &self)),
})
}
fn visit_u64<E: serde::de::Error>(self, n: u64) -> Result<Foo, E> {
Ok(match n {
1 => Foo::A,
2 => Foo::B,
3 => Foo::C,
_ => return Err(E::invalid_value(serde::de::Unexpected::Unsigned(n), &self)),
})
}
}
deserializer.deserialize_any(FooVisitor)
}
}
Note that using deserialize_any means we are relying on the data format being self-describing; i.e., that the deserializer knows whether the data is stringy or integerish and will call the correct visit_ method accordingly. Serde also supports non-self-describing formats; however, you won't be able to use them with this Deserialize implementation.
The following code example is the best that I have come up with so far:
enum Variant {
VariantA(u64),
VariantB(f64),
}
fn main() {
let my_vec = vec![Variant::VariantA(1),
Variant::VariantB(-2.0),
Variant::VariantA(4),
Variant::VariantA(3),
Variant::VariantA(2),
Variant::VariantB(1.0)];
let my_u64_vec = my_vec
.into_iter()
.filter_map(|el| match el {
Variant::VariantA(inner) => Some(inner),
_ => None,
})
.collect::<Vec<u64>>();
println!("my_u64_vec = {:?}", my_u64_vec);
}
I would like to know if there is a less verbose way of obtaining the vector of inner values (i.e., Vec<u64> in the example). It feels like I might be able to use something like try_from or try_into to make this less verbose, but I cannot quite get there.
Enums are not "special" and don't have much if any implicitly associated magic, so by default yes you need a full match -- or at least an if let e.g.
if let Variant::VariantA(inner) = el { Some(inner) } else { None }
However nothing prevents you from implementing whatever utility methods you're thinking of on your enum e.g. get_a which would return an Option<A> (similar to Result::ok and Result::err), or indeed to implement TryFrom on it:
use std::convert::{TryFrom, TryInto};
enum Variant {
VariantA(u64),
VariantB(f64),
}
impl TryFrom<Variant> for u64 {
type Error = ();
fn try_from(value: Variant) -> Result<Self, Self::Error> {
if let Variant::VariantA(v) = value { Ok(v) } else { Err(()) }
}
}
fn main() {
let my_vec = vec![Variant::VariantA(1),
Variant::VariantB(-2.0),
Variant::VariantA(4),
Variant::VariantA(3),
Variant::VariantA(2),
Variant::VariantB(1.0)];
let my_u64_vec = my_vec
.into_iter()
.filter_map(|el| el.try_into().ok())
.collect::<Vec<u64>>();
println!("my_u64_vec = {:?}", my_u64_vec);
}
The following Python code returns the first non-empty string (in this example, the content of bar):
foo = ""
bar = "hello"
foo or bar # returns "hello"
How do I write it in Rust? I tried with this:
let foo = "";
let bar = "";
foo || bar;
but I am getting this
error[E0308]: mismatched types
--> src/main.rs:4:5
|
4 | foo || bar;
| ^^^ expected bool, found &str
|
= note: expected type `bool`
found type `&str`
I suppose I can't easily do what I do in Python with Rust?
You can also create an extension trait that will add your desired behavior:
trait Or: Sized {
fn or(self, other: Self) -> Self;
}
impl<'a> Or for &'a str {
fn or(self, other: &'a str) -> &'a str {
if self.is_empty() { other } else { self }
}
}
Now you can use it like this:
assert_eq!("foo".or("bar"), "foo");
assert_eq!("".or("").or("baz").or("").or("quux"), "baz");
If you need to make sure that the second argument is evaluated lazily you can extend the Or trait with the following method:
fn or_else<F: FnOnce() -> Self>(self, f: F) -> Self;
See analogous methods of Option: Option#or and Option#or_else for more details.
Rust has no concept of truthy or falsy values like Python, so you cannot use strs as booleans. In fact, you cannot use anything but actual bools with comparison operators.
An alternative to #Aratz's solution using match would be
let result = match (foo.is_empty(), bar.is_empty) {
(true,_) => Some(foo),
(_, true) => Some(bar),
_ => None,
};
If you want this kind or functionality for multiple strings, you can use a macro:
macro_rules! first_nonempty {
( $( $string:expr),+ )=> ({
$(
if !$string.is_empty() {
Some($string)
} else
)+
{ None }
})
}
Used like this
let res = first_nonempty!("foo", "bar", ""); // res is Some("foo")
let res = first_nonempty!("", "bar", "baz", "quux"); // res is Some("bar")
let res = first_nonempty!(""); // res is None
If you have several strings, I would use iterators;
let strs = ["", "foo", "bar"];
let non_empty = strs.iter().skip_while(|&x| x.is_empty()).next();
println!("{}", non_empty.unwrap_or(&""));
It can also go into its own function, if you're using this a lot:
// call as let non_empty = first_non_empty(&["", "foo", "bar"]);
fn first_non_empty<'a>(strs: &[&'a str]) -> &'a str {
strs.iter().skip_while(|&x| x.is_empty()).next().unwrap_or(&"")
}
The problem here is that Rust will not implicitly convert strings (or anything else) to booleans in logical expressions.
If you want to mimic Python's behavior, i.e. you want to keep strings after your boolean expression, you have to be more explicit with your code, e.g.:
if foo != "" {
foo
} else if bar != "" {
bar
} else {
""
}