Rust の所有権システム
所有権システムの利点
- ガベージコレクタが不要になる
- プログラムのランタイムが軽量化される
- 応答時間やメモリ使用量が予測しやすくなる
- メモリ安全性がコンパイル時に保証される
- メモリの二重解放による未定義動作を起こさない
- 不正なポインタ(ダングリングポインタ)を作らない
- メモリだけでなく、ファイルなどロックした終わったリソースが使い時点で自動解放できる
所有権システムの概要
所有権システムの役割
- リソースの自動解放
- 値が不要になったら、それが使用していたリソースを速やかに、ただ一度だけ解放する
- 解放漏れによるリソースリークの防止
- 二重開放による未定義動作の防止
- 「リソース」とは、以下のようなものを指す:
- メモリ
- ファイルディスクリプタ(開いているファイルへのハンドル)
- ソケットなどのネットワークリソース
- マルチスレッドプログラミングにおける排他制御用のロック
- ダングリングポインタの防止
所有権システムを実現するもの
所有権(ownership)
- 所有権はある値を所有できる権利
- 所有者は所有権を持つ者
- 変数が値の所有者になれる
- 値自身も他の値の所有者になれる
- 値には所有権が一つだけある:値の所有者はある時点でただ一人だけ
- 所有者は値を指す可変・不変の参照を作ることで、他者に値を貸し出せる
- 所有者は所有権を他者に譲渡(移動)できる:これにより、もとの所有者は所有権を失う
- 所有者がスコープを抜けるときに、値のライフタイムが尽きる
- その時点で値が破棄され、使用していたリソースが解放される
ムーブセマンティクスとコピーセマンティクス(move & copy semantics)
- ある変数から別の変数へ値を代入するとき、値の型によってプログラムの意味(セマンティクス)が変わる
- ムーブセマンティクス:代入元の変数から代入先へ所有権が移動(ムーブ)する
- 代入元から代入先へ所有権が移動する
- コピーセマンティクス:値が複製(コピー)されたとみなす:所有権は移動しない
- 代入元の変数はもとの値をそのまま所有
- 代入先の変数は複数された値を所有
- ムーブセマンティクス:代入元の変数から代入先へ所有権が移動(ムーブ)する
借用(borrow)
- 値を指す参照を作ると、所有権の観点からは値を借用していることになる
- 借用には、不変の借用と可変の借用がある
ライフタイム(lifetime)
- ライフタイムは生存期間
- 値のライフタイム:値が構築されてから破棄されるまでの期間
- 「値のスコープ」と呼ばれる
- 参照のライフタイム:値への参照が使用される期間
- 値のライフタイム:値が構築されてから破棄されるまでの期間
借用規則
- 不変・可変を問わず、参照のライフタイムが値のスコープよりも短いこと
- 値が共有されている間(不変の参照が有効な間)は値の変更を許さない
- ある値
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つはアンセーフな操作には分類されない
ムーブセマンティクス
所有権の移動を伴う操作は以下:
- パターンマッチ:match 式だけでなく、let 文による変数の束縛も含む
- 関数呼び出し
- 関数やブロックからのリターン
- コンストラクタ
- move クロージャ
コピーセマンティクス
- 構造体や列挙型に
std::maker::Copy
を実装すると、値はムーブせずにコピーされるようになる Copy
トレイトを実装できる条件は以下:- その型のすべてのフィールド型が Copy トレイトを実装している
- 例えば、
i32
は Copy トレイトを実装している
- 例えば、
- その型自身と全てのフィールドがデストラクタを実装していない
Box<T>
,Vec<T>
,String
はデストラクタを持つ
- その型自身が
std::clone::Clone
トレイトを実装している
- その型のすべてのフィールド型が Copy トレイトを実装している
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
変数の値、または、リテラルなど、コンパイル時に値が確定するものからしか作れない