What Is Rust Doing Behind the Curtains?

Matthias Endler

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

原文は Matthias Endler により に公開されました。 このブログを購読する

Rustは多くの糖衣構文を備えており、書くことを楽しくしてくれます。しかし、カーテンの裏側を覗き込み、コンパイラが私たちのコードを実際にどう処理しているのかを見るのは、時に困難です。

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

概要から引用します:

Rustコンパイラは、ユーザーの生活を楽にする数多くの便宜を提供しています。これらが何であるかを知っておくことは、内部で何が起きているのかに惑わされないために重要です……世界について魔法のように考えることが少なければ少ないほど良いのです。

彼は続けて、こうした便宜の例をいくつか挙げています:

  • ライフタイム省略
  • 型推論
  • 糖衣構文
  • 暗黙のデリファレンス
  • 型強制
  • 隠されたコード(例:prelude

講演で彼がこれらの便宜がある場合とない場合のコードを比較してみせる様子は、とても教育的で面白いものでした。

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

Redditで、脱糖衣化された出力を生成するためのコンパイラフラグについてのディスカッションを見つけました。(ここではrustupを使って、+nightlyフラグでnightlyコンパイラを呼び出していることに注意してください。)

rustc +nightly -Zunpretty=hir example.rs

HIRはhigh-level intermediate representation(高水準中間表現)の略です。これは基本的に、コンパイラが扱いやすいように調整された抽象構文木(AST)です。糖衣構文を、後続のコンパイルステップで扱いやすい基本的な構成要素に置き換えます。詳しく知りたい方は、Nico Matsakis氏による詳細な解説をご覧ください。

rustcのコンパイル図。HIRは最初のステップ。

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

rustfmtをかけてみたところ、驚くほどうまく機能しました。この小さな成功に励まされ、これをcargoのサブコマンドにまとめてcargo-inspectと名付けました。

実際のコードでcargo-inspectを試してみましょう!

例 — 範囲式の脱糖衣化

以下の例は、プロジェクトのexamplesフォルダでも確認できます。

入力:

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

cargo-inspectの出力:

範囲式の出力

きれいにフォーマットされたターミナルの出力です。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フォルダにコントリビュートしてください。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント