不是标识符的标识符

有两个标记,当你撞见时,很有可能最终认为它们是标识符,但实际上它们不是。然而正是这些标记,在某些情况下又的确是标识符。

第一个是self。毫无疑问,它是一个关键词。在一般的Rust代码中,不可能出现把它解读成标识符的情况;但在宏中这种情况则有可能发生:

  1. macro_rules! what_is {
  2. (self) => {"the keyword `self`"};
  3. ($i:ident) => {concat!("the identifier `", stringify!($i), "`")};
  4. }
  5. macro_rules! call_with_ident {
  6. ($c:ident($i:ident)) => {$c!($i)};
  7. }
  8. fn main() {
  9. println!("{}", what_is!(self));
  10. println!("{}", call_with_ident!(what_is(self)));
  11. }

上述代码的输出将是:

  1. the keyword `self`
  2. the keyword `self`

但这没有任何道理!call_with_ident!要求一个标识符,而且它的确匹配到了,还成功替换了!所以,self同时是一个关键词,但又不是。你可能会想,好吧,但这鬼东西哪里重要呢?看看这个:

  1. macro_rules! make_mutable {
  2. ($i:ident) => {let mut $i = $i;};
  3. }
  4. struct Dummy(i32);
  5. impl Dummy {
  6. fn double(self) -> Dummy {
  7. make_mutable!(self);
  8. self.0 *= 2;
  9. self
  10. }
  11. }
  12. #
  13. # fn main() {
  14. # println!("{:?}", Dummy(4).double().0);
  15. # }

编译它会失败,并报错:

  1. <anon>:2:28: 2:30 error: expected identifier, found keyword `self`
  2. <anon>:2 ($i:ident) => {let mut $i = $i;};
  3. ^~

所以说,宏在匹配的时候,会欣然把self当作标识符接受,进而允许你把self带到那些实际上没办法使用的情况中去。但是,也成吧,既然得同时记住self既是关键词又是标识符,那下面这个讲道理应该可行,对吧?

  1. macro_rules! make_self_mutable {
  2. ($i:ident) => {let mut $i = self;};
  3. }
  4. struct Dummy(i32);
  5. impl Dummy {
  6. fn double(self) -> Dummy {
  7. make_self_mutable!(mut_self);
  8. mut_self.0 *= 2;
  9. mut_self
  10. }
  11. }
  12. #
  13. # fn main() {
  14. # println!("{:?}", Dummy(4).double().0);
  15. # }

实际上也不行,编译错误变成:

  1. <anon>:2:33: 2:37 error: `self` is not available in a static method. Maybe a `self` argument is missing? [E0424]
  2. <anon>:2 ($i:ident) => {let mut $i = self;};
  3. ^~~~

这同样也没有任何道理。它明明不在静态方法里。这简直就像是在抱怨说,它看见的两个self不是同一个self… 就搞得像关键词self也有卫生性一样,类似…标识符。

  1. macro_rules! double_method {
  2. ($body:expr) => {
  3. fn double(mut self) -> Dummy {
  4. $body
  5. }
  6. };
  7. }
  8. struct Dummy(i32);
  9. impl Dummy {
  10. double_method! {{
  11. self.0 *= 2;
  12. self
  13. }}
  14. }
  15. #
  16. # fn main() {
  17. # println!("{:?}", Dummy(4).double().0);
  18. # }

还是报同样的错。那这个如何:

  1. macro_rules! double_method {
  2. ($self_:ident, $body:expr) => {
  3. fn double(mut $self_) -> Dummy {
  4. $body
  5. }
  6. };
  7. }
  8. struct Dummy(i32);
  9. impl Dummy {
  10. double_method! {self, {
  11. self.0 *= 2;
  12. self
  13. }}
  14. }
  15. #
  16. # fn main() {
  17. # println!("{:?}", Dummy(4).double().0);
  18. # }

终于管用了。所以说,self是关键词,但当它想的时候,它同时也能是一个标识符。那么,相同的道理对类似的其它东西有用吗?

  1. macro_rules! double_method {
  2. ($self_:ident, $body:expr) => {
  3. fn double($self_) -> Dummy {
  4. $body
  5. }
  6. };
  7. }
  8. struct Dummy(i32);
  9. impl Dummy {
  10. double_method! {_, 0}
  11. }
  12. #
  13. # fn main() {
  14. # println!("{:?}", Dummy(4).double().0);
  15. # }
  1. <anon>:12:21: 12:22 error: expected ident, found _
  2. <anon>:12 double_method! {_, 0}
  3. ^

哈,当然不行。 _是一个关键词,在模式以及表达式中有效,但不知为何,并不像self,它并不是一个标识符;即便它——如同self——从定义上讲符合标识符的特性。

你可能觉得,既然_在模式中有效,那换成$self_:pat是不是就能一石二鸟了呢?可惜了,也不行,因为self不是一个有效的模式。真棒。

如果你真想同时匹配这两个标记,仅有的办法是换用tt来匹配。