コンパイラと対話する
目標
Rustコンパイラーが何をなぜ止めるのかを、エラーメッセージを読みながら身につけます。 このラボにはわざとビルドを失敗させるステップがあります。そのメッセージが 学ぶ対象だからです。
なぜ重要なのか
Rustを難しくしているのは文法ではなく所有権です。そして所有権を 学ぶ最も速い道は、コンパイラーに止められてみることです。
止められる2つは、実際には将来のバグです。
E0382(moved value): 解放されたメモリを使う事故E0502(borrow conflict): コンテナが再割り当てされたあと、古いポインターを読む事故
C++では、どちらも黙って通り、あとで妙な値が出ます。
環境
ネットワークがないため、cargo addは動作しません。このラボは標準
ライブラリだけを使うので問題ありません。
cargo build --offline 빌드
cargo run --offline 실행
cargo clippy --offline 더 나은 방법 제안
ステップ
すべてのファイルは/root/work/ownの中に作ります。
cargo init --name ownexamples/move_err.rs: わざと失敗 →02-move.txtexamples/move_ok.rs: helpに従って直すexamples/borrow_err.rs: わざと失敗 →04-borrow.txtexamples/borrow_ok.rs: 範囲を狭めて通過Resultでエラーを扱う →06-result.txt- clippy →
07-clippy.txt - 何が止められ、なぜだったのか →
08-notes.md
なぜexamples/なのか: cargo buildはexamplesには触れません。
わざと壊したコードをそこに置いても、プロジェクト全体のビルドは通り続けます。
参考
- エラーメッセージを最後まで読んでください。
help:の行に直し方があります。 - 所有権と戦って詰まったら、
clone()して先に進んでもかまいません。性能が 問題になったら、そのとき直します。最初から参照で最適化しようとすることが、Rustを 難しくしている最も一般的な理由です。
プロジェクトを作る
/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行目が核心です。コンパイラーが将来のバグをいま見せてくれたのです(解放されたメモリへのアクセス、再割り当てされたベクターの古いポインター)。