2016-08-13 4 views
6

マクロへの重複した引数を防ぐのに便利な場合があります。これが有効な錆Rustのマクロと同じ引数が重複するのを防ぐことはできますか?

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

ですが:

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

誰かが誤っ例えば、同じ引数を複数回に渡すことができます。一例としては、valueはどちらかABまたはCであれば、このマクロelem(value, ...)をチェックすることですそれはほぼ確実に事故であり、の可能性はほとんどありませんは、開発者が意図したものであると考えられます。これは簡単な例ですが、実際のエラーケースはもっと複雑になります。

重複する引数を渡すときにコンパイラの警告/エラーを表示する方法はありますか?

  • 引数が必ずしもすべての定数であるとは限りません。引数も変数と混在する可能性があります。

  • これは一部のコードで見つかった実際のバグです。マクロ/コンパイラが間違いを防ぐための制限がありますが、マクロが許可していない場合にはこれが早期に検出されている可能性があります。この種の間違いはコードレビューで見つけられるべきですが、間違いが起こります。

  • これを行う1つの方法は、識別子を文字列に変換してから、いずれかの識別子が完全一致であれば静的にアサートすることです。これは、異なる識別子が同じ定数値を表す可能性があるという明らかな欠点を有する。例えば、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'のようなものになります。 https://gitlab.com/ideasman42/blender/blob/7ea280cc6a5cb499c90b843651df14f97db29bcb/source/blender/blenlib/BLI_utildefines.h#L514)* – ideasman42

+0

あなた - 私は*(かなり悪プリプロセッサのstrcmpを使用してCでこれを検出したマクロを作るために管理しましたあなたがより大きなコントロールを持ってあなたの目標を達成できるようになる[コンパイラプラグイン](https://doc.rust-lang.org/stable/book/compiler-plugins.html)を書いて手続き型マクロを作成することもできます。 – antoyo

関連する問題