所有権

このガイドはRustの所有権システムの3つの解説の1つ目です。 これはRustの最も独特で注目されている機能です。そして、Rust開発者はそれについて高度に精通しておくべきです。 所有権こそはRustがその最大の目標、メモリ安全性を得るための方法です。 そこにはいくつかの別個の概念があり、各概念が独自の章を持ちます。

それらの3つの章は関連していて、それらは順番に並んでいます。 所有権システムを完全に理解するためには、3つ全てを必要とするでしょう。

概論

詳細に入る前に、所有権システムについての2つの重要な注意があります。

Rustは安全性とスピードに焦点を合わせます。 Rustはそれらの目標を、様々な「ゼロコスト抽象化」を通じて成し遂げます。 それは、Rustでは抽象化を機能させるためのコストをできる限り小さくすることを意味します。 所有権システムはゼロコスト抽象化の主な例です。 このガイドの中で話すであろう解析の全ては コンパイル時に行われます 。 それらのどの機能に対しても実行時のコストは全く掛かりません。

しかし、このシステムはあるコストを持ちます。それは学習曲線です。 多くのRust入門者は、私たちが「借用チェッカとの戦い」と呼ぶものを経験します。 そこではRustコンパイラが、開発者が正しいと考えるプログラムをコンパイルすることを拒絶します。 所有権がどのように機能するのかについてのプログラマのメンタルモデルがRustの実装する実際のルールにマッチしないため、これはしばしば起きます。 しかし、よいニュースがあります。より経験豊富なRustの開発者は次のことを報告します。 それは、所有権システムのルールと共にしばらく仕事をすれば、借用チェッカと戦うことは次第に少なくなっていく、というものです。

それを念頭に置いて、所有権について学びましょう。

所有権

Rustでは 変数束縛 はある特性を持ちます。それは、束縛されているものの「所有権を持つ」ということです。 これは束縛がスコープから外れるとき、Rustは束縛されているリソースを解放するだろうということを意味します。 例えばこうです。

fn main() { fn foo() { let v = vec![1, 2, 3]; } }
fn foo() {
    let v = vec![1, 2, 3];
}

v がスコープに入るとき、新しい ベクタスタック 上に作られ、要素を格納するために ヒープ に空間を割り当てます。 foo() の最後で v がスコープから外れるとき、Rustはベクタに関連するもの全てを取り除くでしょう。 それがヒープ割り当てのメモリであってもです。 これはスコープの最後で決定的に起こります。

ベクタ については、前のセクションで説明済みですが、簡単に復習しましょう。 ここではベクタを、実行時にヒープに空間を割り当てる型の例として用いています。 ベクタは 配列 のように振る舞いますが、追加の要素を push() するとサイズが変わるところは違います。

ベクタは ジェネリクス型 Vec<T> を持ちますので、この例における vVec<i32> 型になるでしょう。 ジェネリクスについては、この章の後の方で詳しく説明します。

ムーブセマンティクス

しかし、ここではもっと些細に見えることがあります。それは、Rustは与えられたリソースに対する束縛が 1つだけ あることを保証するというものです。 例えば、もしベクタがあれば、それを別の束縛に割り当てることはできます。

fn main() { let v = vec![1, 2, 3]; let v2 = v; }
let v = vec![1, 2, 3];

let v2 = v;

しかし、もし後で v を使おうとすると、エラーが出ます。

fn main() { let v = vec![1, 2, 3]; let v2 = v; println!("v[0] is: {}", v[0]); }
let v = vec![1, 2, 3];

let v2 = v;

println!("v[0] is: {}", v[0]);

こんな感じのエラーです。

error: use of moved value: `v`
println!("v[0] is: {}", v[0]);
                        ^

もし所有権を受け取る関数を定義して、引数として何かを渡した後でそれを使おうとするならば、同じようなことが起きます。

fn main() { fn take(v: Vec<i32>) { // what happens here isn’t important. // ここで何が起きるかは重要ではない } let v = vec![1, 2, 3]; take(v); println!("v[0] is: {}", v[0]); }
fn take(v: Vec<i32>) {
    // ここで何が起きるかは重要ではない
}

let v = vec![1, 2, 3];

take(v);

println!("v[0] is: {}", v[0]);

「use of moved value」という同じエラーです。 所有権を何か別のものに転送するとき、参照するものを「ムーブした」と言います。 これは特別な種類の注釈なしに行われます。 つまりRustのデフォルトの動作です。

詳細

束縛をムーブした後で、それを使うことができないと言いました。 その理由は、ごく詳細かもしれませんが、とても重要です。

このようなコードを書いた時、

fn main() { let x = 10; }
let x = 10;

Rustは スタック 上に整数 i32 のためのメモリを割り当て、そこに、10という値を表すビットパターンをコピーします。 そして後から参照できるよう、変数名xをこのメモリ領域に束縛します。

今度は、こんなコード片について考えてみましょう。

fn main() { let v = vec![1, 2, 3]; let mut v2 = v; }
let v = vec![1, 2, 3];

let mut v2 = v;

最初の行では、先ほどの x と同様に、ベクタオブジェクト v のために、スタック上にメモリを割り当てます。 しかし、これに加えて、実際のデータ( [1, 2, 3] )のために、 ヒープ 上にもメモリを割り当てます。 スタック上のベクタオブジェクトの中にはポインタがあり、Rustはいま割り当てたヒープのアドレスをそこへコピーします。

すでに分かりきっているかもしれませんが、念のためここで確認しておきたいのは、ベクタオブジェクトとそのデータは、それぞれが別のメモリ領域に格納されていることです。 決してそれらは、1つの連続したメモリ領域に置かれているわけではありません(その理由についての詳細は、いまは省きます)。 そして、ベクタにおけるこれら2つの部分(スタック上のものと、ヒープ上のもの)は、要素数やキャパシティ(容量)などについて、常にお互いの間で一貫性が保たれている必要があります。

vv2 にムーブするときRustが実際に行うのは、ビット単位のコピーを使って、ベクタオブジェクト v が示すスタック領域の情報を、 v2 が示すスタック領域へコピーすることです。 この浅いコピーでは、実際のデータを格納しているヒープ領域はコピーしません。 これは、ベクタの内容として、同一のヒープメモリ領域を指すポインタが2つあることを意味します。 もし誰かが vv2 に同時にアクセスできるとしたら? これはデータ競合を持ち込むことになり、Rustの安全性保証に違反するでしょう。

例えば v2 を通して、ベクタを2要素分、切り詰めたとしましょう。

fn main() { let v = vec![1, 2, 3]; let mut v2 = v; v2.truncate(2); }
v2.truncate(2);

もしまだ v1 にアクセスできたとしたら、v1 はヒープデータが切り詰められたことを知らないので、不正なベクタを提供することになってしまいます。 ここでスタック上の v1 は、ヒープ上で対応する相手と一貫性が取れていません。 v1 はベクタにまだ3つの要素があると思っているので、もし私たちが存在しない要素 v1[2] にアクセスしようとしたら、喜んでそうさせるでしょう。 しかし、すでにお気づきの通り、特に次のような理由から大惨事に繋がるかもしれません。 これはセグメンテーション違反を起こすかもしれませんし、最悪の場合、権限を持たないユーザーが、本来アクセスできないはずのメモリを読めてしまうかもしれないのです。

このような理由から、Rustはムーブを終えた後の v の使用を禁止するのです。

また知っておいてほしいのは、状況によっては最適化により、スタック上のバイトを実際にコピーする処理が省かれる可能性があることです。 そのため、ムーブは最初に思ったほど非効率ではないかもしれません。

Copy

所有権が他の束縛に転送されるとき、元の束縛を使うことができないということを証明しました。 しかし、この挙動を変更する トレイト があります。それは Copy と呼ばれます。 トレイトについてはまだ議論していませんが、とりあえずそれらを、ある型に対してある挙動を追加するための、注釈のようなものとして考えて構いません。 例えばこうです。

fn main() { let v = 1; let v2 = v; println!("v is: {}", v); }
let v = 1;

let v2 = v;

println!("v is: {}", v);

この場合、 vi32 で、それは Copy トレイトを実装します。 これはちょうどムーブと同じように、 vv2 に代入するとき、データのコピーが作られることを意味します。 しかし、ムーブと違って、後でまだ v を使うことができます。 これは i32 がどこか別の場所へのポインタを持たず、コピーが完全コピーだからです。

全てのプリミティブ型は Copy トレイトを実装しているので、推測どおりそれらの所有権は「所有権ルール」に従ってはムーブしません。 例として、次の2つのコードスニペットはコンパイルが通ります。なぜなら、 i32 型と bool 型は Copy トレイトを実装するからです。

fn main() { let a = 5; let _y = double(a); println!("{}", a); } fn double(x: i32) -> i32 { x * 2 }
fn main() {
    let a = 5;

    let _y = double(a);
    println!("{}", a);
}

fn double(x: i32) -> i32 {
    x * 2
}
fn main() { let a = true; let _y = change_truth(a); println!("{}", a); } fn change_truth(x: bool) -> bool { !x }
fn main() {
    let a = true;

    let _y = change_truth(a);
    println!("{}", a);
}

fn change_truth(x: bool) -> bool {
    !x
}

もし Copy トレイトを実装していない型を使っていたならば、ムーブした値を使おうとしたため、コンパイルエラーが出ていたでしょう。

error: use of moved value: `a`
println!("{}", a);
               ^

独自の Copy 型を作る方法は トレイト セクションで議論するでしょう。

所有権を越えて

もちろん、もし書いた全ての関数で所有権を返さなければならないのであれば、こうなります。

fn main() { fn foo(v: Vec<i32>) -> Vec<i32> { // do stuff with v // vについての作業を行う // hand back ownership // 所有権を返す v } }
fn foo(v: Vec<i32>) -> Vec<i32> {
    // vについての作業を行う

    // 所有権を返す
    v
}

これは非常に退屈になるでしょう。 もっとたくさんのものの所有権を受け取ろうとすると、状況はさらに悪化します。

fn main() { fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) { // do stuff with v1 and v2 // v1とv2についての作業を行う // hand back ownership, and the result of our function // 所有権と関数の結果を返す (v1, v2, 42) } let v1 = vec![1, 2, 3]; let v2 = vec![1, 2, 3]; let (v1, v2, answer) = foo(v1, v2); }
fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    // v1とv2についての作業を行う

    // 所有権と関数の結果を返す
    (v1, v2, 42)
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let (v1, v2, answer) = foo(v1, v2);

うわあ! 戻り値の型、リターン行、関数呼出しがもっと複雑になります。

幸運なことに、Rustは借用という機能を提供します。それはこの問題を解決するために手助けしてくれます。 それが次のセクションの話題です。