Rust でキーワード(予約語)を識別子として使う方法 (raw identifier)と、その例外

3 min read読了の目安(約3300字

小ネタです。

厳格なキーワードと予約されたキーワード

Rust では、以下のようなワードが「キーワード」とされています[1]。通常、関数名、変数名、型パラメータ名などにはプログラマが任意に名前をつけることができますが、キーワードに関してはこれらの場所で名前として使うことができません。

厳格なキーワード (strict keywords)

  • as
  • const
  • if
  • for
  • trait
  • use
  • super
  • など……
const FOO: &str = "foo"; // OK!
let bar = 42; // OK!

let const = 42; // だめ!

変数名が const だと直感的に嫌な気分になります(パーサーはもっと嫌な気持ちになっていそう)。このような名前をつけることができないのは理にかなっています。

予約されたキーワード (reserved keywords)

  • abstract
  • box
  • do
  • yield
  • typeof
  • など……

予約されたキーワード、予約語はさきほどのグループとは少し毛色が違います。さきほどのグループに属しているワード、例えば for は、当然ながら Rust のプログラムにおいて構文的に大きな意味をもっています。一方、予約語の abstract などは現在の Rust で文法的に何か意味をもっているわけではありません。しかし、将来言語を拡張していく上で、このワードを使った新機能を実装するかもしれないから、ユーザーが自由にこの名前を使うことができないように仮押さえをしているということになります。

例えば、box が予約語となっているのは、box_syntax という新しい構文が導入される可能性があるためです。Nightly Rust であれば #![feature(box_syntax)] をつけることで box_syntax を利用することができます。

#![feature(box_syntax)]

fn main() {
    // let b = Box::new(5); と同じ
    let b = box 5;
    
    let box = 42; // だめ!
}

キーワードを名前として使うためのテクニック raw identifier

キーワードは変数名などとして使えない!ということを見てきましたが、これだと不便なケースが稀によくあります。
例えば、何かの移動に関するモジュールを move.rs というファイルに書いていたとします。このファイルをクレートのルートファイル(main.rs とします)で読み込む場合、以下のように書きたくなります。

main.rs
mod move;

fn main() {
    // なんらかのコード
}

これはだめです。move がキーワードであるため、mod ○○ の○○の部分に使うことはできません。

すぐに思いつく解決策は move.rs を違う名前に変えることですが、いろいろな事情がありこのファイル名を変更することはできないとしましょう。
こういうときに救世主となるのが raw identifier という機能です。名前の前に r# をつけることで、キーワードであっても大丈夫なようになります。つまり、

main.rs
mod r#move;

のように書けばエラーは出なくなります。一件落着です。

raw identifier をもってしても使用を禁じられるキーワードたち

なんと、raw identifier を使ってもなお使用することが許されないキーワードがあります。

  • super
  • self
  • Self
  • extern
  • crate
  • _
// だめ! r# をつけたとしても super を名前として使うことはできない
fn r#super() {
    // なんらかのコード
}

理由はこちらに書かれています:

https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094

「raw identifier なのか否か?」という情報は、トークンがパースされ、名前解決をされる段階では失われてしまっていて、例えば r#Self と書いた場合、名前解決時にはすでに通常の Self と完全に同一状態になってしまっているので、これらのワードを raw identifier として扱うとコンパイルがうまくいかなくなってしまうようです。

このような事情により、どうしてもこれらのキーワードを関数名、変数名、構造体のフィールド名、などに使いたい場合には、別の名前を考える必要があります。
一例として、Rust の公式フォーマッタである rustfmt では、以下のような enum が登場します

src/formatting/imports.rs
pub(crate) enum UseSegment {
    Ident(String, Option<String>),
    Slf(Option<String>),
    Super(Option<String>),
    Crate(Option<String>),
    Glob,
    List(Vec<UseTree>),
}

この記事をここまで読んできた皆様なら察しがつくと思いますが、SlfSelf が使えないがための代替策です。

以下、初めてこのコードを読んだとき、この Slf が何を指すのか分からず数十分を溶かしたときの僕の様子です。

https://twitter.com/yusuktan/status/1355431741431848966

参考文献

脚注
  1. 厳密には weak keywords というのもあって、これは変数名などに使うことができるのですが、記事中では省略しています ↩︎

この記事に贈られたバッジ