Chapter 4: 高度なライフタイムパターン
この章の目標
この章を読み終えると、以下のことが理解できるようになります:
- Generic Associated Types(GATs)の理論と実践
- Lending Iterators vs Streaming Iterators
- Async関数での高度なライフタイム管理
- 高度なトレイト境界(trait bounds)のテクニック
- 実世界の複雑なライフタイム問題の解決
- WindowsIterator: 可変参照を返すイテレータ
- StreamingParser: 毎回異なるライフタイムでデータを返す
- AsyncStream: 非同期データストリーム
- Database Cursor: クエリ結果の遅延評価
なぜ高度なパターンが必要か
基本的なライフタイムだけでは解決できない問題があります:
これらは最新のRust機能を駆使して実現されます。
---
1. Generic Associated Types(GATs)
1.1 GATsとは何か
背景:
- Rust 1.65(2022年11月)で安定化
- トレイトの関連型にジェネリクスを追加
- 長年の課題(RFC 1598: 2016年)がついに解決
基本的な関連型(GATs以前):
trait Container {
type Item; // ← 固定された型
fn get(&self) -> Option<&Self::Item>;
}
// 使用例
impl Container for Vec<i32> {
type Item = i32;
fn get(&self) -> Option<&i32> {
self.first()
}
}
Generic Associated Types(GATs):
trait Container {
type Item<'a> where Self: 'a; // ← ライフタイムパラメータ!
fn get<'a>(&'a self) -> Option<Self::Item<'a>>;
}
// 使用例
impl Container for Vec<i32> {
type Item<'a> = &'a i32;
fn get<'a>(&'a self) -> Option<&'a i32> {
self.first()
}
}
1.2 なぜGATsが必要か
問題: Lending Iterator
// GATs以前は表現不可能
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
// ^^ ^^
// selfのライフタイムとItemのライフタイムが連動
}
通常のIteratorとの違い:
// 標準のIterator(所有権を移動)
trait Iterator {
type Item; // ライフタイムなし
fn next(&mut self) -> Option<Self::Item>;
// ^^^^^^^^^^^
// 所有された値を返す
}
// LendingIterator(借用を返す)
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
// ^^^^^^^^^^^
// 借用を返す(selfに紐付く)
}
視覚化:
通常のIterator:
┌─────────────┐
│ Iterator │
│ ┌────────┐ │
│ │ items │ │
│ └────────┘ │
└─────────────┘
│
│ next()
▼
Item(所有)
LendingIterator:
┌─────────────┐
│ LIterator │
│ ┌────────┐ │
│ │ items │ │ ◄────┐
│ └────────┘ │ │
└─────────────┘ │
│ │
│ next() │
▼ │
&'a Item ─────────┘
(借用)
1.3 GATsの実装例
WindowsIterator: スライディングウィンドウ
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}
struct Windows<'data, T> {
data: &'data [T],
window_size: usize,
position: usize,
}
impl<'data, T> Windows<'data, T> {
fn new(data: &'data [T], window_size: usize) -> Self {
Windows {
data,
window_size,
position: 0,
}
}
}
impl<'data, T> LendingIterator for Windows<'data, T> {
type Item<'a> = &'a [T] where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
if self.position + self.window_size > self.data.len() {
return None;
}
let window = &self.data[self.position..self.position + self.window_size];
self.position += 1;
Some(window)
}
}
使用例:
fn main() {
let data = vec![1, 2, 3, 4, 5];
let mut windows = Windows::new(&data, 3);
while let Some(window) = windows.next() {
println!("{:?}", window);
}
// 出力:
// [1, 2, 3]
// [2, 3, 4]
// [3, 4, 5]
}
メモリレイアウト:
data: [1, 2, 3, 4, 5]
↑
└─────┬─────────┐
Windows │ │
┌───────────┬┴┬────────┴┐
│ data: & │ │ size: 3 │
│ pos: 0 │ │ │
└───────────┴─┴─────────┘
│
next() │
▼
&[1, 2, 3] ← 'a のライフタイム
│
next() │
▼
&[2, 3, 4] ← 新しい 'a のライフタイム
1.4 高度なGATs: 複数のパラメータ
ジェネリック型とライフタイム:
trait Database {
type Query<'db, T>
where
Self: 'db,
T: 'db;
fn query<'db, T>(&'db self) -> Self::Query<'db, T>
where
T: Deserialize<'db>;
}
struct PostgresDB {
conn: Connection,
}
impl Database for PostgresDB {
type Query<'db, T> = PostgresQuery<'db, T>
where
Self: 'db,
T: 'db;
fn query<'db, T>(&'db self) -> PostgresQuery<'db, T>
where
T: Deserialize<'db>,
{
PostgresQuery {
db: self,
_marker: PhantomData,
}
}
}
struct PostgresQuery<'db, T> {
db: &'db PostgresDB,
_marker: PhantomData<T>,
}
---
2. Lending Iterators vs Streaming Iterators
2.1 Lending Iteratorの深掘り
定義と特徴:
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
// for_eachは実装できない!
// fn for_each<F>(mut self, f: F)
// where
// F: FnMut(Self::Item<'???>) -> (), // ライフタイムが決まらない
}
制限事項:
実例: チャンク化された可変参照
struct ChunksMut<'data, T> {
data: &'data mut [T],
chunk_size: usize,
}
impl<'data, T> ChunksMut<'data, T> {
fn new(data: &'data mut [T], chunk_size: usize) -> Self {
ChunksMut { data, chunk_size }
}
}
impl<'data, T> LendingIterator for ChunksMut<'data, T> {
type Item<'a> = &'a mut [T] where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
if self.data.is_empty() {
return None;
}
let chunk_size = self.chunk_size.min(self.data.len());
// 重要: std::mem::take を使って一時的に所有権を移動
let data = std::mem::take(&mut self.data);
let (chunk, rest) = data.split_at_mut(chunk_size);
self.data = rest;
Some(chunk)
}
}
使用例:
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6];
let mut chunks = ChunksMut::new(&mut data, 2);
while let Some(chunk) = chunks.next() {
// 各チャンクを変更可能
chunk[0] *= 10;
println!("{:?}", chunk);
}
println!("{:?}", data); // [10, 2, 30, 4, 50, 6]
}
2.2 Streaming Iteratorパターン
streaming-iteratorクレート:
trait StreamingIterator {
type Item;
fn advance(&mut self);
fn get(&self) -> Option<&Self::Item>;
// 便利メソッド
fn next(&mut self) -> Option<&Self::Item> {
self.advance();
self.get()
}
}
Lending Iteratorとの違い:
Lending Iterator:
- next() が &'a mut self を取る
- 毎回新しいライフタイム
Streaming Iterator:
- advance() と get() を分離
- get() は &self なので複数回呼べる
実装例: ウィンドウストリーミング
struct StreamingWindows<'data, T> {
data: &'data [T],
window_size: usize,
current: usize,
}
impl<'data, T> StreamingIterator for StreamingWindows<'data, T> {
type Item = [T];
fn advance(&mut self) {
if self.current + self.window_size < self.data.len() {
self.current += 1;
} else {
self.current = self.data.len();
}
}
fn get(&self) -> Option<&[T]> {
if self.current + self.window_size <= self.data.len() {
Some(&self.data[self.current..self.current + self.window_size])
} else {
None
}
}
}
使用例:
use streaming_iterator::StreamingIterator;
fn main() {
let data = vec![1, 2, 3, 4, 5];
let mut windows = StreamingWindows {
data: &data,
window_size: 3,
current: 0,
};
// 現在のウィンドウに複数回アクセス可能
while let Some(window) = windows.next() {
println!("Current: {:?}", window);
// もう一度同じウィンドウを見れる
if let Some(same) = windows.get() {
println!("Same: {:?}", same);
}
// for_eachも実装可能
}
}
---
3. Async関数での高度なライフタイム管理
3.1 Async Functionのライフタイム推論
基本的なasync関数:
async fn fetch_user(id: u64) -> User {
// 実装
User { id, name: "Alice".to_string() }
}
// 展開後(概念):
fn fetch_user(id: u64) -> impl Future<Output = User> {
async move {
User { id, name: "Alice".to_string() }
}
}
参照を含むasync関数:
async fn process<'a>(data: &'a [u8]) -> Result<&'a str, Error> {
// data への参照を保持したまま非同期処理
let parsed = parse_async(data).await?;
Ok(parsed)
}
// 展開後:
fn process<'a>(data: &'a [u8]) -> impl Future<Output = Result<&'a str, Error>> + 'a {
// ^^^
// Futureは 'a に束縛される
async move {
let parsed = parse_async(data).await?;
Ok(parsed)
}
}
3.2 AsyncStream とライフタイム
async-streamクレート:
use async_stream::stream;
use futures::Stream;
fn range_stream(n: u32) -> impl Stream<Item = u32> {
stream! {
for i in 0..n {
yield i;
}
}
}
参照を返すStream:
fn lending_stream<'a>(data: &'a [String]) -> impl Stream<Item = &'a str> + 'a {
stream! {
for s in data {
yield s.as_str();
}
}
}
// 使用例
#[tokio::main]
async fn main() {
let data = vec!["hello".to_string(), "world".to_string()];
let mut s = lending_stream(&data);
while let Some(item) = s.next().await {
println!("{}", item);
}
}
3.3 複雑なAsync Lifetimeパターン
複数の参照と非同期処理:
use tokio::io::{AsyncRead, AsyncWrite};
async fn bidirectional_copy<'a, R, W>(
reader: &'a mut R,
writer: &'a mut W,
) -> std::io::Result<(u64, u64)>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
// reader と writer を並行して使用
tokio::select! {
result = copy_data(reader, writer) => result,
_ = tokio::signal::ctrl_c() => {
Ok((0, 0))
}
}
}
async fn copy_data<R, W>(
reader: &mut R,
writer: &mut W,
) -> std::io::Result<(u64, u64)>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
// 実装省略
Ok((0, 0))
}
AsyncトレイトとGATs:
trait AsyncDatabase {
type Transaction<'db>: AsyncTransaction
where
Self: 'db;
async fn begin_transaction<'db>(&'db mut self) -> Self::Transaction<'db>;
}
trait AsyncTransaction {
async fn execute(&mut self, query: &str) -> Result<(), Error>;
async fn commit(self) -> Result<(), Error>;
}
// 実装例
struct PostgresDB {
pool: Pool,
}
impl AsyncDatabase for PostgresDB {
type Transaction<'db> = PostgresTransaction<'db>
where
Self: 'db;
async fn begin_transaction<'db>(&'db mut self) -> PostgresTransaction<'db> {
PostgresTransaction {
db: self,
queries: Vec::new(),
}
}
}
struct PostgresTransaction<'db> {
db: &'db mut PostgresDB,
queries: Vec<String>,
}
impl AsyncTransaction for PostgresTransaction<'_> {
async fn execute(&mut self, query: &str) -> Result<(), Error> {
self.queries.push(query.to_string());
Ok(())
}
async fn commit(self) -> Result<(), Error> {
// 実装
Ok(())
}
}
---
4. 高度なトレイト境界
4.1 Higher-Ranked Trait Bounds(復習と深掘り)
for<'a>の詳細:
// すべてのライフタイムに対して成立
fn apply<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s = String::from("test");
let result = f(&s);
println!("{}", result);
}
HRTBsとGATsの組み合わせ:
trait HigherKindedIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
fn filter<F>(self, predicate: F) -> Filter<Self, F>
where
F: for<'a> FnMut(&Self::Item<'a>) -> bool,
// ^^^^^^^^ すべてのライフタイムで動作
Self: Sized,
{
Filter {
iter: self,
predicate,
}
}
}
struct Filter<I, F> {
iter: I,
predicate: F,
}
impl<I, F> LendingIterator for Filter<I, F>
where
I: LendingIterator,
F: for<'a> FnMut(&I::Item<'a>) -> bool,
{
type Item<'a> = I::Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
while let Some(item) = self.iter.next() {
if (self.predicate)(&item) {
return Some(item);
}
}
None
}
}
4.2 型レベルライフタイム制約
複雑なwhere句:
trait ComplexTrait<'a, 'b>
where
'a: 'b, // 'a は 'b より長生き
{
type Output: 'a;
fn process(&'a self, data: &'b str) -> Self::Output;
}
// 実装例
struct Processor<'data> {
cache: &'data Cache,
}
impl<'a, 'b> ComplexTrait<'a, 'b> for Processor<'a>
where
'a: 'b,
{
type Output = ProcessedData<'a>;
fn process(&'a self, data: &'b str) -> ProcessedData<'a> {
// cache('a) と data('b) を使用
// 'a: 'b により、cacheはdataより長生き
ProcessedData {
result: data,
cache_ref: self.cache,
}
}
}
struct Cache;
struct ProcessedData<'a> {
result: &'a str,
cache_ref: &'a Cache,
}
4.3 型制約の伝播
複雑なジェネリック境界:
trait Repository {
type Entity<'e>: Entity
where
Self: 'e;
fn find<'e>(&'e self, id: u64) -> Option<Self::Entity<'e>>;
}
trait Entity {
fn id(&self) -> u64;
}
// 複数のトレイトを組み合わせ
fn process_entities<'a, R, F>(repo: &'a R, mut func: F)
where
R: Repository,
F: for<'e> FnMut(R::Entity<'e>),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// すべてのライフタイムで動作する関数
{
for i in 0..10 {
if let Some(entity) = repo.find(i) {
func(entity);
}
}
}
---
5. 実世界のケーススタディ
5.1 データベースクエリビルダー
設計:
trait QueryBuilder {
type Query<'q>: Query
where
Self: 'q;
fn select<'q>(&'q mut self, columns: &[&str]) -> Self::Query<'q>;
}
trait Query {
fn where_clause(&mut self, condition: &str) -> &mut Self;
fn execute(&self) -> Result<Rows, Error>;
}
struct SqlBuilder {
connection: Connection,
}
struct SqlQuery<'builder> {
builder: &'builder SqlBuilder,
select_cols: Vec<String>,
where_clauses: Vec<String>,
}
impl QueryBuilder for SqlBuilder {
type Query<'q> = SqlQuery<'q> where Self: 'q;
fn select<'q>(&'q mut self, columns: &[&str]) -> SqlQuery<'q> {
SqlQuery {
builder: self,
select_cols: columns.iter().map(|s| s.to_string()).collect(),
where_clauses: Vec::new(),
}
}
}
impl Query for SqlQuery<'_> {
fn where_clause(&mut self, condition: &str) -> &mut Self {
self.where_clauses.push(condition.to_string());
self
}
fn execute(&self) -> Result<Rows, Error> {
// SQL実行
Ok(Rows { data: Vec::new() })
}
}
// 使用例
fn main() {
let mut builder = SqlBuilder { connection: Connection };
let rows = builder
.select(&["id", "name"])
.where_clause("age > 18")
.where_clause("active = true")
.execute()
.unwrap();
}
struct Connection;
struct Rows {
data: Vec<String>,
}
struct Error;
5.2 パーサーコンビネータ
ゼロコピーパーサー:
trait Parser<'input> {
type Output;
type Error;
fn parse(&self, input: &'input str) -> Result<(Self::Output, &'input str), Self::Error>;
}
// コンビネータ: map
struct Map<P, F> {
parser: P,
func: F,
}
impl<'input, P, F, O> Parser<'input> for Map<P, F>
where
P: Parser<'input>,
F: Fn(P::Output) -> O,
{
type Output = O;
type Error = P::Error;
fn parse(&self, input: &'input str) -> Result<(O, &'input str), P::Error> {
let (output, rest) = self.parser.parse(input)?;
Ok(((self.func)(output), rest))
}
}
// 文字列リテラル パーサー
struct Literal<'a> {
expected: &'a str,
}
impl<'input, 'a> Parser<'input> for Literal<'a> {
type Output = &'input str;
type Error = &'static str;
fn parse(&self, input: &'input str) -> Result<(&'input str, &'input str), Self::Error> {
if input.starts_with(self.expected) {
Ok((&input[..self.expected.len()], &input[self.expected.len()..]))
} else {
Err("literal not found")
}
}
}
5.3 非同期ストリーム処理
実装例:
use async_trait::async_trait;
#[async_trait]
trait AsyncStream {
type Item;
async fn next(&mut self) -> Option<Self::Item>;
}
struct FileLineStream<'a> {
reader: &'a mut tokio::io::BufReader<tokio::fs::File>,
buffer: String,
}
#[async_trait]
impl AsyncStream for FileLineStream<'_> {
type Item = String;
async fn next(&mut self) -> Option<String> {
use tokio::io::AsyncBufReadExt;
self.buffer.clear();
match self.reader.read_line(&mut self.buffer).await {
Ok(0) => None,
Ok(_) => Some(self.buffer.clone()),
Err(_) => None,
}
}
}
---
まとめ
重要なポイント
- Lending vs Streaming:
- Async Lifetimes:
- 高度なトレイト境界:
Rustライフタイムのマスタリー
ここまで学んだ内容で、Rustのライフタイムシステムを完全に理解しました:
- Chapter 1: 形式的理解(Tofte-Talpin、Variance)
- Chapter 2: 複雑なパターン(複数ライフタイム、省略規則)
- Chapter 3: 自己参照構造体(Pin
、Future) - Chapter 4: 最先端パターン(GATs、Async)
- 自作データベースライブラリ
- パーサーコンビネータライブラリ
- 非同期フレームワーク
- Rustコンパイラへの貢献
これで、どんなライフタイム問題も解決できるスキルを身につけました。
次のステップ
実践的なプロジェクトでこれらの知識を活用しましょう:
ライフタイムは、Rustの最も強力な機能です。この知識を武器に、安全で高速なソフトウェアを構築してください。