简体   繁体   中英

Is it possible to write chained comparison macro in Rust?

In rust, it's possible to pass > or <= etc inside macro arguments so long as the arguments are ident s.

Is it possible to create a macro that lets you chain comparison operators?

let x = 3;
let y = 1;
let z = -3;

assert_eq!(cond!(z <= x > y), true);

I think the following does what you expect as long as you are careful with the arguments to cond .

It uses tt (for argument operator0 ) to match < , <= , >= , etc. to avoid repeating lots of cases, but tt , of course, matches other tokens, too.

macro_rules! cond{
    ($x:ident $operator0:tt $x0:ident) => {
        ($x $operator0 $x0)
    };
    ($x:ident $operator0:tt $x0:ident $($operator1:tt $x1:ident)*) => {
        ($x $operator0 $x0) && cond!($x0 $($operator1 $x1)*)
    };
}

fn main() {
    let x = 3;
    let y = 1;
    let z = -3;

    assert_eq!(cond!(z <= x > y), true);
}

Yes you can. You need to use tt for the operator type:

macro_rules! cond {
    (@rec ($head:expr) $last:ident $op:tt $next:ident $($tail:tt)*) => {
        cond!(@rec (($head) && ($last $op $next)) $next $($tail)*)
    };
    (@rec ($head:expr) $last:ident) => { $head };
    ($first:ident $op:tt $next:ident $($tail:tt)*) => {
        cond!(@rec ($first $op $next) $next $($tail)*)
    }
}

fn main() {
    let x = 3;
    let y = 1;
    let z = -3;
    println!("(z <= x > y) = {}", cond!(z <= x > y));
}

Playground

You can also read The little book of Rust Macros for more advanced macros patterns.

I think it is technically possible, but I'm not sure I'd do it personally. to handle all the operators without matching on some things that shouldn't work like cond!(a + b) , cond!(a) or cond!() I had to be pretty verbose, and use a recursive macro. It might be possible to simplify the initial (non @recur ) cases, but I was worried that doing it wrong would lead to infinite recursion.

macro_rules! cond {
    ( @recur $x:ident ) => { true };
    ( @recur $x:ident < $y:ident $($tail:tt)* ) => { ($x < $y) && cond!( @recur $y $($tail)*) };
    ( @recur $x:ident > $y:ident $($tail:tt)* ) => { ($x > $y) && cond!( @recur $y $($tail)*) };
    ( @recur $x:ident <= $y:ident $($tail:tt)* ) => { ($x <= $y) && cond!( @recur $y $($tail)*) };
    ( @recur $x:ident >= $y:ident $($tail:tt)* ) => { ($x >= $y) && cond!( @recur $y $($tail)*) };
    ( @recur $x:ident == $y:ident $($tail:tt)* ) => { ($x == $y) && cond!( @recur $y $($tail)*) };
    ( $x:ident < $y:ident $($tail:tt)* ) => { ($x < $y) && cond!( @recur $y $($tail)*) };
    ( $x:ident > $y:ident $($tail:tt)* ) => { ($x > $y) && cond!( @recur $y $($tail)*) };
    ( $x:ident <= $y:ident $($tail:tt)* ) => { ($x <= $y) && cond!( @recur $y $($tail)*) };
    ( $x:ident >= $y:ident $($tail:tt)* ) => { ($x >= $y) && cond!( @recur $y $($tail)*) };
    ( $x:ident == $y:ident $($tail:tt)* ) => { ($x == $y) && cond!( @recur $y $($tail)*) };
}

fn main() {
    let x = 3;
    let y = 1;
    let z = -3;
    println!("(z <= x > y) = {}", cond!(z <= x > y));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM