所有権

Essential
最終更新: タグ: Rust, 所有権

RAIIはすべてのリソースに所有者を与え、スコープ終了時のクリーンアップを保証する。所有権はRustがその保証を厳密かつ検証可能にするためのコンパイル時のシステムだ。このチェックポイントはルールを明示する——3つの所有権の公理、変数間で値がムーブ(move)する仕組み、そして代わりに CopyClone が適用される場合を扱う。

3つの所有権ルール

Rustのすべての値は、同時に3つのルールを満たす。

  1. すべての値にはちょうど1人の所有者がいる——それを保持する変数バインディングだ。
  2. 所有者がスコープを抜けると、値はドロップされる——クリーンアップが自動的に実行される。
  3. 所有権は新しい所有者へ移譲できる。その後、元の所有者はその値を使えなくなる。

この3つのルールで、リソースのリークも二重解放も保証なく防げる。

ムーブセマンティクス

別の変数に値を代入するか、関数に値を渡すと、所有権がムーブする。元のバインディングは永続的に無効になる。

let s = String::from("hello"); // s がヒープ割り当てを所有する
let t = s;                     // 所有権が s から t にムーブする
// println!("{}", s);          // コンパイルエラー: 値がムーブ済み
println!("{}", t);             // ok

これはコピーではない——新しいヒープ割り当ては作られない。ts が保持していた同じ割り当てを保持する。コンパイラは s をムーブ済みとしてマークし、その後の使用をすべて拒否する。

関数に値を渡す場合も同じルールが適用される。

fn consume(s: String) {
    println!("{}", s);
} // s はここでドロップされる。ヒープ割り当てが解放される

let greeting = String::from("hi");
consume(greeting);
// greeting はもう有効ではない — consume にムーブされた

ムーブできるもの

型が値渡しでムーブ可能であるためには、そのサイズがコンパイル時に既知でなければならない。Sized マーカートレイトが形式的な要件だ。すべてのジェネリック型パラメータには暗黙の T: Sized 境界がある。スライス [T]str、トレイトオブジェクト dyn Trait などのサイズ不定型(unsized type)は、所有された値として保持できない。参照の背後にのみ現れられる。

Copy: 暗黙に複製するムーブ

Copy を実装する型は、「ムーブ」されるたびに**ビット単位で複製(bitwise-duplicated)**される。元のバインディングは有効なまま残る。

let x: i32 = 5;
let y = x;         // x はムーブではなくコピー(ビット単位)される
println!("{}", x); // x はまだ有効

Copy はビット単位のコピーが意味的に正しく安価な型に適用される。整数、浮動小数点数、boolchar、および Copy 型のタプルや配列がこれに当たる。Drop を実装する型は Copy を実装できない——2つのコピーがそれぞれ drop を呼び出し、同じリソースを2度解放してしまうからだ。

Clone: 明示的な複製

Clone はオプトイン(opt-in)形式の複製だ。.clone() を呼ぶと、コストが非自明な場合もある独立したコピーが生成される。

let s = String::from("hello");
let t = s.clone(); // t のために独立したヒープ割り当てが行われる
println!("{} {}", s, t); // 両方有効

Stringclone 時にヒープバッファ全体をコピーしなければならない。この割り当ては実際の作業なので、Rustはそれを明示的にする。コンパイラがヒープデータを暗黙的に複製するのではなく、オプトインするために .clone() を書く。

すべての Copy 型は Clone も実装する——Copy 型に .clone() を呼ぶとビットをコピーするだけだ。逆は成り立たない。StringClone だが Copy ではない。

コンパイラによる強制

ボローチェッカーはプログラムのすべての時点ですべての変数のムーブ状態を追跡する。ムーブ済みの値を使うとコンパイルエラーになる。

let v = vec![1, 2, 3];
let w = v;
println!("{:?}", v); // error[E0382]: borrow of moved value: `v`

エラーメッセージは変数名を示し、どこでムーブされたかを表示し、無効な使用箇所を指摘する。これらのエラーは、プログラムが実行される前にコンパイラがリソース管理の健全性を証明しているものだ。

まとめ

  • すべての値にはちょうど1人の所有者がいる。所有者がスコープを抜けると値はドロップされる。
  • 新しい変数への代入や関数への渡しは所有権をムーブする。古いバインディングはもう有効ではない。
  • 値渡しには Sized が必要だ。サイズ不定型は参照の背後にしか置けない。
  • Copy 型はビット単位で暗黙に複製される。Drop の実装はできない。
  • Clone 型は複製するために明示的な .clone() の呼び出しが必要だ——コストにオプトインする。