Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

% 衛生性

Rust のマクロは部分的に衛生的です。具体的には、ほとんどの識別子については衛生的ですが、ジェネリック型パラメーターやライフタイムについてはそうではありません

衛生性は、すべての識別子に不可視の「構文コンテキスト」値を付与することで機能します。2 つの識別子を比較する際には、その 2 つが等しいと見なされるために、識別子のテキスト上の名前と構文コンテキストの両方が同一でなければなりません。

これを説明するために、次のコードを考えてみましょう。

macro_rules! using_a {
    ($e:expr) => {
        {
            let a = 42;
            $e
        }
    }
}

let four = using_a!(a / 10);

背景色を使って構文コンテキストを表します。では、マクロ呼び出しを展開してみましょう。

let four = {
    let a = 42;
    a / 10
};

まず、macro_rules! 呼び出しは展開中に事実上消えることを思い出してください。

次に、このコードをコンパイルしようとすると、コンパイラーはおおよそ次のような内容で応答します。

<anon>:11:21: 11:22 error: unresolved name `a`
<anon>:11 let four = using_a!(a / 10);

展開されたマクロの背景色(すなわち構文コンテキスト)は、展開の一部として変化することに注意してください。各マクロ展開には、その内容に対して新しい一意の構文コンテキストが与えられます。その結果、展開後のコードには2 つの異なる a が存在します。一方は最初の構文コンテキストにあり、もう一方は別の構文コンテキストにあります。言い換えると、aa と同じ識別子ではありません。見た目がどれほど似ていてもです。

とはいえ、展開後の出力に代入されたトークンは、元の構文コンテキストを保持します(マクロ自体の一部であったのではなく、マクロに提供されたものであるためです)。したがって、解決策は次のようにマクロを修正することです。

macro_rules! using_a {
    ($a:ident, $e:expr) => {
        {
            let $a = 42;
            $e
        }
    }
}

let four = using_a!(a, a / 10);

これは展開すると次のようになります。

let four = {
    let a = 42;
    a / 10
};

使用されている a が 1 つだけであるため、コンパイラーはこのコードを受け入れます。