Macro to fill all variants?

Let’s say I have a simple enum like this:

enum Fruit {
    Apple(String),
    Cherry(u32)
}

And I want to apply a provided function to both variants. Seems I need a macro for this - but then how do I define a macro that uses the matched arg with the function that was passed in?

For example, either of these should work:

let apple = Fruit::Apple("delicious".to_string());
let cherry = Fruit::Cherry(42);

//made up syntax
with_both!(apple, inner_contents => {
        println!("fruit has {}", $inner_contents)
});

with_both!(cherry, inner_contents => {
        println!("fruit has {}", $inner_contents)
});

I’d also be fine with having a magic keyword, e.g. without the arrow:

with_both!(apple, {
        println!("fruit has {}", $inner_contents);
});

?

Is the print the only scenario or one of many and it should work with any function?

print is just an example - should work with any function.

However, no need to like accept a list of functions - calling the macro each time would be fine

Here’s a starting place (thanks to someone on Discord) - but it breaks down due to the type conflicts:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=39d368dd19b44cf6b1ecd519cbd3eba3

I also tried working around it with Traits but no dice :\

I’ve got this but it breaks down fairly quickly:

macro_rules! both {
    ($fruit: ident, $closure: expr) => {
        match $fruit {
            Fruit::Apple(inner) => ($closure)(inner),
            Fruit::Cherry(inner) => ($closure)(inner),
        }
    };
}

// used like this
both!(fruit, |inner| {
    println!("fruit has {}", inner);
});

I don’t know if a “general” solution even exist.

yeah I think it’s like the above, breaks down when type annotations are needed :frowning:

You kind of need a macro to generate the required macro (a meta-macro of sorts). This makes the following code work:

derive_match! { matching_Fruit in [crate],
    enum Fruit {
        Apple(String),
        Cherry(u32)
    }
}

fn main ()
{
    let apple = Fruit::Apple("delicious".to_string());
    let cherry = Fruit::Cherry(42);

    matching_Fruit! {
        let Fruit(ref inner_contents) = apple;
        println!("fruit has {}", inner_contents);
    }
    matching_Fruit! {
        let Fruit(ref inner_contents) = cherry;
        println!("fruit has {}", inner_contents);
    }
}

(Taken from this other post)


Note: declaration-site syntax could be made nicer with a proc_macro_derive, but I don’t know how much would people be interested by it :man_shrugging:

1 Like

lol… looking at that playground… I’m going to have some whiskey now :slight_smile:

seriously though I’m sure it’s amazing… out of curiousity - I am wondering it works out of the box with the second example (it seems Strings/uints cause some false positives with type inference)?

in the meantime I think I’m going to just write the duplicate code… in a pinch it seems the procedural macro capabilities would make this easy since it’s just string replacement (but evidently that needs to be done in a separate crate?)

one of those days…

I remember writing a defer!() macro ages ago which would let you apply the same operation to each variant in an enum (in this case, when working with AST nodes).

Here is a variant of that macro which I uploaded to crates.io.

#[macro_export]
macro_rules! defer {
    ($kind:ident as $variable:expr; $( $variant:ident )|* => |ref $item:ident| $exec:expr) => {
        $crate::defer!(@foreach_variant $kind, $variable;
            $(
                $kind::$variant(ref $item) => $exec
            ),*
        )
    };
    ($kind:ident as $variable:expr; $( $variant:ident )|* => |ref mut $item:ident| $exec:expr) => {
        $crate::defer!(@foreach_variant $kind, $variable;
            $(
                $kind::$variant(ref mut $item) => $exec
            ),*
        )
    };
    (@foreach_variant $kind:ident, $variable:expr; $( $pattern:pat => $exec:expr ),*) => {
        match $variable {
            $(
                $pattern => $exec,
            )*
            #[allow(unreachable_patterns)]
            _ => unreachable!("Unexpected variant, {}, for {}",
                <_ as $crate::SumType>::variant(&$variable),
                stringify!($kind)),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Foo {
    First(u32),
    Second(f64),
    Third(String),
}

let third = Foo::Third(String::from("Hello, World!"));

let repr = defer!(Foo as third; First | Second | Third => |item| item.to_string());

// expands roughly to:

let repr = match third {
  Foo::First(item) => item.to_string(),
  Foo::Second(item) => item.to_string(),
  Foo::Third(item) => item.to_string(),
};
1 Like

Wow that looks very close… just not sure about the Foo as third part… doesn’t that require knowing up front which variant it is (which kinda defeats the purpose here)?

No, just the enum type.

It wasn’t ideal. I wanted a generic defer!() macro which could apply to a match with each match arm evaluating the provided expression. Unfortunately this only really works if you pass in the type (the Foo in Foo as ...), as well as each variant (the First | Second | Third).

I guess you could write a macro which generates this sort of macro without requiring the type and variants (e.g. defer_foo!(some_foo => |item| item.to_string())), but that sounded too hard.

I reckon it’d be easy to do with a custom attribute (e.g. #[defer] enum Foo { ... }) that generates something like this:

#[defer(name = defer_foo)]
enum Foo {
    First(u32),
    Second(String),
    Empty,
}

// expands to:

macro_rules! defer_foo {
    ($value:expr => |$var:ident| $eval:expr) => {
        match $value {
            Foo::First($var) => $eval,
            Foo::Second($var) => $eval,
        }
    }
}
1 Like

ah and I see - the name given is just the name for the generated match variable. Cool!

Thanks!

Cross-posting this here since it’s highly related and could help someone searching, even though the title there is totally different: How to publish a crate, with docs, that depends on features?

(specifically, the macro to fill out the traits automatically: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a33a830471c9f77d5caf0d59da2bd3a0)

2 Likes