gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-29 07:48:33 28 4
gpt4 key购买 nike

在某些极少数情况下,防止宏的重复参数可能很有用。一个例子是这个 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/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com