課題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: 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
提出期限
---
ヒント
問題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、そして高度なパターンを学びます。