参照と借用

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

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

概論

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

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

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

それを念頭に置いて、借用について学びましょう。

借用

所有権 セクションの最後に、このような感じの厄介な関数に出会いました。

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的なコードではありません。なぜなら、それは借用の利点を生かしていないからです。 これが最初のステップです。

fn main() { fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 { // do stuff with v1 and v2 // v1とv2についての作業を行う // return the answer // 答えを返す 42 } let v1 = vec![1, 2, 3]; let v2 = vec![1, 2, 3]; let answer = foo(&v1, &v2); // we can use v1 and v2 here! // ここではv1とv2が使える! }
fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    // v1とv2についての作業を行う

    // 答えを返す
    42
}

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

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

// ここではv1とv2が使える!

引数として Vec<i32> を使う代わりに、参照、つまり &Vec<i32> を使います。 そして、 v1v2 を直接渡す代わりに、 &v1&v2 を渡します。 &T 型は「参照」と呼ばれ、それは、リソースを所有するのではなく、所有権を借用します。 何かを借用した束縛はそれがスコープから外れるときにリソースを割当解除しません。 これは foo() の呼出しの後に元の束縛を再び使うことができることを意味します。

参照は束縛とちょうど同じようにイミュータブルです。 これは foo() の中ではベクタは全く変更できないことを意味します。

fn main() { fn foo(v: &Vec<i32>) { v.push(5); } let v = vec![]; foo(&v); }
fn foo(v: &Vec<i32>) {
     v.push(5);
}

let v = vec![];

foo(&v);

次のようなエラーが出ます。

error: cannot borrow immutable borrowed content `*v` as mutable
v.push(5);
^

値の挿入はベクタを変更するものであり、そうすることは許されていません。

&mut参照

参照には2つ目の種類、 &mut T があります。 「ミュータブルな参照」によって借用しているリソースを変更することができるようになります。 例は次のとおりです。

fn main() { let mut x = 5; { let y = &mut x; *y += 1; } println!("{}", x); }
let mut x = 5;
{
    let y = &mut x;
    *y += 1;
}
println!("{}", x);

これは 6 をプリントするでしょう。 yx へのミュータブルな参照にして、それから y の指示先に1を足します。 xmut とマークしなければならないことに気付くでしょう。 そうしないと、イミュータブルな値へのミュータブルな借用ということになってしまい、使うことができなくなってしまいます。

アスタリスク( * )を y の前に追加して、それを *y にしたことにも気付くでしょう。これは、 y&mut 参照だからです。 参照の内容にアクセスするためにもそれらを使う必要があるでしょう。

それ以外は、 &mut 参照は普通の参照と全く同じです。 しかし、2つの間には、そしてそれらがどのように相互作用するかには大きな違いが あります 。 前の例で何かが怪しいと思ったかもしれません。なぜなら、 {} を使って追加のスコープを必要とするからです。 もしそれらを削除すれば、次のようなエラーが出ます。

error: cannot borrow `x` as immutable because it is also borrowed as mutable
    println!("{}", x);
                   ^
note: previous borrow of `x` occurs here; the mutable borrow prevents
subsequent moves, borrows, or modification of `x` until the borrow ends
        let y = &mut x;
                     ^
note: previous borrow ends here
fn main() {

}
^

結論から言うと、ルールがあります。

ルール

これがRustでの借用についてのルールです。

最初に、借用は全て所有者のスコープより長く存続してはなりません。 次に、次の2種類の借用のどちらか1つを持つことはありますが、両方を同時に持つことはありません。

これがデータ競合の定義と非常に似ていることに気付くかもしれません。全く同じではありませんが。

「データ競合」は2つ以上のポインタがメモリの同じ場所に同時にアクセスするとき、少なくともそれらの1つが書込みを行っていて、作業が同期されていないところで「データ競合」は起きます。

書込みを行わないのであれば、参照は好きな数だけ使うことができます。 &mut は同時に1つしか持つことができないので、データ競合は起き得ません。 これがRustがデータ競合をコンパイル時に回避する方法です。もしルールを破れば、そのときはエラーが出るでしょう。

これを念頭に置いて、もう一度例を考えましょう。

スコープの考え方

このコードについて考えていきます。

fn main() { let mut x = 5; let y = &mut x; *y += 1; println!("{}", x); }
let mut x = 5;
let y = &mut x;

*y += 1;

println!("{}", x);

このコードは次のようなエラーを出します。

error: cannot borrow `x` as immutable because it is also borrowed as mutable
    println!("{}", x);
                   ^

なぜなら、これはルールに違反しているからです。つまり、 x を指示する &mut T を持つので、 &T を作ることは許されないのです。 どちらか1つです。 note の部分はこの問題についての考え方のヒントを示します。

note: previous borrow ends here
fn main() {

}
^

言い換えると、ミュータブルな借用は先程の例の残りの間ずっと保持されるということです。 必要なものは、 println! を呼び出し、イミュータブルな借用を作ろうとする 前に 終わるミュータブルな借用です。 Rustでは借用はその有効なスコープと結び付けられます。 そしてスコープはこのように見えます。

fn main() { let mut x = 5; let y = &mut x; // -+ &mut borrow of x starts here // | *y += 1; // | // | println!("{}", x); // -+ - try to borrow x here // -+ &mut borrow of x ends here let y = &mut x; // -+ xの&mut借用がここから始まる // | *y += 1; // | // | println!("{}", x); // -+ - ここでxを借用しようとする // -+ xの&mut借用がここで終わる }
let mut x = 5;

let y = &mut x;    // -+ xの&mut借用がここから始まる
                   //  |
*y += 1;           //  |
                   //  |
println!("{}", x); // -+ - ここでxを借用しようとする
                   // -+ xの&mut借用がここで終わる

スコープは衝突します。 y がスコープにある間は、 &x を作ることができません。

そして、波括弧を追加するときはこうなります。

fn main() { let mut x = 5; { let y = &mut x; // -+ &mut borrow starts here *y += 1; // | } // -+ ... and ends here let y = &mut x; // -+ &mut借用がここから始まる *y += 1; // | } // -+ ... そしてここで終わる println!("{}", x); // <- try to borrow x here println!("{}", x); // <- ここでxを借用しようとする }
let mut x = 5;

{
    let y = &mut x; // -+ &mut借用がここから始まる
    *y += 1;        //  |
}                   // -+ ... そしてここで終わる

println!("{}", x);  // <- ここでxを借用しようとする

問題ありません。 ミュータブルな借用はイミュータブルな借用を作る前にスコープから外れます。 しかしスコープは借用がどれくらい存続するのか理解するための鍵となります。

借用が回避する問題

なぜこのような厳格なルールがあるのでしょうか。 そう、前述したように、それらのルールはデータ競合を回避します。 データ競合はどのような種類の問題を起こすのでしょうか。 ここに一部を示します。

イテレータの無効

一例は「イテレータの無効」です。それは繰返しを行っているコレクションを変更しようとするときに起こります。 Rustの借用チェッカはこれの発生を回避します。

fn main() { let mut v = vec![1, 2, 3]; for i in &v { println!("{}", i); } }
let mut v = vec![1, 2, 3];

for i in &v {
    println!("{}", i);
}

これは1から3までをプリントアウトします。 ベクタに対して繰り返すとき、要素への参照だけを受け取ります。 そして、 v はそれ自体イミュータブルとして借用され、それは繰返しを行っている間はそれを変更できないことを意味します。

fn main() { let mut v = vec![1, 2, 3]; for i in &v { println!("{}", i); v.push(34); } }
let mut v = vec![1, 2, 3];

for i in &v {
    println!("{}", i);
    v.push(34);
}

これがエラーです。

error: cannot borrow `v` as mutable because it is also borrowed as immutable
    v.push(34);
    ^
note: previous borrow of `v` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `v` until the borrow ends
for i in &v {
          ^
note: previous borrow ends here
for i in &v {
    println!(“{}”, i);
    v.push(34);
}
^

v はループによって借用されるので、それを変更することはできません。

解放後の使用

参照はそれらの指示するリソースよりも長く生存することはできません。 Rustはこれが真であることを保証するために、参照のスコープをチェックするでしょう。

もしRustがこの性質をチェックしなければ、無効な参照をうっかり使ってしまうかもしれません。 例えばこうです。

fn main() { let y: &i32; { let x = 5; y = &x; } println!("{}", y); }
let y: &i32;
{
    let x = 5;
    y = &x;
}

println!("{}", y);

次のようなエラーが出ます。

error: `x` does not live long enough
    y = &x;
         ^
note: reference must be valid for the block suffix following statement 0 at
2:16...
let y: &i32;
{
    let x = 5;
    y = &x;
}

note: ...but borrowed value is only valid for the block suffix following
statement 0 at 4:18
    let x = 5;
    y = &x;
}

言い換えると、 yx が存在するスコープの中でだけ有効だということです。 x がなくなるとすぐに、それを指示することは不正になります。 そのように、エラーは借用が「十分長く生存していない」ことを示します。なぜなら、それが正しい期間有効ではないからです。

参照がそれの参照する変数より 前に 宣言されたとき、同じ問題が起こります。 これは同じスコープにあるリソースはそれらの宣言された順番と逆に解放されるからです。

fn main() { let y: &i32; let x = 5; y = &x; println!("{}", y); }
let y: &i32;
let x = 5;
y = &x;

println!("{}", y);

次のようなエラーが出ます。

error: `x` does not live long enough
y = &x;
     ^
note: reference must be valid for the block suffix following statement 0 at
2:16...
    let y: &i32;
    let x = 5;
    y = &x;

    println!("{}", y);
}

note: ...but borrowed value is only valid for the block suffix following
statement 1 at 3:14
    let x = 5;
    y = &x;

    println!("{}", y);
}

前の例では、 yx より前に宣言されています。それは、 yx より長く生存することを意味し、それは許されません。