How to define functions for specific variants - rust

I have an enum with an associated show function.
enum Foo {
Bar(u32),
Baz
}
fn show(foo: Foo) {
match foo {
Foo::Bar(_) => show_bar(foo),
Foo::Baz => show_baz(foo),
}
}
Based on the variant, a specialized show_* function is called.
fn show_bar(foo: Foo) {
match foo {
Foo::Bar(x) => println!("BAR({x})"),
_ => (),
}
}
fn show_baz(foo: Foo) {
match foo {
Foo::Baz => println!("BAZ"),
_ => (),
}
}
Since I can't pass variants as types, I need to repeat the match foo pattern matching inside each show_* function, which is a bit verbose and undeeded.
Is there a better / more idiomatic way to do this?

I'm a fairly simple soul and not a Rust expert, so maybe this doesn't meet your needs for some reason - but why not simply pass the associated data? (Which is nothing in the case of Baz)
enum Foo {
Bar(u32),
Baz
}
fn show(foo: Foo) {
match foo {
Foo::Bar(barValue) => show_bar(barValue),
Foo::Baz => show_baz(),
}
}
fn show_bar(x: u32) {
println!("BAR({x})"),
}
fn show_baz() {
println!("BAZ"),
}

Related

Pattern match on enum type don't care about arguments

enum Foo {
Bar(usize, usize),
Baz(isize),
}
impl Operator {
fn value(&self) -> usize {
use Foo::*;
match (self) {
Bar(_) => 1,
Baz(_) => 2,
}
}
}
neither Bar(_), Bar, nor Bar() work.
I just want to pattern-match the enum type, and the arguments don't matter at all. I would prefer to not have to remember how many _ I need to put for each enum variant, and not have to change these patterns in the case that the enum definition changes.
Since Bar has two arguments you either have to match them both:
match self {
Bar(_, _) => 1,
Baz(_) => 2,
}
or use ..
match self {
Bar(..)
Baz(_) => 2,
}

Rust Macros: Repeat n times based on number of arguments without using the actual argument

Is it possible to repeat something n times based on the number of arguments without using the actual argument? My usecase would be to implement an Enum variant, that takes 1 or more type params as well as an implementation that matches the enum variant using wildcards. So the number of wildcards will depend on the number of arguments provided.
Example:
macro_rules! impl_enum {
($name: ident, $($params: ty)+, $val:expr) => {
enum MyEnum {
$name($($params)+,)
}
impl MyEnum {
fn get_val(self) {
match self {
MyEnum::$name(??????) => $val
}
}
}
}
}
Now I would like the output of
impl_enum!(Variant1, Type1 Type2 Type3, 42);
to be
enum MyEnum {
Variant1(Type1, Type2, Type3),
}
impl MyEnum {
fn get_val(self) {
match self {
MyEnum::Variant1(_,_,_) => 42,
}
}
}
Is this possible?
You can do this with a macro that converts a ty to an underscore:
macro_rules! type_as_underscore {
( $t: ty ) => { _ };
}
Now, using this and fixing up a few other errors in your macro:
macro_rules! impl_enum {
($name: ident, $($params: ty)+, $val:expr) => {
enum MyEnum {
$name($($params),+)
}
impl MyEnum {
fn get_val(self) {
match self {
MyEnum::$name($(type_as_underscore!($params)),+) => $val
};
}
}
};
}
On nightly, this is supported by the experimental feature macro_metavar_expr. You can add ${ignore(metavariable)} to a repeat expression to mark that you want it to be based on metavariable, but without actually emitting it:
#![feature(macro_metavar_expr)]
macro_rules! impl_enum {
($name: ident, $($params: ty)+, $val:expr) => {
enum MyEnum {
$name($($params),+)
}
impl MyEnum {
fn get_val(self) {
match self {
MyEnum::$name($( ${ignore(params)} _ ),*) => $val
}
}
}
}
}

How to access nested enums without full match syntax

I am using the Serde crate to deserialise a JSON file, which has a nested structure like this:
struct Nested {
a: Vec<Foo>,
b: u8,
}
struct Foo {
c: Bar,
d: Vec<f32>,
}
Struct Bar {
e: u32,
f: String,
}
Part of the applications purpose is to check for missing parameters (or incorrect types in parameters), and then display a nicely printed list of errors found in the file, so I need to handle the structure missing parameters or wrongly typed.
I came across this great post that helped solved my issue, by wrapping each parameter in an enum result that contains the value if it passed, the value if it failed, or a final enum if it was missing (since the nested structures might also be missing I wrapped them in the same enum):
pub enum TryParse<T> {
Parsed(T),
Unparsed(Value),
NotPresent
}
struct Nested {
a: TryParse<Vec<Foo>>,
b: TryParse<u8>,
}
struct Foo {
c: TryParse<Bar>,
d: TryParse<Vec<f32>>,
}
Struct Bar {
e: TryParse<u32>,
f: TryParse<String>,
}
However, I'm not sure how to access them now without unpacking every step into a match statement. For example, I can access B very easily:
match file.b {
Parsed(val) => {println!("Got parameter of {}", val)},
Unparsed(val) => {println!("Invalid type: {:?}", val)}
NotPresent => {println!("b not found")},
};
However, I'm not sure how to access the nested ones (C D E and F). I can't use Unwrap or expect since this isn't technically a 'Result', so how do I unpack these?:
if file.a.c.e.Parsed() && file.a.c.e == 32 {... //invalid
if file.a.d && file.a.d.len() == 6... //invalid
I know in a way this flies against rust's 'handle every outcome' philosophy, and I want to handle them, but I want to know if there is a nicer way than 400 nested match statements (the above example is very simplified, the files I am using have up to 6 nested layers, each parameter in the top node has at least 3 layers, some are vectors as well)…
Perhaps I need to implement a function similar to unwrap() on my 'TryParse'? or would it be better to wrap each parameter in a 'Result', extend that with the deserialise trait, and then somehow store the error in the Err option that says if it was a type error or missing parameter?
EDIT
I tried adding the following, some of which works and some of which does not:
impl <T> TryParse<T> {
pub fn is_ok(self) -> bool { //works
match self {
Self::Parsed(_t) => true,
_ => false,
}
}
pub fn is_absent(self) -> bool { //works
match self {
Self::NotPresent => true,
_ => false,
}
}
pub fn is_invalid(self) -> bool { //works
match self {
Self::Unparsed(_) => true,
_ => false,
}
}
pub fn get(self) -> Result<T, dyn Error> { //doesnt work
match self {
Self::Parsed(t) => Ok(t),
Self::Unparsed(e) => Err(e),
Self::NotPresent => Err("Invalid")
}
}
}
I can't believe it is this hard just to get the result, should I just avoid nested enums or get rid of the TryParse enums/ functions all together and wrap everything in a result, so the user simply knows if it worked or didn't work (but no explanation why it failed)
Implementing unwrap() is one possibility. Using Result is another, with a custom error type. You can deserialize directly into result with #[serde(deserialize_with = "...")], or using a newtype wrapper.
However, a not-enough-used power of pattern matching is nested patterns. For example, instead of if file.a.c.e.Parsed() && file.a.c.e == 32 you can write:
if let TryParse::Parsed(a) = &file.a {
// Unfortunately we cannot combine this `if let` with the surrounding `if let`,
// because `Vec` doesn't support pattern matching (currently).
if let TryParsed::Parsed(
[Foo {
c:
TryParse::Parsed(Bar {
e: TryParse::Parsed(32),
..
}),
..
}, ..],
) = a.as_slice()
{
// ...
}
}
May not be the most Rust-y way of doing it, but for those like me moving from another language like C/Python/C++, this is the way I have done it that still allows me to quickly validate if I have an error and use the match syntax to handle it. Thanks to #Chayim Friedman for assisting with this, his way is probably better but this made the most sense for me:
#[derive(Debug)]
pub enum TryParse<T> {
Parsed(T),
Unparsed(Value),
NotPresent
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for TryParse<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match Option::<Value>::deserialize(deserializer)? {
None => Ok(TryParse::NotPresent),
Some(value) => match T::deserialize(&value) {
Ok(t) => Ok(TryParse::Parsed(t)),
Err(_) => Ok(TryParse::Unparsed(value)),
},
}
}
}
impl <T> TryParse<T> {
//pub fn is_ok(self) -> bool { ---> Use get().is_ok(), built into result
// match self {
// Self::Parsed(_t) => true,
// _ => false,
// }
//}
pub fn is_absent(self) -> bool {
match self {
Self::NotPresent => true,
_ => false,
}
}
pub fn is_invalid(self) -> bool {
match self {
Self::Unparsed(_) => true,
_ => false,
}
}
pub fn get(&self) -> Result<&T, String> {
match self {
Self::Parsed(t) => Ok(t),
Self::Unparsed(v) => Err(format!("Unable to Parse {:?}", v)),
Self::NotPresent => Err("Parameter not Present".to_string())
}
}
// pub fn get_direct(&self) -> &T {
// match self {
// Self::Parsed(t) => t,
// _ => panic!("Can't get this value!"),
// }
// }
}
match &nested.a.get().unwrap()[1].c.get.expect("Missing C Parameter").e{
Parsed(val) => {println!("Got value of E: {}", val)},
Unparsed(val) => {println!("Invalid Type: {:?}", val)}
NotPresent => {println!("Param E Not Found")},
};
//Note the use of '&' at the beginning because we need to borrow a reference to it
I know I need to change my mindset to use the rust way of thinking, and I am completely open to other suggestions if they can demonstrate some working code.

How to match an enum variant in a match

Is there a position for # operator or some other solution that would bind the variable in a match arm to a variant type rather than to the entire enum? In the following example, all bar, baz and qux have types of Foo, rather than Foo::Bar, Foo::Baz, Foo::Qux and the sample fails to compile.
enum Foo {
Bar(i32),
Baz{s: String},
Qux,
}
fn main() {
let foo = Foo::Bar(42);
match foo {
bar # Bar(..) => bar.0.to_string(),
baz # Baz{..} => baz.s,
qux # Qux => "".to_owned(),
}
}
Though handy if they existed, there is no such thing as an enum variant type in Rust. All variants have the same type. Their contents, on the other hand, might carry values of different types.
For example, both Foo::Bar(1) and Foo::Qux have the same type, which is Foo. So you can't bind those values and treat them differently as they are of the same type.
The most “idiomatic” solution I can think of is to just grab what's inside of the specific enum variant you're currently matching on, like so:
fn main() {
let foo = Foo::Bar(42);
let my_string = match foo {
Bar(bar) => bar.to_string(),
Baz{ s } => s,
Qux => "".to_owned(),
};
}
To reuse a variant enum you need to use enum tuple style and define external structure, it's a lot more used than enum struct:
struct Bar(i32);
struct Baz {
s: String,
}
enum Foo {
Bar(Bar),
Baz(Baz),
Qux,
}
fn main() {
let foo = Foo::Bar(Bar(42));
let foo = match foo {
Foo::Bar(bar) => bar.0.to_string(),
Foo::Baz(baz) => baz.s,
Foo::Qux => "".to_owned(),
};
assert_eq!(foo, "42");
}
I think the syntax you're looking for is this?
enum Foo {
Bar(i32),
Baz{s: String},
Qux,
}
fn main() {
let foo = Foo::Bar(42);
let s = match foo {
Foo::Bar(bar) => bar.to_string(),
Foo::Baz{s} => s,
Foo::Qux => "".to_owned(),
};
dbg!(s);
}
What you're looking for is use:
enum Foo {
Bar(i32),
Baz{s: String},
Qux,
}
fn main() {
use Foo::*;
let foo = Bar(42);
let s = match foo {
Bar(bar) => bar.to_string(),
Baz{s} => s,
Qux => "".to_owned(),
};
}

Is there a way to match on all variants of an enum in rust?

Is there something that is equivalent to this:
enum ABC {
A(u32),
B(i32),
C(f64),
}
fn main() {
let abc = ABC::A(42);
match abc {
_(foo) => foo,
}
}
The reason why I'm asking is that sometimes, I want to use an enum for the different possible types, but most cases I need to handle the data in the enum variants the same exact way.
If you want to match against all possible variants and perform a common action you can
write a macro for it and reduce code repetition:
enum ABC {
A(u32),
B(i32),
C(f64),
}
#[macro_use]
macro_rules! per_letter {
($val:expr, $pattern:pat => { $res:expr }) => (
match $val {
$crate::ABC::A($pattern) => $res,
$crate::ABC::B($pattern) => $res,
$crate::ABC::C($pattern) => $res,
}
)
}
impl ABC {
pub fn print_inner(&self) {
per_letter!(self, letter => {println!("{}", letter)});
}
pub fn print_quad(&self) {
per_letter!(self, letter => {println!("{}", *letter**letter)});
}
}
Note that this is limited. You can't have multiple possible return types without wrapping them in an enum. But it's still useful if your inner types share a common functionality.
fn main() {
let foo = ABC::A(2);
foo.print_inner();
foo.print_quad();
}

Resources