TT Lab
はじめる
学ぶ 学習パス コース

Rust — コンパイラが止めるもの

コンパイラと対話する

TT Labで続きを見る

目標

Rustコンパイラーが何をなぜ止めるのかを、エラーメッセージを読みながら身につけます。 このラボにはわざとビルドを失敗させるステップがあります。そのメッセージが 学ぶ対象だからです。

なぜ重要なのか

Rustを難しくしているのは文法ではなく所有権です。そして所有権を 学ぶ最も速い道は、コンパイラーに止められてみることです。

止められる2つは、実際には将来のバグです。

C++では、どちらも黙って通り、あとで妙な値が出ます。

環境

ネットワークがないため、cargo addは動作しません。このラボは標準 ライブラリだけを使うので問題ありません。

cargo build --offline     빌드
cargo run --offline       실행
cargo clippy --offline    더 나은 방법 제안

ステップ

すべてのファイルは/root/work/ownの中に作ります。

  1. cargo init --name own
  2. examples/move_err.rs: わざと失敗 → 02-move.txt
  3. examples/move_ok.rs: helpに従って直す
  4. examples/borrow_err.rs: わざと失敗 → 04-borrow.txt
  5. examples/borrow_ok.rs: 範囲を狭めて通過
  6. Resultでエラーを扱う → 06-result.txt
  7. clippy → 07-clippy.txt
  8. 何が止められ、なぜだったのか → 08-notes.md

なぜexamples/なのか: cargo buildはexamplesには触れません。 わざと壊したコードをそこに置いても、プロジェクト全体のビルドは通り続けます。

参考

プロジェクトを作る

/root/work/ownにcargoプロジェクトを作り、空の状態でビルドが通るようにしてください。

mkdir -p /root/work/own && cd /root/work/own && cargo init --name own。ネットワークがないのでcargo addは使わないでください。このラボは標準ライブラリだけを使います。常に--offlineを付けます: cargo build --offline。

わざとムーブして、エラーを読む

ムーブされた値をもう一度使うコードをexamples/move_err.rsに書き、ビルドエラーの全文を02-move.txtに残してください。このステップの正解はビルド失敗です。

mkdir -p examplesのあと、examples/move_err.rsにmainを1つ書き、その中でlet s = String::from("hi"); let t = s; println!("{} {}", s, t);と書きます。続けてcargo build --offline --example move_err > 02-move.txt 2>&1。E0382と「moved」が見えるはずです。examples/はcargo buildが触れないので、壊れたままにしておいてもかまいません。

コンパイラーの言うとおりに直す

同じことをエラーなしで実現するバージョンをexamples/move_ok.rsに書き、実行すると2つの文字列が出力されるようにしてください。

エラーメッセージの下のhelp:の行が直し方を教えてくれます。s.clone()でコピーするか、&sで借りて通してください。どちらも正解です。cargo run --offline --example move_ok。

借用の規則にぶつかってみる

読み取りの借用が生きている間にpushするコードをexamples/borrow_err.rsに書き、エラーを04-borrow.txtに残してください。ここも正解は失敗です。

let mut v = vec![1,2,3]; let first = &v[0]; v.push(4); println!("{}", first); → cargo build --offline --example borrow_err > 04-borrow.txt 2>&1。E0502が出ます。読み取りの借用が生きているのに書き込みの借用を求めた、という意味です。

範囲を狭めて通す

同じことをしつつ、ビルドが通るバージョンをexamples/borrow_ok.rsに書いてください。pushもして、最初の値も出力する必要があります。

借用は最後に使われた地点までしか生きません。firstをpushの前に使い終えれば、衝突は消えます。値をコピーしておくこと(let first = v[0];)も正解です。i32はCopyだからです。

エラーをResultで扱う

fn half(n: i32) -> Result<i32, String>を作って、奇数ならErrを返すようにし、mainで成功・失敗の両方を出力してください。結果を06-result.txtに残します。

src/main.rsに入れて、match half(n) { Ok(v) => ..., Err(e) => ... }で2つの分岐をどちらも処理してください。unwrap()は使わないでください。それは失敗時にプロセスを落とすpanicです。cargo run --offline > 06-result.txt 2>&1。

clippyに聞いてみる

cargo clippy --offlineが警告なしで通るようにして、出力を07-clippy.txtに残してください。

cargo clippy --offline --all-targets -- -D warnings > 07-clippy.txt 2>&1で、警告をエラーに引き上げて確認します。examples/のわざと壊したファイルは除外する必要があるので、--all-targetsの代わりに既定の対象だけを見てください: cargo clippy --offline > 07-clippy.txt 2>&1。指摘が出たら、何をなぜ指摘しているのかを読んで直してください。

何が止められ、なぜだったのか

08-notes.mdに3行でまとめてください。E0382が止めたもの、E0502が止めたもの、そしてその2つがなかったら実行中にどんな事故が起きていたか。

2つのエラーコードを本文にそのまま書いてください。3行目が核心です。コンパイラーが将来のバグをいま見せてくれたのです(解放されたメモリへのアクセス、再割り当てされたベクターの古いポインター)。