Skip to content
TwitterQiitaGitHub

Rust の所有権システム

所有権システムの利点

  • ガベージコレクタが不要になる
    • プログラムのランタイムが軽量化される
    • 応答時間やメモリ使用量が予測しやすくなる
  • メモリ安全性がコンパイル時に保証される
    • メモリの二重解放による未定義動作を起こさない
    • 不正なポインタ(ダングリングポインタ)を作らない
  • メモリだけでなく、ファイルなどロックした終わったリソースが使い時点で自動解放できる

所有権システムの概要

所有権システムの役割

  1. リソースの自動解放
  • 値が不要になったら、それが使用していたリソースを速やかに、ただ一度だけ解放する
  • 解放漏れによるリソースリークの防止
  • 二重開放による未定義動作の防止
  • 「リソース」とは、以下のようなものを指す:
    • メモリ
    • ファイルディスクリプタ(開いているファイルへのハンドル)
    • ソケットなどのネットワークリソース
    • マルチスレッドプログラミングにおける排他制御用のロック
  1. ダングリングポインタの防止

所有権システムを実現するもの

所有権(ownership)

  • 所有権はある値を所有できる権利
  • 所有者は所有権を持つ者
  • 変数が値の所有者になれる
  • 値自身も他の値の所有者になれる
  • 値には所有権が一つだけある:値の所有者はある時点でただ一人だけ
  • 所有者は値を指す可変・不変の参照を作ることで、他者に値を貸し出せる
  • 所有者は所有権を他者に譲渡(移動)できる:これにより、もとの所有者は所有権を失う
  • 所有者がスコープを抜けるときに、値のライフタイムが尽きる
    • その時点で値が破棄され、使用していたリソースが解放される

ムーブセマンティクスとコピーセマンティクス(move & copy semantics)

  • ある変数から別の変数へ値を代入するとき、値の型によってプログラムの意味(セマンティクス)が変わる
    • ムーブセマンティクス:代入元の変数から代入先へ所有権が移動(ムーブ)する
      • 代入元から代入先へ所有権が移動する
    • コピーセマンティクス:値が複製(コピー)されたとみなす:所有権は移動しない
      • 代入元の変数はもとの値をそのまま所有
      • 代入先の変数は複数された値を所有

借用(borrow)

  • 値を指す参照を作ると、所有権の観点からは値を借用していることになる
  • 借用には、不変の借用と可変の借用がある

ライフタイム(lifetime)

  • ライフタイムは生存期間
    • 値のライフタイム:値が構築されてから破棄されるまでの期間
      • 「値のスコープ」と呼ばれる
    • 参照のライフタイム:値への参照が使用される期間

借用規則

  1. 不変・可変を問わず、参照のライフタイムが値のスコープよりも短いこと
  2. 値が共有されている間(不変の参照が有効な間)は値の変更を許さない
  • ある値 T について、以下のいずれかの状態のみを許す
    • 任意個の不変の参照 &T を持つ
    • ただ一つの可変の借用 &mut T を持つ
  • 借用規則が守られているかは、コンパイラの借用チェッカ(borrow checker)によって検査される

デストラクタ

  • 構造体や列挙型で値が破棄される直前に終了処理を行うための特別な関数をデストラクタと呼ぶ
  • std::opt::Drop トレイトを通して実装できる
    • drop メソッドは値が破棄される直前に暗黙的に呼ばれる
    • 明示的に呼ぼうとするとコンパイルエラー
// Parent構造体にデストラクタを実装する
impl Drop for Parent {
fn drop(&mut self) {
println!("Dropping {:?}", self);
}
}
  • 値の破棄の意図的な遅延をさせたり、デストラクタを実行させない方法もある
    • std::mem::forget
    • Box::info_raw
    • Box::leak
  • 上記3つはアンセーフな操作には分類されない

ムーブセマンティクス

所有権の移動を伴う操作は以下:

  1. パターンマッチ:match 式だけでなく、let 文による変数の束縛も含む
  2. 関数呼び出し
  3. 関数やブロックからのリターン
  4. コンストラクタ
  5. move クロージャ

コピーセマンティクス

  • 構造体や列挙型に std::maker::Copy を実装すると、値はムーブせずにコピーされるようになる
  • Copy トレイトを実装できる条件は以下:
    • その型のすべてのフィールド型が Copy トレイトを実装している
      • 例えば、 i32 は Copy トレイトを実装している
    • その型自身と全てのフィールドがデストラクタを実装していない
      • Box<T>, Vec<T>, String はデストラクタを持つ
    • その型自身が std::clone::Clone トレイトを実装している

Copy トレイトを実装する主な型

  • すべてのスカラ型

  • 不変の参照 &T 型、生ポインタ const T 型と mut T

    • 可変の参照 &mut T は Copy を実装しない
  • 関数ポインタ型と関数定義型

  • すべての要素に Copy な型を持つタプル型と配列型

  • 環境に何も捕捉しない、あまたは、 Copy な型だけを捕捉したクロージャ型

  • すべての要素が Copy な型を持つ Option<T> 型と result<T>

  • std::cmp::Ordering, std::net::Ip::Addr, std::maker::PhantomData<T>

  • Copy と Clone の違い

    トレイトコピーの実行コピーの処理内容コピー時のコスト
    Copy暗黙的。所有権が移動する場面で、移動せずにコピーされる。単純なバイトレベルのコピーで、ロジックのカスタマイズはできない。低い
    Clone明示的。clone メソッドによりコピーされる。シンプルなロジックから複雑なロジックまで、自由に実装可能。処理内容と値に依存して高いか低いかが決まる。

ライフタイム

  • 参考になる資料
  • 関数の引数と戻り値に参照が現れるとき、それらの関係を示すためにライフタイム指定子を付ける。
  • ライフタイムの省略:コンパイラは以下の規則に基づいてライフタイムを推論する
    • 関数の戻り値の型が参照型のとき、
      • 引数の中で参照型が1つだけなら、その引数から借用する
      • 第1引数が &self または &mut self のメソッドなら、他の参照型の引数があっても self から借用する
      • それ以外の場合はライフタイムは省略できないため、コンパイルエラーになる
  • 'static ライフタイムは、プログラムの終了時まで続く
    • static 変数の値、または、リテラルなど、コンパイル時に値が確定するものからしか作れない