在某些罕见的情况下,防止宏的重复参数可能很有用.一个例子是这个elem(value, ...)宏来检查是否value是A,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预处理器完成了这个.
实现您想要的目标的一种方法如下:
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.