How can I get "Bar" from "Foo::Bar" in rust macro? - rust

Original demand: I want to implement a macro that converts Foo::* to Bar::*.
Pseudo code will look like this:
macro_rules! convert_foo_to_bar {
($v: ty, $p: path) => (<$v>::$p.name)
}
// convert_foo_to_bar!(Bar, Foo::A) -> Bar::A
While $p.name refers to A.

You can match the Foo::A using Foo::$variant:ident to get A as $variant like this:
macro_rules! convert_foo_to_bar {
($v: ty, Foo::$variant:ident) => (<$v>::$variant)
}
Playground
If you need to convert a variable, you will need to use a normal function such as this:
fn convert_foo_to_bar(foo: Foo) -> Bar {
match foo {
Foo::A => Bar::A,
Foo::B => Bar::B,
// .. for all of your variants
}
}

Related

Match on pair of enum when both are of the same kind

I want to get rid of the repeated code here, and not have to list each enum kind:
use Geometry::*;
let geometry = match (&self.geometry, &other.geometry) {
(Point(a), Point(b)) => Point(*a.interpolate(b, t)),
(Curve(a), Curve(b)) => Curve(*a.interpolate(b, t)),
(EPBox(a), EPBox(b)) => EPBox(*a.interpolate(b, t)),
(_, _) => unimplemented!("Kinds not matching")
};
The types inside the kinds all implement the Interpolate trait which has the interpolate method:
enum Geometry {
Point(Point),
Curve(Curve),
EPBox(EPBox)
}
trait Interpolate {
fn interpolate(&self, other: &Self, t: f64) -> Box<Self> {
// ...
}
}
impl Interpolate for Point {}
impl Interpolate for Curve {}
impl Interpolate for EPBox {}
What I want to do is something like this:
let geometry = match (&self.geometry, &other.geometry) {
(x(a), x(b)) => x(*a.interpolate(b, t)), // and check that a and b impls Interpolate
(_, _) => unimplemented!("Kinds not matching")
};
I'm not sure how many of these operations you want to implement, and how many enum variants you actually have. Depending on that, the best answer changes quite a bit, I think. If you really only have one operation (interpolate), and three variants: type it out, anything else will be a lot less simple and maintainable.
That being said, I might use
let geometry = binop!(match (&self.geometry, &other.geometry) {
(a, b) => *a.interpolate(b, t)
});
based on
macro_rules! binop {
(match ($av:expr, $bv:expr) { ($a:ident, $b:ident) => $op:expr }) => {{
use Geometry::*;
binop!($av, $bv, $a, $b, $op, Point, Curve, EPBox)
}};
($av:expr, $bv:expr, $a:ident, $b:ident, $op:expr, $($var:path),*) => {
match ($av, $bv) {
$(($var($a), $var($b)) => $var($op),)*
_ => unimplemented!("Kinds not matching")
}
};
}
Playground
You'll have to list up the enum variants once. If you don't want that, you'll have to go for a proc macro. I think that would be overkill.
With your current code, you can't really do this. Checking if the enum variants are the same variant is fairly simple with the use of core::mem::discriminant, but you can't access the contained value without match or if let statements. What you can do is use type parameters for the variables and type IDs. This is a really tacky way to do things, and should be avoided really, but it works. I've given an example to show you how you could implement it
use std::any::Any;
trait SomeTrait {
fn some_function(&self, other: Self) -> &Self;
}
#[derive(Clone)]
struct Var0 {
eg: i64
}
#[derive(Clone)]
struct Var1 {
other_eg: f64
}
fn check<T: 'static, B: 'static>(lhs: T, rhs: B) -> Option<T> {
if lhs.type_id() == rhs.type_id() {
Some(lhs.some_function(rhs))
} else {
None
}
}
fn main() {
let a = Var0{ eg: 2 };
let b = Var0{ eg: 9 };
let c = Var1 { other_eg: -3f64 };
assert!(check(a.clone(), b).is_some());
assert!(check(a, c).is_none());
}
impl SomeTrait for Var0 { /* ... */ }
impl SomeTrait for Var1 { /* ... */ }
Please note though: as I said before this is messy and pretty hacky and not very nice to read. If I were writing this I would use the match statement over this, but it's up to you what you do since this does require some reworking to implement. I'm also not sure if you can use different lifetimes for the check function other than `static which might be a problem.
I came up with the following solution, which is easy to extend with new Interpolate types:
macro_rules! geometries {
( $( $x:ident ),+ ) => {
enum Geometry {
$(
$x($x),
)*
}
fn interpolate_geometries(a: &Geometry, b: &Geometry, t: f64) -> Geometry {
use Geometry::*;
match (a, b) {
$(
($x(a), $x(b)) => $x(a.interpolate(b, t)),
)*
_ => unimplemented!("Kinds not matching")
}
}
$(
impl Interpolate for $x {}
)*
};
}
geometries!(Point, Curve, EPBox);
Which make it possible to later do:
let geometry = interpolate_geometries(&self.geometry, &other.geometry, t);
Thanks for all answers and comments! Especially the one with the binop macro, which is quite similar to my solution. After reading up on macros (thanks to comments here) I came up with this solution.

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();
}

Simplifying a `match` using a Rust macro

There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.
I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):
fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
let begin = Instant::now();
let output: T = question_func();
let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
println!("{}", output);
println!("{:.6}s taken", elapsed_secs);
}
fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }
fn main() {
// ...
match question_num {
1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
_ => {
println!("Question doesn't exist.");
},
}
}
I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:
error: variable 'question_num' is still repeating at this depth
I'm rather stumped too how I can print the Question doesn't exist. as a default case.
#[macro_export]
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
{
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
}
};
}
The way I'd like to use it, is (or anything just as short is fine as well):
run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
I read a bit of the Rust book, but there aren't exactly that many examples of macros.
How would I go about doing this?
Rather than many if statements, I just reproduced the match statement
with a repetition $( ... )* for all the available branches.
It seems to behave like the extensive match expression.
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
match $chosen_question {
$($question_num => run_question($question_mod),)*
_ => {
println!("Question doesn't exist.");
}
}
};
}
The error message explained:
macro_rules! run_questions {
($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:
$(if $chosen_question == $question_num {
run_question($question_mod::solve);
})*
Although as pointed out by #prog-fh's answer, better to use a match in the macro, same as in the straight code:
match $chosen_question {
$($question_num => run_question ($question_mod::solve),)*
_ => println!("Question doesn't exist.")
};

How do I assert an enum is a specific variant if I don't care about its fields?

I'd like to check enums with fields in tests while ignoring the actual value of the fields for now.
Consider the following example:
enum MyEnum {
WithoutFields,
WithFields { field: String },
}
fn return_with_fields() -> MyEnum {
MyEnum::WithFields {
field: "some string".into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn example() {
assert_eq!(return_with_fields(), MyEnum::WithFields {..});
}
}
playground
I'd like to use assert_eq! here, but the compiler tells me:
error: expected expression, found `}`
--> src/lib.rs:18:64
|
18 | assert_eq!(return_with_fields(), MyEnum::WithFields {..});
| ^ expected expression
This is similar to Why do I get an error when pattern matching a struct-like enum variant with fields?, but the solution does not apply in my case.
Of course, I can use match and do it myself, but being able to use assert_eq! would be less work.
Rust 1.42
You can use std::matches:
assert!(matches!(return_with_fields(), MyEnum::WithFields { .. }));
Previous versions
Your original code can be made to work with a new macro:
macro_rules! is_enum_variant {
($v:expr, $p:pat) => (
if let $p = $v { true } else { false }
);
}
#[test]
fn example() {
assert!(is_enum_variant!(return_with_fields(), MyEnum::WithoutFields {..}));
}
Personally, I tend to add methods to my enums:
fn is_with_fields(&self) -> bool {
match self {
MyEnum::WithFields { .. } => true,
_ => false,
}
}
I also tend to avoid struct-like enums and instead put in extra work:
enum MyEnum {
WithoutFields,
WithFields(WithFields),
}
struct WithFields { field: String }
impl MyEnum {
fn is_with_fields(&self) -> bool {
match self {
MyEnum::WithFields(_) => true,
_ => false,
}
}
fn as_with_fields(&self) -> Option<&WithFields> {
match self {
MyEnum::WithFields(x) => Some(x),
_ => None,
}
}
fn into_with_fields(self) -> Option<WithFields> {
match self {
MyEnum::WithFields(x) => Some(x),
_ => None,
}
}
}
I hope that some day, enum variants can be made into their own type to avoid this extra struct.
If you are using Rust 1.42 and later, see Shepmaster's answer below.
A simple solution here would be to do the opposite assertion:
assert!(return_with_fields() != MyEnum::WithoutFields);
or even more simply:
assert_ne!(return_with_fields(), MyEnum::WithoutFields);
Of course if you have more members in your enum, you'll have to add more asserts to cover all possible cases.
Alternatively, and this what OP probably had in mind, since assert! just panics in case of failure, the test can use pattern matching and call panic! directly in case something is wrong:
match return_with_fields() {
MyEnum::WithFields {..} => {},
MyEnum::WithoutFields => panic!("expected WithFields, got WithoutFields"),
}
I'd use a macro like #Shepmaster proposed, but with more error reporting (like the existing assert! and assert_eq! macros:
macro_rules! assert_variant {
($value:expr, $pattern:pat) => ({
let value = &$value;
if let $pattern = value {} else {
panic!(r#"assertion failed (value doesn't match pattern):
value: `{:?}`,
pattern: `{}`"#, value, stringify!($pattern))
}
})
// TODO: Additional patterns for trailing args, like assert and assert_eq
}
Rust playground demonstrating this example

How to write macro for similar matching arms?

I have some repetitive code
match *x {
A(ref a) => "special",
B(ref a) => "B foo",
C(ref a) => "C foo",
D(ref a) => "D foo",
// ...
}
I would like a macro like
macro_rules! generic_fmt {
($T:ident) => {
$T(ref a) => {"$T foo"},
}
}
So that I can simplify my matching to
match *x {
A(ref a) => "special",
generic_fmt!(B),
generic_fmt!(C),
generic_fmt!(D),
// ...
}
What's the best way to do that? I'm using rustc 1.19.0-nightly.
You cannot do that exactly. A macro cannot expand to a match arm (<Variant> => <Expression>).
The closest you could get is probably something like this:
enum Foo {
A(u32),
B(u32),
C(u32),
D(u32),
}
macro_rules! gen1 {
($Variant:ident) => {
Foo::$Variant(ref a)
}
}
macro_rules! gen2 {
($Variant:ident) => {
concat!(stringify!($Variant), " foo")
}
}
fn print(x: Foo) {
println!("{}", match x {
Foo::A(ref a) => "special",
gen1!(B) => gen2!(B),
gen1!(C) => gen2!(C),
gen1!(D) => gen2!(D),
});
}
fn main() {
print(Foo::A(42));
print(Foo::B(42));
print(Foo::C(42));
print(Foo::D(42));
}
Playground link.
If changing your output a little is acceptable, you could use a catch-all for all the same arms:
match x {
Foo::A(ref a) => println!("special"),
_ => println!("{:?} Foo", x),
}
Playground link
But this would print the type and its parameters. If you are on nightly, and not afraid of experimental, you can use std::intrinsics::type_name to display only the type name.
Or you can use a macro that does all your match arms:
macro_rules! gen_match {
($x:ident, $Special:ident, [$( $Foo:ident ),*]) => {
match $x {
Foo::$Special(ref a) => println!("special"),
$(Foo::$Foo(ref a) => println!("{} foo", stringify!($Foo)),)*
}
}
}
and calling it:
gen_match!(x, A, [B, C, D]);
Playground link
The brackets around the variants that will be generic formatted are for readability, they could be removed from macro definition

Resources