What Is Rust Doing Behind the Curtains?

Matthias Endler

Rustは舞台裏で何をしているのか?

Rustには多くのシンタックスシュガーが用意されていて、コードを書くのが楽しくなります。しかし、舞台裏をのぞいて、コンパイラが実際にコードに対して何をしているのかを確認するのは、難しいことがあります。

Rust Belt Rust 2018で、Tshepang LekhonkhobeによるSyntax conveniences afforded by the compiler録画はこちら)という講演を見ました。

概要から引用します。

Rustコンパイラは、ユーザーの生活を楽にするさまざまな便利機能を提供しています。内部で何が起きているのかに戸惑わないためにも、それらが何なのかを知っておくとよいでしょう。世界を魔法のように考えることは、少ないほどよいのです。

講演では、こうした便利機能の例がいくつか紹介されました。

  • ライフタイムの省略
  • 型推論
  • シンタックスシュガー
  • 暗黙のデリファレンス
  • 型強制
  • 隠れたコード(例:prelude

講演中、こうした便利機能があるコードとないコードを比較して見せてくれたのは、とても勉強になりましたし、楽しいものでした。

家に帰ると、もっと詳しく知りたくなりました。Rustが舞台裏で何をしているのかを明らかにしてくれるツールはないだろうか、と考えたのです。

Redditで、デシュガー後の出力を生成するコンパイラフラグについての議論を見つけました。(ここでは、+nightlyフラグでnightlyコンパイラを起動するためにrustupを使っています。)

rustc +nightly -Zunpretty=hir example.rs

HIRはhigh-level intermediate representation(高レベル中間表現)の略です。これは基本的には抽象構文木(AST)ですが、コンパイラで使いやすいように作られています。シンタックスシュガーを、後続のコンパイル処理で扱いやすい基本的な構成要素に置き換えます。詳しくは、Nico Matsakisによる詳細な解説を読んでください。

Rustcのコンパイル図。HIRは最初の段階です。

ともあれ、出力は驚くほど読みやすいものでした(下記参照)。シンタックスハイライトと整形を加えれば、かなり便利なツールになりそうです。

試しにrustfmtを適用してみると、驚くほどうまくいきました。この小さな成功に背中を押されて、cargoのサブコマンドとしてまとめ、cargo-inspectと名付けました。

それでは、実際のコードでcargo-inspectを試してみましょう。

例 - range式のデシュガー

以下の例は、プロジェクトのexamplesフォルダーにもあります。

入力:

for n in 1..3 {
    println!("{}", n);
}

cargo-inspectの出力:

rangeの出力

これが、きれいに整形されたターミナル出力です。行番号と色が付いているのは、prettyprintのおかげです。これはbatをベースにしたライブラリです。もしかすると読みにくいかもしれないので、要点を示します。

match ::std::iter::IntoIterator::into_iter(
        ::std::ops::Range { start: 1, end: 3 })
  mut iter => loop {
    // ...
  },
};

1..3std::ops::Range { start: 1, end: 3 }に変換されていることがわかります。コンパイラのバックエンドにとって、これらは完全に同じものです。したがって、次が成り立ちます。

assert_eq!((1..3), std::ops::Range { start: 1, end: 3 });

例 - ファイル処理

入力:

use std::fs::File;
use std::io::Error;

fn main() -> Result<(), Error> {
    let file = File::open("file.txt")?;
    Ok(())
}

出力:

#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
use std::fs::File;
use std::io::Error;

fn main() -> Result<(), Error> {
  let file = match ::std::ops::Try::into_result(
      <File>::open("file.txt")) {
    ::std::result::Result::Err(err) =>
    #[allow(unreachable_code)]
    {
      #[allow(unreachable_code)]
      return ::std::ops::Try::from_error(
          ::std::convert::From::from(err))
    }
    ::std::result::Result::Ok(val) =>
    #[allow(unreachable_code)]
    {
      #[allow(unreachable_code)]
      val
    }
  };
  Ok(())
}

?演算子が、File::openResultに対するmatchへとデシュガーされていることがわかります。エラーの場合は、std::convert::From::fromを適用してエラー型を変換します。それ以外の場合は、Okの値をそのまま返します。

講演

ベルギーで開催されたFOSDEMでは、このプロジェクトについて詳しく話す機会がありました。録画はこちらです。

今後の作業

ここでコンパイラを書き直すつもりはありません。rustcは、私などよりはるかに優れた仕事をしています。これらの機能はすべて以前から存在していました。私がしているのは、私のような学習者にとってコンパイラをより親しみやすいものにしようとすることだけです。

現時点では、このツールはかなり壊れやすいものです。問題が起きると、見栄えの悪いエラーメッセージを出します。小さく独立したコード例で実行したときに、最も力を発揮します。

参加しましょう!

Githubでは、他の人にも参加してもらえるよう、いくつかissueを立てました。具体的には、次のようなオプションがあればよいと思っています。

それから、特に面白いコード例を見つけたら、遠慮せずにexamplesフォルダーへ追加してください。

原文は Matthias Endler により に公開されました。

この記事は「gpt-5.6-luna」を使用して翻訳されました。