課題2: 複雑なライフタイム

マンダトリー要件(80点)

問題1: 複数のライフタイムパラメータ(20点)

1.1 独立したライフタイム(10点)

以下の構造体を実装しなさい:

struct DataPair<'a, 'b> {
    first: &'a str,
    second: &'b str,
}

impl<'a, 'b> DataPair<'a, 'b> {
    fn new(first: &'a str, second: &'b str) -> Self {
        // ここを実装しなさい(2点)
    }

    fn get_first(&self) -> &'a str {
        // ここを実装しなさい(2点)
    }

    fn get_second(&self) -> &'b str {
        // ここを実装しなさい(2点)
    }

    // 両方のフィールドを結合した新しい String を返す
    fn concat(&self) -> String {
        // ここを実装しなさい(2点)
    }

    // first が second より長い場合は true を返す
    fn is_first_longer(&self) -> bool {
        // ここを実装しなさい(2点)
    }
}

要件:

  • 全てのメソッドを実装すること
  • ライフタイムを正確に扱うこと

1.2 包含関係のあるライフタイム(10点)

以下の関数を実装しなさい:

// 'a は 'b より長く生きる
fn select_longer<'a, 'b>(x: &'a str, y: &'b str) -> &'b str
where
    'a: 'b,
{
    // ここを実装しなさい(5点)
    // 仕様:
    // - x と y のうち、文字列として長い方を返す
    // - 同じ長さの場合は x を返す
    // - 戻り値は 'b のライフタイムを持つ
}

// 最小共通ライフタイムを使用
fn combine<'a>(x: &'a str, y: &'a str, separator: &str) -> String {
    // ここを実装しなさい(5点)
    // 仕様:
    // - x と y を separator で結合した String を返す
    // - 例: combine("hello", "world", " ") → "hello world"
}

---

問題2: ライフタイム省略規則の理解(20点)

2.1 省略規則の予測(10点)

以下のコードで省略されているライフタイムを明示した形を書きなさい:

コードA:

fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

  • 省略されているライフタイムを明示した形を書きなさい。(3点)
  • どの省略規則(規則1、2、3)が適用されたか説明しなさい。(2点)

コードB:

struct Buffer<'a> {
    data: &'a [u8],
}

impl<'a> Buffer<'a> {
    fn slice(&self, start: usize, end: usize) -> &[u8] {
        &self.data[start..end]
    }
}

  • slice メソッドの完全な型シグネチャを書きなさい。(3点)
  • どの省略規則が適用されたか説明しなさい。(2点)
  • 2.2 省略できないケース(10点)

    以下の関数を完成させなさい:

    // エラーが出る関数を修正しなさい
    fn choose_string(x: &str, y: &str, use_first: bool) -> &str {
        if use_first { x } else { y }
    }
    

  • このコードのエラーを説明しなさい。(3点)
  • 正しく修正したコードを書きなさい。(4点)
  • なぜライフタイムの明示が必要か説明しなさい。(3点)

---

問題3: Higher-Ranked Trait Bounds(20点)

3.1 基本的なHRTBs(10点)

以下の関数を完成させなさい:

// 任意のライフタイムで動作するクロージャを受け取る関数
fn apply_to_ref<F>(f: F, value: &i32) -> i32
where
    F: ???,  // ここを埋めなさい(5点)
{
    *f(value)
}

// クロージャを返す関数
fn returns_identity() -> ??? {  // ここを埋めなさい(5点)
    Box::new(|x: &str| x)
}

要件:

  • ??? の部分を正しいHRTBsの型注釈で埋めること
  • テストケースを3つ作成すること

3.2 実践的なHRTBs(10点)

以下のトレイトと実装を完成させなさい:

// 任意のライフタイムの文字列を処理できるトレイト
trait StringProcessor {
    fn process<'a>(&self, input: &'a str) -> &'a str;
}

// 実装1: 入力をそのまま返す
struct IdentityProcessor;

impl StringProcessor for IdentityProcessor {
    // ここを実装しなさい(3点)
}

// 実装2: 空白をトリムして返す
struct TrimProcessor;

impl StringProcessor for TrimProcessor {
    // ここを実装しなさい(3点)
}

// プロセッサを使用する関数
fn process_text<P>(processor: &P, texts: Vec<String>) -> Vec<String>
where
    P: StringProcessor,
{
    // ここを実装しなさい(4点)
    // 仕様:
    // - 各テキストに processor を適用
    // - 結果を新しい String の Vec として返す
}

---

問題4: ライフタイム境界(20点)

4.1 T: 'a の理解(10点)

以下の構造体を実装しなさい:

struct Holder<'a, T>
where
    T: 'a,
{
    value: &'a T,
}

impl<'a, T> Holder<'a, T>
where
    T: 'a,
{
    fn new(value: &'a T) -> Self {
        // ここを実装しなさい(2点)
    }

    fn get(&self) -> &'a T {
        // ここを実装しなさい(2点)
    }

    // 値を別の Holder にコピー
    fn clone_to<'b>(&self) -> Holder<'b, T>
    where
        'a: 'b,
        T: 'b,
    {
        // ここを実装しなさい(3点)
    }

    // クロージャに値を渡して実行
    fn apply<F, R>(&self, f: F) -> R
    where
        F: FnOnce(&'a T) -> R,
    {
        // ここを実装しなさい(3点)
    }
}

質問:

  • なぜ T: 'a の境界が必要か説明しなさい。(各2点)
  • clone_to メソッドの制約を説明しなさい。(各3点)

4.2 T: 'static の実践(10点)

以下の関数を実装しなさい:

use std::thread;

// スレッドセーフな値を別スレッドで処理
fn process_in_thread<T, F>(value: T, f: F) -> thread::JoinHandle<()>
where
    T: ???,  // ここを埋めなさい(3点)
    F: ???,  // ここを埋めなさい(3点)
{
    thread::spawn(move || {
        f(value);
    })
}

// 使用例を実装
fn test_process_in_thread() {
    // ここを実装しなさい(4点)
    // 要件:
    // - i32 を別スレッドで2倍にして表示
    // - String を別スレッドで大文字に変換して表示
    // - スレッドの完了を待つ
}

---

ボーナス課題(20点)

ボーナス1: ライフタイムの変性(Variance)の実装(8点)

以下のコードを完成させ、変性の動作を確認しなさい:

use std::marker::PhantomData;

// Covariant な型
struct CovariantType<'a, T> {
    _marker: PhantomData<&'a T>,
    data: *const T,
}

impl<'a, T> CovariantType<'a, T> {
    fn new(data: &'a T) -> Self {
        // ここを実装しなさい(2点)
    }

    fn get(&self) -> &'a T {
        // ここを実装しなさい(1点)
        // 注: unsafe を使用
    }
}

// Invariant な型
struct InvariantType<'a, T> {
    _marker: PhantomData<&'a mut T>,
    data: *mut T,
}

impl<'a, T> InvariantType<'a, T> {
    fn new(data: &'a mut T) -> Self {
        // ここを実装しなさい(2点)
    }

    fn get(&self) -> &T {
        // ここを実装しなさい(1点)
        // 注: unsafe を使用
    }

    fn get_mut(&mut self) -> &mut T {
        // ここを実装しなさい(2点)
        // 注: unsafe を使用
    }
}

要件:

  • 安全な API を提供すること
  • unsafe ブロック内で適切にポインタを扱うこと
  • 各型の変性を説明するコメントを追加すること

---

ボーナス2: 複雑なパーサーの実装(7点)

JSONライクなトークナイザを実装しなさい:

#[derive(Debug, PartialEq)]
enum Token<'a> {
    String(&'a str),
    Number(f64),
    True,
    False,
    Null,
    LeftBrace,
    RightBrace,
    LeftBracket,
    RightBracket,
    Comma,
    Colon,
}

struct Tokenizer<'input> {
    input: &'input str,
    position: usize,
}

impl<'input> Tokenizer<'input> {
    fn new(input: &'input str) -> Self {
        Tokenizer { input, position: 0 }
    }

    fn next_token(&mut self) -> Option<Token<'input>> {
        // ここを実装しなさい(5点)
        // 仕様:
        // - 空白をスキップ
        // - 文字列: "..." 形式(エスケープ不要)
        // - 数値: 整数または小数
        // - ブール値と null
        // - 記号: { } [ ] , :
    }

    fn skip_whitespace(&mut self) {
        // ここを実装しなさい(1点)
    }

    fn parse_string(&mut self) -> Option<&'input str> {
        // ここを実装しなさい(1点)
    }
}

評価基準:

  • 全てのトークンタイプを正しく認識(4点)
  • ライフタイム 'input を正確に使用(2点)
  • テストケースを10個以上作成(1点)

---

ボーナス3: ゼロコピーパターンの実装(5点)

以下のゼロコピー設定パーサーを実装しなさい:

use std::collections::HashMap;

#[derive(Debug)]
struct Config<'a> {
    raw: &'a str,
    entries: HashMap<&'a str, &'a str>,
}

impl<'a> Config<'a> {
    // "key1=value1\nkey2=value2" 形式をパース
    fn parse(input: &'a str) -> Result<Self, &'static str> {
        // ここを実装しなさい(3点)
        // 仕様:
        // - 各行を = で分割
        // - key と value はトリム
        // - 空行はスキップ
        // - = がない行はエラー
    }

    fn get(&self, key: &str) -> Option<&'a str> {
        // ここを実装しなさい(1点)
    }

    fn keys(&self) -> impl Iterator<Item = &'a str> {
        // ここを実装しなさい(1点)
    }
}

// 使用例
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_zero_copy_config() {
        let input = "name=rust\nversion=1.70\nauthor=ferris";
        let config = Config::parse(input).unwrap();

        assert_eq!(config.get("name"), Some("rust"));
        assert_eq!(config.get("version"), Some("1.70"));
        assert_eq!(config.get("author"), Some("ferris"));
        assert_eq!(config.get("unknown"), None);

        // 元の文字列が生きている限り config も有効
        assert!(input.contains("rust"));
    }
}

評価基準:

  • ゼロコピー実装(コピー不要)(2点)
  • エラーハンドリング(1点)
  • テストの網羅性(2点)
  • ---

    評価基準

    マンダトリー部分(80点)

    項目 配点 評価ポイント
    問題1:複数のライフタイム 20点 独立・包含関係の理解
    問題2:省略規則 20点 規則の理解と適用
    問題3:HRTBs 20点 高階トレイト境界の実装
    問題4:ライフタイム境界 20点 `T: 'a` と `T: 'static` の理解

    ボーナス部分(20点)

    項目 配点 評価ポイント
    ボーナス1:変性の実装 8点 PhantomData と unsafe の理解
    ボーナス2:複雑なパーサー 7点 実践的なライフタイム設計
    ボーナス3:ゼロコピーパターン 5点 パフォーマンス最適化

    : ボーナスは最大20点まで加算されます。

    ---

    提出方法

    ファイル構成

    rust-lifetime-mastery-02/
    ├── src/
    │   ├── lib.rs
    │   ├── problem1.rs          # 問題1の実装
    │   ├── problem2.rs          # 問題2の回答
    │   ├── problem3.rs          # 問題3の実装
    │   ├── problem4.rs          # 問題4の実装
    │   ├── bonus1_variance.rs   # ボーナス1
    │   ├── bonus2_tokenizer.rs  # ボーナス2
    │   └── bonus3_zero_copy.rs  # ボーナス3
    ├── tests/
    │   ├── problem1_tests.rs
    │   ├── problem3_tests.rs
    │   └── integration_tests.rs
    └── Cargo.toml
    

    提出期限

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

---

ヒント

問題1のヒント

独立したライフタイム:

impl<'a, 'b> DataPair<'a, 'b> {
    fn new(first: &'a str, second: &'b str) -> Self {
        DataPair { first, second }
    }

    fn get_first(&self) -> &'a str {
        self.first  // 'a を返す
    }
}

包含関係:

fn select_longer<'a, 'b>(x: &'a str, y: &'b str) -> &'b str
where
    'a: 'b,  // 'a は 'b より長く生きる
{
    // x は &'a str
    // しかし戻り値は &'b str
    // 'a: 'b なので、x は &'b str として扱える(covariant)
    if x.len() > y.len() { x } else { y }
}

問題2のヒント

省略規則の復習:

  • 規則1: 各参照パラメータに独立したライフタイムを割り当て
  • 規則2: 入力ライフタイムが1つなら、全ての出力に適用
  • 規則3: メソッドの &self のライフタイムを出力に適用

省略できないケース:

// 複数の入力、1つの出力 → 明示が必要
fn choose_string<'a>(x: &'a str, y: &'a str, use_first: bool) -> &'a str {
    if use_first { x } else { y }
}

問題3のヒント

HRTBsの型注釈:

// 任意のライフタイムで動作
F: for<'a> Fn(&'a i32) -> &'a i32

// クロージャを返す
Box<dyn for<'a> Fn(&'a str) -> &'a str>

トレイト実装:

impl StringProcessor for TrimProcessor {
    fn process<'a>(&self, input: &'a str) -> &'a str {
        input.trim()
    }
}

問題4のヒント

T: 'a の必要性:

struct Holder<'a, T>
where
    T: 'a,  // T は 'a より長く生きる必要がある
{
    value: &'a T,  // &'a T を持つため
}

T: 'static:

where
    T: Send + 'static,  // スレッドに送れる & 参照を含まない
    F: FnOnce(T) + Send + 'static,

ボーナス1のヒント

PhantomData:

// Covariant
struct CovariantType<'a, T> {
    _marker: PhantomData<&'a T>,  // &'a T と同じ変性
    data: *const T,
}

// Invariant
struct InvariantType<'a, T> {
    _marker: PhantomData<&'a mut T>,  // &'a mut T と同じ変性
    data: *mut T,
}

ボーナス2のヒント

トークナイザ:

fn next_token(&mut self) -> Option<Token<'input>> {
    self.skip_whitespace();

    if self.position >= self.input.len() {
        return None;
    }

    let rest = &self.input[self.position..];
    let first_char = rest.chars().next()?;

    match first_char {
        '"' => self.parse_string().map(Token::String),
        '{' => { self.position += 1; Some(Token::LeftBrace) },
        // ... 他の記号
        _ => None,
    }
}

ボーナス3のヒント

ゼロコピー実装:

fn parse(input: &'a str) -> Result<Self, &'static str> {
    let mut entries = HashMap::new();

    for line in input.lines() {
        let line = line.trim();
        if line.is_empty() {
            continue;
        }

        let parts: Vec<&str> = line.split('=').collect();
        if parts.len() != 2 {
            return Err("Invalid format");
        }

        entries.insert(parts[0].trim(), parts[1].trim());
    }

    Ok(Config { raw: input, entries })
}

---

学習の確認

この課題を通じて、以下を理解できたか確認してください:

  • [ ] 複数のライフタイムパラメータの設計
  • [ ] 独立したライフタイムと包含関係
  • [ ] 3つのライフタイム省略規則
  • [ ] 省略できないケースの判断
  • [ ] HRTBs (for<'a>) の使い方
  • [ ] ライフタイム境界 (T: 'a, T: 'static)
  • [ ] 変性(Covariant、Invariant)の理解
  • [ ] ゼロコピーパターンの実装

次の章では、自己参照構造、Pin、そして高度なパターンを学びます。