Chapter 4: 高度なパターン
学習目標
- ライフタイム省略の高度なテクニックを習得する
- トレイトオブジェクトとライフタイムの関係を理解する
- 高階ライフタイムの使い方をマスターする
- プロダクションコードでのベストプラクティスを学ぶ
- コンパイラとの対話方法を理解する
---
4.1 高度な省略テクニック
4.1.1 暗黙的な 'static 境界
// この2つは異なる
fn foo<T>(x: T) {}
// ^
// T: 'static が暗黙的
fn bar<T: ?Sized>(x: &T) {}
// ^^^^^^^^^
// 'static を要求しない
// 明示的に書くと
fn foo_explicit<T: 'static>(x: T) {}
fn bar_explicit<T: ?Sized + 'static>(x: &T) {}
理由:
T を所有するには、T が 'static である必要がある
(参照を含む場合、その参照も 'static)
ただし &T の場合は T: 'static は不要
(借用しているだけなので)
4.1.2 impl Trait とライフタイム
// 戻り値の impl Trait
fn returns_iter<'a>(s: &'a str) -> impl Iterator<Item = &'a str> + 'a {
// ^^^^
// 境界が必要
s.split_whitespace()
}
// 省略形(Rust 2018+)
fn returns_iter_short(s: &str) -> impl Iterator<Item = &str> + '_ {
// ^^
// 省略記法
s.split_whitespace()
}
---
4.2 トレイトオブジェクトとライフタイム
4.2.1 dyn Trait のライフタイム
trait Trait {}
// これらは異なる
fn foo1(x: Box<dyn Trait>) {}
// ^^^^^^^^^^^
// 暗黙的に Box<dyn Trait + 'static>
fn foo2(x: &dyn Trait) {}
// ^^^^^^^^^^
// 暗黙的に &(dyn Trait + '_)
// 入力のライフタイムから推論
// 明示的に書くと
fn foo1_explicit(x: Box<dyn Trait + 'static>) {}
fn foo2_explicit<'a>(x: &'a (dyn Trait + 'a)) {}
4.2.2 トレイトオブジェクトの境界
trait Trait {}
struct Struct<'a> {
trait_object: Box<dyn Trait + 'a>,
// ^^^^
// 明示的な境界
}
impl<'a> Struct<'a> {
fn new(obj: Box<dyn Trait + 'a>) -> Self {
Struct { trait_object: obj }
}
}
// 使用例
fn example<'a>(s: &'a str) -> Struct<'a> {
struct MyType<'a>(&'a str);
impl<'a> Trait for MyType<'a> {}
Struct::new(Box::new(MyType(s)))
}
---
4.3 Higher-Rank Trait Bounds の高度な使用
4.3.1 複雑な HRTB
// 関数を受け取る関数
fn apply_to_all<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s1 = String::from("hello");
let s2 = String::from("world");
let r1 = f(&s1);
let r2 = f(&s2);
println!("{} {}", r1, r2);
}
// 使用例
fn main() {
apply_to_all(|s| {
if s.len() > 5 {
&s[..5]
} else {
s
}
});
}
4.3.2 HRTB とトレイト
trait Processor {
fn process<'a>(&self, input: &'a str) -> &'a str;
}
struct Upper;
impl Processor for Upper {
fn process<'a>(&self, input: &'a str) -> &'a str {
// 実際の実装は省略
input
}
}
// HRTB を使った境界
fn use_processor<P>(processor: P)
where
P: for<'a> Fn(&'a str) -> &'a str,
{
let result = processor("test");
println!("{}", result);
}
---
4.4 ライフタイムとクロージャ
4.4.1 クロージャのキャプチャライフタイム
fn closure_lifetime<'a>(s: &'a str) -> impl Fn() -> &'a str + 'a {
// ^^
// クロージャの境界
move || s
}
// より複雑な例
fn complex_closure<'a, 'b>(
s1: &'a str,
s2: &'b str,
) -> impl Fn() -> (&'a str, &'b str)
where
'a: 'b,
{
move || (s1, s2)
}
4.4.2 クロージャとHRTB
// クロージャをHRTBで制約
fn accepts_closure<F>(f: F)
where
F: for<'a, 'b> Fn(&'a str, &'b str) -> bool,
{
let s1 = String::from("hello");
let s2 = String::from("world");
println!("{}", f(&s1, &s2));
}
fn main() {
accepts_closure(|a, b| a.len() > b.len());
}
---
4.5 実践的なデザインパターン
4.5.1 ビルダーパターンとライフタイム
struct Query<'a> {
table: &'a str,
conditions: Vec<(&'a str, &'a str)>,
}
struct QueryBuilder<'a> {
table: Option<&'a str>,
conditions: Vec<(&'a str, &'a str)>,
}
impl<'a> QueryBuilder<'a> {
fn new() -> Self {
QueryBuilder {
table: None,
conditions: Vec::new(),
}
}
fn table(mut self, table: &'a str) -> Self {
self.table = Some(table);
self
}
fn where_clause(mut self, key: &'a str, value: &'a str) -> Self {
self.conditions.push((key, value));
self
}
fn build(self) -> Result<Query<'a>, &'static str> {
Ok(Query {
table: self.table.ok_or("table is required")?,
conditions: self.conditions,
})
}
}
4.5.2 イテレータパターン
struct Parser<'a> {
input: &'a str,
position: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, position: 0 }
}
}
impl<'a> Iterator for Parser<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.position >= self.input.len() {
return None;
}
let start = self.position;
while self.position < self.input.len()
&& !self.input[self.position..].starts_with(' ')
{
self.position += 1;
}
let end = self.position;
self.position += 1; // スペースをスキップ
Some(&self.input[start..end])
}
}
---
4.6 コンパイラとの対話
4.6.1 エラーメッセージの読み方
fn example() {
let s1 = String::from("hello");
let r;
{
let s2 = String::from("world");
r = longest(&s1, &s2);
}
println!("{}", r);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
エラーメッセージ:
error[E0597]: `s2` does not live long enough
--> src/main.rs:7:25
|
7 | r = longest(&s1, &s2);
| ^^^ borrowed value does not live long enough
8 | }
| - `s2` dropped here while still borrowed
9 |
10 | println!("{}", r);
| - borrow later used here
解決策:
// ライフタイムを分ける
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where
'a: 'b,
{
x // yを返さない
}
4.6.2 rustc --explain
$ rustc --explain E0597
# ライフタイムエラーの詳細な説明が表示される
---
4.7 パフォーマンス最適化
4.7.1 ゼロコピーパース
// コピーを避けるデザイン
struct JsonValue<'a> {
raw: &'a str,
}
impl<'a> JsonValue<'a> {
fn parse(input: &'a str) -> Result<Self, Error> {
// input をコピーせずに参照を保持
Ok(JsonValue { raw: input })
}
fn as_str(&self) -> &'a str {
self.raw
}
}
// 使用例
fn parse_config(json: &str) -> Result<Config, Error> {
let value = JsonValue::parse(json)?;
// value は json のライフタイムに依存
Ok(Config::from_json(value))
}
4.7.2 Cow の活用
use std::borrow::Cow;
fn process<'a>(input: &'a str) -> Cow<'a, str> {
if input.contains("bad") {
// 変更が必要な場合のみ所有
Cow::Owned(input.replace("bad", "good"))
} else {
// 変更不要なら借用
Cow::Borrowed(input)
}
}
fn main() {
let s1 = "hello world";
let s2 = "bad example";
let r1 = process(s1); // Cow::Borrowed
let r2 = process(s2); // Cow::Owned
println!("{} {}", r1, r2);
}
---
4.8 ライフタイムのテスト
4.8.1 コンパイル失敗テスト
#[cfg(test)]
mod tests {
use super::*;
// このテストはコンパイルエラーになるべき
// compile_fail 属性でテスト
#[cfg(doctest)]
/// compile_fail
/// fn invalid_lifetime() {
/// let r;
/// {
/// let x = 5;
/// r = &x;
/// }
/// println!("{}", r);
/// }
/// fn test_lifetime_error() {}
}
4.8.2 実行時テスト
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lifetime_correctness() {
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest(&s1, &s2);
assert_eq!(result, "long string");
// s1, s2 がまだ有効
}
}
---
4.9 プロダクションコードのベストプラクティス
4.9.1 ドキュメント
/// パーサーを作成します。
///
/// # Lifetimes
///
/// * `'a` - 入力文字列のライフタイム
/// * `'b` - 設定オブジェクトのライフタイム
///
/// パーサーは入力と設定の両方より長く生きることはできません。
///
/// # Examples
///
///
/// let input = "example input";
/// let config = Config::default();
/// let parser = Parser::new(input, &config);
/// pub struct Parser<'a, 'b> {
input: &'a str,
config: &'b Config,
}
4.9.2 エラーハンドリング
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ParseError {
#[error("無効な入力: {0}")]
InvalidInput(String),
#[error("ライフタイムエラー")]
LifetimeError,
}
pub fn parse<'a>(input: &'a str) -> Result<Value<'a>, ParseError> {
if input.is_empty() {
return Err(ParseError::InvalidInput("空の入力".to_string()));
}
Ok(Value { data: input })
}
---
4.10 まとめ
4.10.1 ライフタイムマスタリーのチェックリスト
✓ ライフタイムの形式的理解
✓ 省略規則の完全な理解
✓ HRTB の使いこなし
✓ Pin と自己参照の理解
✓ トレイトオブジェクトとライフタイム
✓ パフォーマンス最適化
✓ エラーメッセージの解読
✓ プロダクションコードへの応用
4.10.2 次のステップ
- 実践
- 深掘り
- コミュニティ
---
練習問題
問題1
複雑なライフタイム関係を持つAPIを設計し、そのライフタイム制約を説明しなさい。問題2
HRTBを使った汎用的な関数を実装し、その利点を説明しなさい。問題3
ゼロコピーパーサーを実装し、パフォーマンスを測定しなさい。---
コース完了
Rust Lifetime Mastery コース完了おめでとうございます!
あなたは now Rust のライフタイムシステムを深く理解しました:
- 理論的基盤
- 実践的なパターン
- 高度なテクニック
- プロダクションでのベストプラクティス
次のステップ:
- 実際のプロジェクトでこの知識を活用
- より深いコンパイラの理解
- コミュニティへの貢献
Happy Coding with Rust! 🦀