2016-08-13 39 views
6

在某些罕見的情況下,防止重複的宏參數可能有用。一個例子是這樣的elem(value, ...)宏來檢查是否value要麼是ABC是否有可能防止在Rust中的宏重複相同的參數?

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

有人可能會不小心越過同一參數多次,例如:

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

雖然這是有效的防鏽,這幾乎可以肯定是一個意外,並且極不可能是成爲開發者的意圖。這是一個微不足道的例子,實際的錯誤案例會更復雜。

有沒有辦法讓傳遞重複參數時編譯器警告/錯誤?

  • 參數不一定都是常量,它們也可以與變量混合使用。

  • 這是我在某些代碼中發現的一個實際的錯誤。雖然宏有限制/編譯器可以防止錯誤,但如果宏不允許,可能會提前發現它。應該在代碼審查中發現這些類型的錯誤,但會發生錯誤。

  • 可以將標識符轉換爲字符串,然後靜態斷言是否有任何標識符是完全匹配的一種方法(這不是傻瓜證明)。這具有明顯的缺點,即不同的標識符可以表示相同的常數值。也可以書寫相同的標識符以便不進行比較,例如:A[0]A[ 0 ]

  • 如果預處理器/編譯器無法輕鬆完成此任務,則回退解決方案可能是一些基本的靜態檢查工具。

  • 我設法爲do this with the C preprocessor

+0

如果我的回答不是你要找的,你能否發表評論來解釋爲什麼? – antoyo

回答

4

一種方式來實現你想要的是以下幾點:

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在編譯時捕獲的錯誤。

有了這些宏,則需要通過​​3210前綴每個文字:

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

+0

很好的答案,在實踐中經常會有一些標識符會像'a :: b.c'那樣限制一些實際的使用。我設法做一個宏,檢測這在C使用*(相當邪惡的預處理器strcmp - https://gitlab.com/ideasman42/blender/blob/7ea280cc6a5cb499c90b843651df14f97db29bcb/source/blender/blenlib/BLI_utildefines.h#L514)* – ideasman42

+0

你也可以通過編寫一個[編譯器插件](https://doc.rust-lang.org/stable/book/compiler-plugins.html)來創建一個過程宏,在那裏你將擁有更大的控制權並能夠實現你的目標。 – antoyo

相關問題