注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
このガイドは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>
を使います。
そして、 v1
と v2
を直接渡す代わりに、 &v1
と &v2
を渡します。
&T
型は「参照」と呼ばれ、それは、リソースを所有するのではなく、所有権を借用します。
何かを借用した束縛はそれがスコープから外れるときにリソースを割当解除しません。
これは foo()
の呼出しの後に元の束縛を再び使うことができることを意味します。
参照は束縛と同じようにイミュータブルです。
これは foo()
の中ではベクタは全く変更できないことを意味します。
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);
^
値の挿入はベクタを変更するものであり、そうすることは許されていません。
参照には2つ目の種類、 &mut T
があります。
「ミュータブルな参照」によって借用しているリソースを変更できるようになります。
例は次のとおりです。
let mut x = 5; { let y = &mut x; *y += 1; } println!("{}", x);
これは 6
を表示するでしょう。
y
を x
へのミュータブルな参照にして、それから y
の指示先に1を足します。
x
も mut
とマークしなければならないことに気付くでしょう。
そうしないと、イミュータブルな値へのミュータブルな借用ということになってしまい、使うことができなくなってしまいます。
アスタリスク( *
)を 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つを持つことはありますが、両方を同時に持つことはありません。
&T
)&mut T
)これがデータ競合の定義と非常に似ていることに気付くかもしれません。全く同じではありませんが。
「データ競合」は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() {
}
^
言い換えると、ミュータブルな借用は、先ほどの例の残りの間、ずっと保持されるということです。
ここで私たちが求めているのは、y
によるミュータブルな借用が終わり、リソースがその所有者である x
に返却されることです。
そうすれば x
は println!
にイミュータブルな借用を提供できるわけです。
Rustでは借用はその有効なスコープと結び付けられます。
そしてスコープはこのように見えます。
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
はそれ自体イミュータブルとして借用され、それは繰返しを行っている間はそれを変更できないことを意味します。
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;
}
言い換えると、 y
は x
が存在するスコープの中でだけ有効だということです。
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);
}
前の例では、 y
は x
より前に宣言されています。それは、 y
が x
より長く生存することを意味し、それは許されません。