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

标签 macros rust

在某些极少数情况下,防止宏的重复参数可能很有用。一个例子是这个 elem(value, ...)用于检查 value 是否为 ABC 的宏:

if (elem(value, A, B, C)) { .... }

有人可能会不小心多次传入同一个参数,例如:

if (elem(value, A, B, B)) { .... }

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

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

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

  • 这是我在某些代码中发现的实际错误。虽然有一个限制宏/编译器可以防止错误,但如果宏不允许,这可能已经被及早检测到。这些错误应该在代码审查中发现,但错误确实发生了。

  • 执行此操作的一种方法(并非万无一失)可能是将标识符转换为字符串,然后静态断言是否有任何标识符完全匹配。这有一个明显的缺点,即不同的标识符可能表示相同的常量值。同样的标识符也可以写成不进行比较,例如:A[0] vs A[0]

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

  • 我设法 do this with the C preprocessor .

最佳答案

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

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

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

您可以这样使用它:

if my_elem!(w, x, y, z) {
    println!("{}", w);
}

这是一个错误的例子:

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

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

如果你也想使用文字,你将需要一个具有不同语法的宏来区分文字和标识符:

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

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

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

使用这些宏,您需要在每个文字前加上 lit 前缀:

if elem!(w, x, lit 1, z) {
    println!("{}", w);
}

以下是一些错误示例:

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

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

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

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

关于macros - 是否可以防止 Rust 中的宏重复相同的参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38931479/

相关文章:

c++ - 仅使用宏在 header 中定义静态变量

Java 功能

string - 在接口(interface)上使用 Into<String> - Vec<Into<String>> 的成本为零?

parsing - 解析器表达式语法 - 如何匹配不包括单个字符的任何字符串?

rust - 如何使用对 FnOnce 闭包的引用?

rust - 为什么需要用户输入的代码在 Rust Playground 中不起作用?

c++ - 模板类的实例之间是否有共享范围?

macros - 如何用宏获取类方法的参数类型?

c - 在文本文件中使用整数运算扩展 C 预处理器宏?

rust - 为什么创建const指针的集合对 `for val in a.iter()`无效,而对 `a.iter().map(|val| val)`无效?