所有権

このガイドは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 がスコープに入るとき、新しい Vec<T> が作られます。 この場合、ベクタも3つの要素のために ヒープ に空間を割り当てます。 foo() の最後で v がスコープから外れるとき、Rustはベクタに関連するもの全てを取り除くでしょう。それがヒープ割当てのメモリであってもです。 これはスコープの最後で決定的に起こります。

ムーブセマンティクス

しかし、ここではもっと微妙なことがあります。それは、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 v = vec![1, 2, 3]; let v2 = v; }
let v = vec![1, 2, 3];

let v2 = v;

最初の行はベクタオブジェクト v とそれの含むデータのためのメモリを割り当てます。 ベクタオブジェクトは スタック に保存され、 ヒープ に保存された内容( [1, 2, 3] )へのポインタを含みます。 vv2 にムーブするとき、それは v2 のためにそのポインタのコピーを作ります。 それは、ヒープ上のベクタの内容へのポインタが2つあることを意味します。 それはデータ競合を持ち込むことでRustの安全性保証に違反するでしょう。 そのため、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は借用という機能を提供します。それはこの問題を解決するために手助けしてくれます。 それが次のセクションの話題です!