課題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);
    }
    

  • Rust 2015(レキシカルライフタイム)ではなぜエラーになるか説明しなさい。(4点)
  • Rust 2018(NLL)ではなぜOKになるか説明しなさい。(4点)
  • レキシカルライフタイムとNLLの違いを図示しなさい。(2点)

図の例

┌─────────────────────┐
│ レキシカルライフタイム │
├─────────────────────┤
│ 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);
    }
    

  • 各スコープでの借用状態を表にまとめなさい。(5点)
  • 行番号 不変参照 可変参照 所有者の状態 OK/NG
    1 なし なし v が所有 OK
    ... ? ? ? ?

  • 借用規則違反がないことを証明しなさい。(5点)
  • ---

    問題4: デバッグ技術(15点)

    4.1 借用チェッカーとの対話(8点)

    以下のコードをコンパイルし、エラーを修正するプロセスを記録しなさい:

    fn main() {
        let mut data = vec![1, 2, 3];
        let x = &data[0];
        data.push(4);
        println!("{}", x);
    }
    

  • 初回コンパイル(2点)
- エラーメッセージを記録 - エラーコードを調査

  • 問題の分析(3点)
- なぜエラーになるか説明 - 借用の競合を図示

  • 修正方法(3点)
- 少なくとも2通りの修正方法を示す - 各修正のトレードオフを説明

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       # 参考文献
    

    提出期限

  • マンダトリー:第2章学習後、1週間以内
  • ボーナス:第4章修了時まで

---

ヒント

問題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)
  • [ ] 借用チェッカーの限界と将来の改善
  • 次の章では、実践的な所有権パターン(Interior Mutability、Rc/Arcなど)を学びます。

    ---

    参考資料

    公式ドキュメント

  • MIR(Mid-level IR)
- https://rustc-dev-guide.rust-lang.org/mir/index.html - MIRの仕様とフォーマット

  • Non-Lexical Lifetimes(NLL)
- https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html - NLLの導入とRust 2018

  • Polonius
- https://github.com/rust-lang/polonius - 次世代借用チェッカー

追加リソース

  • "The Rust RFC Book"
- RFC 2094: Non-Lexical Lifetimes - https://rust-lang.github.io/rfcs/2094-nll.html

  • "Rust Compiler Development Guide"
- 借用チェッカーの実装 - https://rustc-dev-guide.rust-lang.org/borrow_check.html

  • Niko Matsakis's Blog
- Polonius and the case of the hereditary harrop predicate - http://smallcultfollowing.com/babysteps/