【禁帯出】stable Rustからunstable featuresを使う
とても素敵な六月だったので初投稿です。
はじめに、あるコードとその実行結果を紹介します。
use nightly_crimes::nightly_crimes;
nightly_crimes! {
#![feature(never_type)]
#![feature(box_syntax)]
fn hey(x: Result<&str, !>) -> Box<String> {
match x {
Ok(x) => box x.to_string(),
Err(x) => x,
}
}
}
fn main() {
println!("{}", hey(Ok("success!")));
}
$ cargo +stable run
Compiling yolo-rustc-bootstrap v1.0.2
Compiling nightly-crimes v1.0.1
Compiling sand v0.1.0 (/home/manami/ws/pgr)
Finished dev [unoptimized + debuginfo] target(s) in 2.02s
Running `target/debug/pgr`
success!
おや、何かがおかしいですね?
Stability on Rust
Rustは後方互換性に特に気を使う言語であり、stable releaseでは破壊的変更をほとんど導入しません[1]。
開発途中で仕様が変わる可能性がある(i.e. 不安定な)言語機能やstd APIなどについてはnightly releaseでのみ利用でき、feature gateと呼ばれる仕組みを使って明示的に有効化してあげる必要があります。
これはソースファイルの先頭に#![feature(feature_name)]
と書いてあげることで有効化できます。
……お気付きになりましたか?
冒頭の例では、説明に反してstable Rustでbox_syntax
やnever_type
といった不安定機能を利用できてしまっています。
コードを見る限り、どうもnightly_crimes!
というマクロが一枚噛んでいるようです。
Hacks to use unstable features on stable Rust
今回はnightly_crimes
v1.0.1のソースコードを見ていきます。こちらから閲覧可能です。
リポジトリはnightly_crimes
crate自身とそれが依存するyolo-rustc-bootstrap
crateで構成されています。
まずは前者から見ていくことにしましょう。
nightly_crimes
ソースコードはこちらから閲覧できます。
3行目でyolo_rustc_bootstrap::do_crimes!()
を呼び出していますが、これは後から見ていきましょう。
一部を引用します。
#[cfg_attr(yolo_rustc_bootstrap, allow_internal_unstable(allow_internal_unstable))]
macro_rules! nightly_crimes {
(
#![feature($($feature:ident),* $(,)?)]
$($code:tt)*
) => (
#[allow_internal_unstable($($feature,)*)]
macro_rules! horrible_crimes { () => ( $($code)* ); }
horrible_crimes! {}
);
}
allow_internal_unstable
はstd/core/allocライブラリやコンパイラ内部にあるマクロをstable Rustでも動くようにするattributeです。例えばお馴染みのassert!
マクロはcore_panic
という内部実装についての不安定機能を使うためこのattributeを持っています。
ややこしいですが、6行目ではallow_internal_unstable
という機能それ自体を許可しようとしています。
マクロ内の上の方は複数のattributeがある場合の整形なので割愛します。
下の部分が処理本体で、コードをhorrible_crimes
に包んでから展開しています。これはallow_internal_unstable
がマクロにのみ効果があるattributeであるためです[2]。
しかし、allow_internal_unstable
はそれ自体が不安定機能であり、このままstable Rustで実行しようとしてもE0554エラーが発生するだけです。
ということはdo_crimes!
マクロが一体何者なのかを探っていく必要がありそうです。
yolo-rustc-bootstrap
ソースコードはこちらから閲覧できます。
実際の処理は30~36行目にあります。以下に引用します。
let mut args = std::env::args_os();
let status = std::process::Command::new(args.next().unwrap())
.arg("--cfg=yolo_rustc_bootstrap")
.args(args)
.env("RUSTC_BOOTSTRAP", "1")
.status()
.unwrap();
args
にはこのプログラムの起動時に実行されたコマンドと引数が入っています。
それを--cfg=yolo_rustc_bootstrap
という引数とRUSTC_BOOTSTRAP=1
という環境変数とともに呼び直しています。
ここで注目すべきはRUSTC_BOOTSTRAP
です。これは主にrustcをブートストラップするときに使われるもので、1
に設定するとrustcのリリースチャンネルに関わらず不安定機能を利用できるようになります[3]。
実はとってもシンプルなタネだったんですね!
実際最初に例示したコードをマクロから取り出してRUSTC_BOOTSTRAP=1
を渡しつつcargo +stable run
してみてもコンパイル・実行できます。
ちなみにcfg
は条件付きコンパイルのためのもので、初回ビルド時に不安定機能を有効化しないために使われています。
So, what's the hack?
まとめると、nightly_crimes!
は以下の流れでコードを実行します。
- 初回は
cfg
で不安定機能を隠しつつ、-cfg=yolo_rustc_bootstrap
とRUSTC_BOOTSTRAP=1
を持たせてnightly_crimes
をビルドし直す - 2回目のビルド時にはマクロを実際に露出させつつ不安定機能を使えるようにする
- マクロ呼び出し時、
allow_internal_unstable
の下に渡されたコードを置くことで任意の不安定機能を使ったユーザーコードをコンパイル・実行できるようにする
元々RUSTC_BOOTSTRAP=1
自体ユーザーが使うべきでないものなのですが、nightly_crimes!
はそれを隠蔽しつつジョークとして仕上げたものになっています[4]。
小話として以前はbuild.rs
を通してこの環境変数を渡せましたが、1.53.0から禁止されました。
nightly_crimes
のREADMEに"Please do not use this."と記載されているように、これはジョークに留めておくべきです。
知っておくとちょっと面白いRust小話、あるいはRustのブートストラップ回りに興味を持つきっかけになるなどしていれば幸いです。
Discussion