是否可以防止Rust中的宏重复相同的参数?

ide*_*n42 6 macros rust

在某些罕见的情况下,防止宏的重复参数可能很有用.一个例子是这个elem(value, ...)宏来检查是否valueA,B或者C:

if (elem(value, A, B, C)) { .... }
Run Code Online (Sandbox Code Playgroud)

有人可能会意外地多次传递相同的参数,例如:

if (elem(value, A, B, B)) { .... }
Run Code Online (Sandbox Code Playgroud)

虽然这是有效的Rust,但它几乎肯定是一个意外,并且极不可能成为开发人员的意图.这是一个简单的例子,实际的错误情况会更复杂.

传递重复参数时有没有办法让编译器发出警告/错误?

  • 参数不一定都是常量,它们也可以与变量混合.

  • 这是我在一些代码中发现的实际错误.虽然有一个限制宏/编译器可以防止错误,但如果宏不允许,可能会提前检测到.这些错误应该在代码审查中找到,但是会发生错误.

  • 一种方法(不是万无一失)可以将标识符转换为字符串,然后如果任何标识符是完全匹配则静态断言.这具有明显的缺点,即不同的标识符可以表示相同的常数值.也可以写入相同的标识符以便不进行比较,例如:A[0]vs A[ 0 ].

  • 如果预处理器/编译器不能轻易地做到这一点,那么后退解决方案可能是一些基本的静态检查工具.

  • 我设法用C预处理器完成了这个.

ant*_*oyo 5

实现您想要的目标的一种方法如下:

macro_rules! unique_args {
    ($($idents:ident),*) => {
        {
            #[allow(dead_code, non_camel_case_types)]
            enum Idents { $($idents,)* __CountIdentsLast }
        }
    };
}

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

macro_rules! my_elem {
    ($($tt:tt)*) => {{
        unique_args!($($tt)*);
        _my_elem!($($tt)*)
    }};
}
Run Code Online (Sandbox Code Playgroud)

这个想法是,两次使用相同的标识符将导致编译器错误,因为枚举不能具有重复的变体名称。

您可以这样使用它:

if my_elem!(w, x, y, z) {
    println!("{}", w);
}
Run Code Online (Sandbox Code Playgroud)

这是一个有错误的示例:

// error[E0428]: a value named `y` has already been defined in this enum
if my_elem!(w, x, y, y) {
    println!("{}", w);
}
Run Code Online (Sandbox Code Playgroud)

但是,这仅适用于标识符。

如果您也想使用文字,则需要一个具有不同语法的宏,以便能够区分文字和标识符:

macro_rules! unique_idents {
    () => {
    };
    ($tt:tt) => {
    };
    ($ident1:ident, $ident2:ident) => {
        {
            #[allow(dead_code, non_camel_case_types)]
            enum Idents {
                $ident1,
                $ident2,
            }
        }
    };
    ($ident:ident, lit $expr:expr) => {
    };
    ($ident1:ident, $ident2:ident, $($tt:tt)*) => {
        {
            #[allow(dead_code, non_camel_case_types)]
            enum Idents {
                $ident1,
                $ident2,
            }
            unique_idents!($ident1, $($tt)*);
            unique_idents!($ident2, $($tt)*);
        }
    };
    ($ident:ident, lit $expr:expr, $($tt:tt)*) => {
        unique_idents!($ident, $($tt)*);
    };
    (lit $expr:expr, $($tt:tt)*) => {
        unique_idents!($($tt)*);
    };
}

macro_rules! unique_literals {
    () => {
    };
    ($tt:tt) => {
    };
    (lit $lit1:expr, lit $lit2:expr) => {{
            type ArrayForStaticAssert_ = [i8; 0 - (($lit1 == $lit2) as usize)];
    }};
    (lit $lit:expr, $ident:ident) => {
    };
    (lit $lit1:expr, lit $lit2:ident, $($tt:tt)*) => {{
            unique_literals!(lit $lit1, lit $lit2);
            unique_literals!(lit $lit1, $($tt)*);
            unique_literals!(lit $lit2, $($tt)*);
    }};
    (lit $lit:expr, $ident:ident, $($tt:tt)*) => {
        unique_literals!(lit $lit, $($tt)*);
    };
    ($ident:ident, $($tt:tt)*) => {
        unique_literals!($($tt)*);
    };
}

macro_rules! unique_args2 {
    ($($tt:tt)*) => {{
        unique_idents!($($tt)*);
        unique_literals!($($tt)*);
    }};
}

macro_rules! _elem {
    () => {
        false
    };
    ($val:expr) => {
        false
    };
    ($val1:expr, $val2:expr) => {{
        $val1 == $val2
    }};
    ($val1:expr, lit $val2:expr) => {{
        $val1 == $val2
    }};
    ($val1:expr, $val2:expr, $($tt:tt)*) => {{
        $val1 == $val2 || _elem!($val1, $($tt)*)
    }};
    ($val1:expr, lit $val2:expr, $($tt:tt)*) => {{
        $val1 == $val2 || _elem!($val1, $($tt)*)
    }};
}

macro_rules! elem {
    ($($tt:tt)*) => {{
        unique_args2!($($tt)*);
        _elem!($($tt)*)
    }};
}
Run Code Online (Sandbox Code Playgroud)

uniq_idents!宏使用与上面相同的技巧。

unique_literals!宏将导致subtract with overflow编译时捕获的错误。

使用这些宏,您需要为每个文字添加前缀lit

if elem!(w, x, lit 1, z) {
    println!("{}", w);
}
Run Code Online (Sandbox Code Playgroud)

以下是一些错误示例:

// error[E0428]: a value named `y` has already been defined in this enum
if elem!(w, x, y, y) {
    println!("{}", w);
}

// error[E0080]: constant evaluation error
if elem!(w, x, lit 1, z, lit 1) {
    println!("{}", w);
}
Run Code Online (Sandbox Code Playgroud)

我认为这是我们在不使用编译器插件的情况下能做的最好的事情。

可以改进这些宏,但您已经明白了。

尽管有一个stringify!宏可以用来将任何表达式转换为字符串,但我认为我们目前没有办法在编译时比较这些字符串(没有编译器插件),至少在我们拥有const fn.