課題2: 借用チェッカーの内部動作
マンダトリー要件(80点)
問題1: MIR(Mid-level IR)の理解(20点)
1.1 MIRの基本(10点)
以下のRustコードに対応するMIRを書きなさい:
fn example(x: i32) -> i32 {
let y = x + 1;
y * 2
}
- 各基本ブロック(Basic Block)を特定しなさい。(3点)
- 変数の生存区間(Live Range)を図示しなさい。(4点)
- ドロップポイントを明示しなさい。(3点)
提出形式:
bb0: {
// ここにMIRを記述
}
1.2 MIRによる借用チェック(10点)
以下のコードのMIRを生成し、なぜコンパイルエラーになるか説明しなさい:
fn main() {
let mut v = vec![1, 2, 3];
let r = &v[0];
v.push(4);
println!("{}", r);
}
- MIRレベルでの借用の表現を記述しなさい。(5点)
- コンフリクトポイントを特定しなさい。(3点)
- エラーメッセージの生成理由を説明しなさい。(2点)
---
問題2: Non-Lexical Lifetimes(NLL)(25点)
2.1 レキシカルライフタイムの限界(10点)
以下のコードを分析しなさい:
fn main() {
let mut s = String::from("hello");
// Rust 2015ではエラー、Rust 2018ではOK
let r1 = &s;
println!("{}", r1);
let r2 = &mut s;
println!("{}", r2);
}
図の例:
┌─────────────────────┐
│ レキシカルライフタイム │
├─────────────────────┤
│ let r = &x; │ ← 'a の開始
│ // use r │ │
│ // more code │ │ 'a: スコープ全体
│ } │ ← 'a の終了
└─────────────────────┘
2.2 NLLの動作原理(15点)
以下のコードについて、NLLがどのように借用を分析するか説明しなさい:
fn process(v: &mut Vec<i32>) {
let first = &v[0]; // 不変参照
println!("{}", first); // firstの最後の使用
v.push(1); // 可変参照(これはOK?)
}
- 制御フローグラフ(CFG)の作成(5点)
- Live Rangeの分析(5点)
first の生存区間を特定
- v の借用区間を特定
- 重複がないことを証明- コンパイル結果の予測(5点)
---
問題3: 借用チェッカーの診断(20点)
3.1 エラーメッセージの解読(10点)
以下のコードのコンパイルエラーを分析しなさい:
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
drop(s);
println!("{} {}", r1, r2);
}
- エラーメッセージを予測しなさい。(3点)
- エラーコード(E0505など)の意味を説明しなさい。(3点)
- 借用チェッカーがどのように問題を検出したか説明しなさい。(4点)
3.2 複雑な借用パターン(10点)
以下のコードの借用チェックを手動で行いなさい:
fn main() {
let mut v = vec![1, 2, 3];
{
let r1 = &v;
let r2 = &v;
println!("{:?} {:?}", r1, r2);
}
let r3 = &mut v;
r3.push(4);
}
| 行番号 | 不変参照 | 可変参照 | 所有者の状態 | OK/NG |
|---|---|---|---|---|
| 1 | なし | なし | v が所有 | OK |
| ... | ? | ? | ? | ? |
---
問題4: デバッグ技術(15点)
4.1 借用チェッカーとの対話(8点)
以下のコードをコンパイルし、エラーを修正するプロセスを記録しなさい:
fn main() {
let mut data = vec![1, 2, 3];
let x = &data[0];
data.push(4);
println!("{}", x);
}
- 問題の分析(3点)
- 修正方法(3点)
4.2 -Z polonius の活用(7点)
Poloniusを有効にして以下のコードを分析しなさい:
RUSTFLAGS="-Z polonius" cargo build
fn get_or_insert<'a>(map: &'a mut HashMap<String, String>, key: String) -> &'a String {
if let Some(v) = map.get(&key) {
return v; // エラー?
}
map.insert(key.clone(), String::new());
map.get(&key).unwrap()
}
- 通常の借用チェッカーでの結果を予測しなさい。(2点)
- Poloniusでの結果を予測しなさい。(2点)
- なぜPoloniusは通常の借用チェッカーより賢いか説明しなさい。(3点)
---
ボーナス課題(20点)
ボーナス1: MIRの実装(10点)
以下の複雑なコードのMIRを完全に書きなさい:
fn complex_borrow() -> i32 {
let mut x = 5;
let y = &x;
let z = if *y > 3 {
&x
} else {
let w = 10;
&w // これはエラー
};
*z
}
要件:
- 完全なMIR表現を記述(基本ブロック、分岐、変数の生存区間)
- エラーが発生する理由をMIRレベルで説明
- 制御フローグラフを図示
提出形式:Markdown形式で、コードブロックと図を含めること
---
ボーナス2: NLLのケーススタディ(10点)
以下のコードがRust 2015とRust 2018で異なる動作をする理由を詳細に説明しなさい:
fn example1() {
let mut s = String::from("hello");
let r = &s;
s.push_str(" world"); // Rust 2015: エラー、Rust 2018: ?
}
fn example2() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{}", first);
v.push(4); // Rust 2015: エラー、Rust 2018: ?
}
fn example3() {
let mut map = HashMap::new();
map.insert("key", "value");
if let Some(v) = map.get("key") {
println!("{}", v);
}
map.insert("key2", "value2"); // Rust 2015: エラー、Rust 2018: ?
}
要件:
- 各例について、Rust 2015とRust 2018での動作を予測
- NLLがどのように問題を解決するか説明
- 実際にコンパイルして結果を確認
- レポートをMarkdown形式で提出(2000文字以上)
---
ボーナス3: 借用チェッカーの限界とPoloniusによる解決(10点)
以下の「借用チェッカーには正しいが受け入れられないコード」を分析しなさい:
fn process_entry(map: &mut HashMap<String, Vec<i32>>, key: &str) {
if !map.contains_key(key) {
map.insert(key.to_string(), vec![]);
}
let list = map.get_mut(key).unwrap();
list.push(1);
}
要件:
- このコードをコンパイルして結果を確認(通常の借用チェッカー)
-Z poloniusを使ってコンパイルして結果を確認- 両者の違いを詳細に説明
- Poloniusの内部アルゴリズム(Datalogベースの分析)を調査
- なぜPoloniusは将来のRustのデフォルトになる可能性があるか考察
提出形式:
- コンパイル結果のスクリーンショット
- 詳細な分析レポート(3000文字以上)
- 参考文献のリスト
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:MIRの理解 | 20点 | MIRの正確な表現と分析能力 |
| 問題2:NLLの理解 | 25点 | NLLの動作原理の理解 |
| 問題3:診断技術 | 20点 | エラーメッセージの解読能力 |
| 問題4:デバッグ | 15点 | 実践的なデバッグスキル |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:MIR実装 | 10点 | 複雑なコードの完全な分析 |
| ボーナス2:NLLケーススタディ | 10点 | 実験と考察の深さ |
| ボーナス3:Poloniusの調査 | 10点 | 最新技術の理解と考察 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-ownership-deep-dive-02/
├── answers.md # マンダトリー問題の回答
├── bonus1-mir.md # ボーナス1のMIR分析
├── bonus2-nll-study.md # ボーナス2のケーススタディ
└── bonus3-polonius/
├── analysis.md # Poloniusの分析
├── screenshots/ # コンパイル結果
└── references.md # 参考文献
提出期限
---
ヒント
問題1のヒント(MIR)
MIRの基本要素:
fn example() -> i32 {
bb0: {
let _0: i32; // 戻り値
let _1: i32; // ローカル変数
_1 = const 42i32; // 代入
_0 = move _1; // ムーブ
return; // リターン
}
}
重要な概念:
- 基本ブロック(bb0, bb1, ...)
- テンポラリ変数(_0, _1, ...)
- 操作(move, copy, borrow)
問題2のヒント(NLL)
レキシカル vs NLL:
// レキシカル:スコープベース
let r = &x; // 'a の開始
// ...
} // 'a の終了(スコープの終わり)
// NLL:最後の使用ベース
let r = &x; // 'a の開始
println!("{}", r); // 'a の終了(最後の使用)
// 以降、rは使われていないので借用は解除
問題3のヒント(診断)
エラーメッセージの構造:
error[E0505]: cannot move out of `s` because it is borrowed
--> src/main.rs:5:10
|
3 | let r1 = &s;
| -- borrow of `s` occurs here
4 | let r2 = &s;
5 | drop(s);
| ^ move out of `s` occurs here
6 | println!("{} {}", r1, r2);
| -- borrow later used here
問題4のヒント(デバッグ)
修正パターン:
- スコープを分ける
let x = {
let data = vec![1, 2, 3];
data[0]
};
// dataは既にドロップ済み
- クローンを使う
let x = data[0]; // i32はCopyなのでOK
- 借用を短くする
println!("{}", &data[0]); // 一時的な借用
data.push(4); // OK
ボーナス3のヒント(Polonius)
Poloniusの特徴:
- Datalogベースの分析
- より精密な制御フロー分析
- パス依存の借用チェック
- 将来のRustのデフォルトになる可能性
Poloniusを試す:
# rustup で nightly をインストール
rustup install nightly
# Poloniusを有効にしてコンパイル
cargo +nightly rustc -- -Z polonius
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
- [ ] MIR(Mid-level IR)の構造
- [ ] 基本ブロックと制御フロー
- [ ] Non-Lexical Lifetimes(NLL)の動作原理
- [ ] レキシカルライフタイムとNLLの違い
- [ ] 借用チェッカーの診断メッセージの読み方
- [ ] デバッグ技術とツール(Polonius)
- [ ] 借用チェッカーの限界と将来の改善
- MIR(Mid-level IR)
次の章では、実践的な所有権パターン(Interior Mutability、Rc/Arcなど)を学びます。
---
参考資料
公式ドキュメント
- Non-Lexical Lifetimes(NLL)
- Polonius
追加リソース
- "The Rust RFC Book"
- "Rust Compiler Development Guide"
- Niko Matsakis's Blog