% 識別子ではない識別子
いずれ遭遇する可能性が高い、識別子のように見えるものの、実際にはそうではないトークンが2つあります。ただし、識別子である場合は別です。
1つ目は self です。これは間違いなくキーワードです。しかし、識別子の定義にもたまたま適合します。通常の Rust コードでは、self が識別子として解釈されることはありませんが、マクロでは起こり得ます:
macro_rules! what_is { (self) => {"the keyword `self`"}; ($i:ident) => {concat!("the identifier `", stringify!($i), "`")}; } macro_rules! call_with_ident { ($c:ident($i:ident)) => {$c!($i)}; } fn main() { println!("{}", what_is!(self)); println!("{}", call_with_ident!(what_is(self))); }
上記は次を出力します:
the keyword `self`
the keyword `self`
しかし、これは筋が通りません。call_with_ident! は識別子を要求し、それにマッチして、それを置換したのです! つまり、self はキーワードであり、同時にキーワードではないということになります。これがいったいどのように重要なのか疑問に思うかもしれません。次の例を見てください:
macro_rules! make_mutable {
($i:ident) => {let mut $i = $i;};
}
struct Dummy(i32);
impl Dummy {
fn double(self) -> Dummy {
make_mutable!(self);
self.0 *= 2;
self
}
}
#
# fn main() {
# println!("{:?}", Dummy(4).double().0);
# }
これは次のエラーでコンパイルに失敗します:
<anon>:2:28: 2:30 error: expected identifier, found keyword `self`
<anon>:2 ($i:ident) => {let mut $i = $i;};
^~
つまり、このマクロは self を識別子として問題なくマッチし、実際には使用できない場所で使うことを許してしまいます。とはいえ、まあよいでしょう。self は識別子であるときでさえ、どういうわけかそれがキーワードであることを覚えているわけです。なら、これもできるはずですよね?
macro_rules! make_self_mutable {
($i:ident) => {let mut $i = self;};
}
struct Dummy(i32);
impl Dummy {
fn double(self) -> Dummy {
make_self_mutable!(mut_self);
mut_self.0 *= 2;
mut_self
}
}
#
# fn main() {
# println!("{:?}", Dummy(4).double().0);
# }
これは次のエラーで失敗します:
<anon>:2:33: 2:37 error: `self` is not available in a static method. Maybe a `self` argument is missing? [E0424]
<anon>:2 ($i:ident) => {let mut $i = self;};
^~~~
これもやはり筋が通りません。これは static メソッドの中ではありません。使おうとしている self が同じ self ではない、と文句を言っているかのようです……まるで self キーワードが、識別子のように衛生性を持っているかのように。
macro_rules! double_method {
($body:expr) => {
fn double(mut self) -> Dummy {
$body
}
};
}
struct Dummy(i32);
impl Dummy {
double_method! {{
self.0 *= 2;
self
}}
}
#
# fn main() {
# println!("{:?}", Dummy(4).double().0);
# }
同じエラーです。では、これはどうでしょうか……
macro_rules! double_method { ($self_:ident, $body:expr) => { fn double(mut $self_) -> Dummy { $body } }; } struct Dummy(i32); impl Dummy { double_method! {self, { self.0 *= 2; self }} } fn main() { println!("{:?}", Dummy(4).double().0); }
ついに、これは動作します。つまり、self は都合のよいときにはキーワードかつ識別子なのです。きっと、これは他の似た構文にも使えるはずですよね?
macro_rules! double_method {
($self_:ident, $body:expr) => {
fn double($self_) -> Dummy {
$body
}
};
}
struct Dummy(i32);
impl Dummy {
double_method! {_, 0}
}
#
# fn main() {
# println!("{:?}", Dummy(4).double().0);
# }
<anon>:12:21: 12:22 error: expected ident, found _
<anon>:12 double_method! {_, 0}
^
いいえ、もちろん違います。_ はパターンや式で有効なキーワードですが、どういうわけかキーワード self のような識別子ではありません。識別子の定義にはまったく同じようにマッチするにもかかわらずです。
代わりに $self_:pat を使えばこれを回避できると思うかもしれません。そうすれば _ はマッチします! ところが、そうはいきません。なぜなら self はパターンではないからです。なんとも楽しいですね。
この回避策は、(これらのトークンの何らかの組み合わせを受け付けたい場合には)代わりに tt マッチャーを使うことだけです。