Rust入門|所有権・借用・ライフタイムを図解で理解

プログラミング言語

Rust入門の最大の山が「所有権・借用・ライフタイム」です。コンパイル時にメモリ安全性を保証するこのモデルを理解すれば、Rustの世界が一気に開けます。本記事では、3つの概念を図解的にわかりやすく解説します。

所有権(Ownership)の3つのルール

  • 各値には1つの所有者がある
  • 所有者がスコープを抜けると値は解放される
  • 所有権はmoveで移動する(参照の手渡し)
let s1 = String::from("hello");
let s2 = s1;          // 所有権がs2へmove
// println!("{}", s1); // ← エラー:s1は使えない

「コピーじゃないの?」と感じるかもしれませんが、これがメモリ二重解放を防ぐ仕組みです。i32などCopyトレイトを実装する型は例外的にコピーされます。

借用(Borrow)

所有権を渡さずに「参照(&)」で借りるのが借用です。&Tが不変借用、&mut Tが可変借用です。

fn print_len(s: &String) {  // 借用
    println!("{}", s.len());
}

let s = String::from("hello");
print_len(&s);   // 借用
println!("{}", s); // sはまだ使える

借用のルール

  • 同時に複数の不変借用はOK
  • 可変借用は1つだけ
  • 不変借用と可変借用は同時に存在できない

これによりデータ競合がコンパイル時に防がれます。

ライフタイム(Lifetime)

参照が指し示す値が「いつまで生きているか」をコンパイラに伝える注釈です。多くの場合は省略可能(lifetime elision)。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

'aは「xとyが両方有効な期間」を表し、返り値もその期間内で有効であることを保証します。

よくあるパターン

  • 関数の引数は基本的に借用(&T、&mut T)
  • 所有権を渡したい時だけ T(move)を渡す
  • 構造体に参照を持たせる時はライフタイム明示が必要
  • 長く保持したい時はString・Box・Rc・Arcで所有権を持つ

所有権が拒否された時の対処

  • cloneして所有権を回避(許される範囲で)
  • 関数シグネチャを&T借用に変更
  • 所有権を返す設計に変える
  • Rc/RefCellで動的な所有関係を表現

まとめ

所有権・借用・ライフタイムは「メモリ安全性を実行時オーバーヘッドなしで保証する」Rust独自の仕組みです。最初は厳しい制約に感じますが、慣れればGCなし・データ競合なし・速いという特性が当たり前に得られます。最初の壁を越えれば、Rustが書きやすくなります。