Rust中是否可能编写链式比较宏?

4
在Rust中,只要参数是标识符(ident),就可以在宏参数中传递><=等内容。
是否可能创建一个宏来让您链接比较运算符?
let x = 3;
let y = 1;
let z = -3;

assert_eq!(cond!(z <= x > y), true);
3个回答

4

可以的。你需要使用tt来表示运算符类型:

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(游乐场)

您也可以阅读《Rust宏小书》了解更高级的宏模式。


这确实允许一些奇怪的结构,可能会有问题,比如 cond!(x+y)。但它确实很简短、整洁。 - Michael Anderson

2

我认为从技术上讲是可能的,但个人不确定是否要这样做。为了处理所有运算符,而又不匹配一些不应该工作的东西,如cond!(a + b)cond!(a)cond!(),我必须非常详细,并使用递归宏。可能可以简化初始(非 @recur)情况,但我担心做错会导致无限递归。

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


也许你可以修改你的示例来支持变量比较运算符。 - CoronA

2
我认为以下内容符合您的期望,只要您在cond的参数上小心处理就可以了。
它使用tt(对于参数operator0)匹配<<=>=等,以避免重复许多情况,但是tt当然也会匹配其他标记。
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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接