0

In C, I'm used to having:

if (ELEM(value, a, b, c)) { ... }

which is a macro with a variable number of arguments to avoid typing out

if (value == a || value == b || value == c) { ... }

A C example can be seen in Varargs `ELEM` macro for use with C.

Is this possible in Rust? I assume it would use match. If so, how would variadic arguments be used to achieve this?

Community
  • 1
  • 1
ideasman42
  • 42,413
  • 44
  • 197
  • 320

4 Answers4

5
macro_rules! cmp {
    // Hack for Rust v1.11 and prior.
    (@as_expr $e:expr) => { $e };

    ($lhs:expr, $cmp:tt any $($rhss:expr),*) => {
        // We do this to bind `$lhs` to a name so we don't evaluate it multiple
        // times.  Use a leading underscore to avoid an unused variable warning
        // in the degenerate case of no `rhs`s.
        match $lhs { _lhs => {
            false || $(
                cmp!(@as_expr _lhs $cmp $rhss)
            ) || *
        //    ^- this is used as a *separator* between terms
        }}
    };

    // Same, but for "all".
    ($lhs:expr, $cmp:tt all $($rhss:expr),*) => {
        match $lhs { _lhs => {
            true && $( cmp!(@as_expr _lhs $cmp $rhss) ) && *
        }}
    };
}

fn main() {
    let value = 2;
    if cmp!(value, == any 1, 2, 3) {
        println!("true! value: {:?}", value);
    }
    if cmp!(value*2, != all 5, 7, 1<<7 - 1) {
        println!("true! value: {:?}", value);
    }
}
DK.
  • 55,277
  • 5
  • 189
  • 162
  • *Rust v1.11 and prior* — does that mean that ugly wart has finally been fixed? – Shepmaster Aug 08 '16 at 12:52
  • 1
    @Shepmaster *Apparently.* Haven't had an opportunity to test it thoroughly, myself, but looks like the reparse trick might be on the way out. – DK. Aug 08 '16 at 12:53
2

First off, if your a, b, and c are concrete values, you can just use match:

fn main() {
    let x = 42;

    match x {
        1 | 2 | 3 => println!("foo"),
        42 => println!("bar"),
        _ => println!("nope"),
    }
}

If you want to match on variables you need to write the match arms like this:

match x {
    x if x == a || x == b || x == c => println!("foo"),
    42 => println!("bar"),
    _ => println!("nope"),
}

…which is basically what you want to avoid.

But: A pretty direct translation of your C macro is also possible!

macro_rules! elem {
    ($val:expr, $($var:expr),*) => {
        $($val == $var)||*
    }
}

fn main() {
    let y = 42;
    let x = 42;

    if elem!(x, 1, 3, y) {
        println!("{}", x);
    }
}
Pascal
  • 2,709
  • 1
  • 19
  • 17
1

I'm partial to writing this without a macro, taking advantage of contains on arrays.

fn main() {
    if [1, 2, 3, 4].contains(&4) {
        println!("OK");
    }
}

It's hard to predict what will happen to this when optimized, but if absolute performance is a goal you'd do well to benchmark each approach.

tari
  • 658
  • 4
  • 14
  • Not as good as benchmarking your actual code, but here is a paper (from 2007) on how LLVM optimizes/lowers switch statements: http://llvm.org/pubs/2007-05-31-Switch-Lowering.pdf – Pascal Aug 08 '16 at 14:22
0

Yes this is possible, the following macro expands to do each check.

macro_rules! elem {
    ($n:expr, $( $hs:expr ),*) => ($( $n == $hs )||* );
}

fn main() {
    if elem!(4, 1, 2, 3, 4) {
        println!("OK");
    }
}

Thanks to @vfs on #rust in IRC.

ideasman42
  • 42,413
  • 44
  • 197
  • 320