参照と借用
リスト4-5のタプルコードの問題は、String型を呼び出し元の関数に戻さないと、calculate_lengthを呼び出した後に、
Stringオブジェクトが使えなくなることであり、これはStringオブジェクトがcalculate_lengthにムーブされてしまうためでした。
代わりに、String値への参照を渡すことができます。
参照はアドレスであり、それを辿ってそのアドレスに置かれているデータにアクセスできるという点で、ポインタと似ています;
データは他の変数によって所有されています。
ポインタと異なる点としては、参照はその生存期間中を通して、特定の型の有効な値を指していることが保証されています。
ここで、値の所有権をもらう代わりに引数としてオブジェクトへの参照を取るcalculate_length関数を定義し、
使う方法を見てみましょう:
ファイル名: src/main.rs
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // '{}'の長さは、{}です println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
まず、変数宣言と関数の戻り値にあったタプルコードは全てなくなったことに気付いてください。
2番目に、&s1をcalcuate_lengthに渡し、その定義では、String型ではなく、&Stringを受け取っていることに注目してください。
これらのアンド記号が参照を表しており、これのおかげで所有権をもらうことなく値を参照することができるのです。
図4-5にこの概念を描写します。
図4-5: String s1を指す&String sの図表
注釈:
&による参照の逆は、参照外しであり、参照外し演算子の*で達成できます。 第8章で参照外し演算子の使用例を眺め、第15章で参照外しについて詳しく議論します。
ここの関数呼び出しについて、もっと詳しく見てみましょう:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // '{}'の長さは、{}です println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
この&s1という記法により、s1の値を参照する参照を生成することができますが、これを所有することはありません。
所有してないということは、指している値は、参照が使用されなくなってもドロップされないということです。
同様に、関数のシグニチャでも、&を使用して引数sの型が参照であることを示しています。
説明的な注釈を加えてみましょう:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { // sはStringへの参照 s.len() } // ここで、sはスコープ外になる。けど、参照しているものの所有権を持っているわけではないので // ドロップはされない。
変数sが有効なスコープは通常の関数の引数のものと同じですが、sはそれが指す値に対する所有権を持っていないので、
sが使用されなくなっても指している値をドロップすることはありません。関数が実際の値の代わりに参照を引数に取ると、
所有権をもらわないので、所有権を返す目的で値を返す必要はありません。
参照を作成することを借用と呼びます。現実生活のように、誰かが何かを所有していたら、 それを借りることができます。用が済んだら、返さないといけません。持っているのとは違うのです。
では、借用した何かを変更しようとしたら、どうなるのでしょうか?リスト4-6のコードを試してください。 ネタバレ注意: 動きません!
ファイル名: src/main.rs
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
リスト4-6: 借用した値を変更しようと試みる
これがエラーです:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
(`*some_string`は`&`参照の背後にあるため、それを可変として借用することはできません)
--> src/main.rs:8:5
|
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
| (`some_string`は`&`参照なので、それが指すデータを可変として借用することはできません)
|
help: consider changing this to be a mutable reference
(これを可変参照に変更することを検討してください)
|
7 | fn change(some_string: &mut String) {
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
変数が標準で不変なのと全く同様に、参照も不変なのです。参照している何かを変更することは叶わないわけです。
可変参照
借用された値を変更できるようにするには、代わりに可変参照を使うという一捻りを加えるだけでよく、 これでリスト4-6のコードを修正できます:
ファイル名: src/main.rs
fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
まずsがmutとなるように変更します。次に、change関数を呼ぶところで&mut sによって可変参照を作成し、
さらに関数シグネチャを、some_string: &mut Stringで可変参照を受け入れるように更新します。
これにより、change関数は借用する値を変更しようとすることがとても明確になります。
可変参照には大きな制約が一つあります: ある値への可変参照が存在するなら、その値への参照を他に作ることはできません。
このコードはsへの可変参照を2個作成しようとしていますが、これは失敗します:
ファイル名: src/main.rs
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
これがエラーです:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
(一度に`s`を可変として2回以上借用することはできません)
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
| (最初の可変借用はここで発生しています)
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
| (2つ目の可変借用はここで発生しています)
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
| (最初の参照はここで後で使用されています)
For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
このエラーによると、一度にsを可変として2回以上借用することはできないので、このコードは不正だ、とのことです。
最初の可変借用はr1にあり、println!で使用されるまで続かないといけませんが、この可変借用の作成から使用までの間に、
r1と同じデータを借用する別の可変借用をr2に作成しようとしました。
同じデータへの複数の可変参照が同時に存在することを禁止する、という制約は、可変化を許可するものの、 それを非常に統制の取れた形で行えます。これは、新たなRustaceanにとっては、 壁です。なぜなら、多くの言語では、いつでも好きな時に可変化できるからです。 この制約がある利点は、コンパイラがコンパイル時にデータ競合を防ぐことができる点です。 データ競合とは、競合状態と類似していて、これら3つの振る舞いが起きる時に発生します:
- 2つ以上のポインタが同じデータに同時にアクセスする。
- 少なくとも一つのポインタがデータに書き込みを行っている。
- データへのアクセスを同期する機構が使用されていない。
データ競合は未定義の振る舞いを引き起こし、実行時に追いかけようとした時に特定し解決するのが難しい問題です。 しかし、Rustは、データ競合が起こるコードのコンパイルを拒否することで、この問題が発生しないようにしてくれるわけです。
いつものように、波かっこを使って新しいスコープを生成し、同時並行なものでなく、複数の可変な参照を作ることができます。
fn main() { let mut s = String::from("hello"); { let r1 = &mut s; } // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる。 let r2 = &mut s; }
コンパイラは可変と不変な参照を組み合わせることに関しても、似たような規則を強制します。このコードはエラーになります:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 問題なし
let r2 = &s; // 問題なし
let r3 = &mut s; // 大問題!
println!("{}, {}, and {}", r1, r2, r3);
}
これがエラーです:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
(`s`は不変で借用されているので、可変で借用できません)
--> src/main.rs:6:14
|
4 | let r1 = &s; // 問題なし
| -- immutable borrow occurs here
| (不変借用はここで発生しています)
5 | let r2 = &s; // 問題なし
6 | let r3 = &mut s; // 大問題!
| ^^^^^^ mutable borrow occurs here
| (可変借用はここで発生しています)
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
| (不変借用はその後ここで使われています)
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
ふう!さらに不変な参照をしている間は、同じ値に対して可変な参照をすることはできません。
不変参照の使用者は、それ以降に値が突然変わることなんて予想してません! しかしながら、複数の不変参照をすることは許されています。 データを読み込んでいるだけの人に、他人がデータを読み込むことに対して影響を与える能力はないからです。
参照のスコープは、それが導入されたところから始まり、その参照が最後に使用される時点まで続きます。
例えば、次のコードはコンパイルできるでしょう。不変参照の最後の使用箇所であるprintln!は、
可変参照が導入されるよりも前に発生するからです:
fn main() { let mut s = String::from("hello"); let r1 = &s; // 問題なし let r2 = &s; // 問題なし println!("{} and {}", r1, r2); // r1とr2はもうこれ以降使用されません let r3 = &mut s; // 問題なし println!("{}", r3); }
不変参照r1とr2のスコープは、それらが最後に使用されるprintln!の後で終了します。
これは可変参照r3が作成されるより前のことです。これらのスコープは重なっていないので、
このコードは許可されます: コンパイラは、スコープの終了より前の時点で参照がもはや使用されていないということを、
判別できるのです。
借用エラーは、時としてイライラするものではありますが、Rustコンパイラがバグの可能性を早期に指摘してくれ(それも実行時ではなくコンパイル時に)、 問題の発生箇所をズバリ示してくれるのだと覚えておいてください。そうして想定通りにデータが変わらない理由を追いかける必要がなくなります。
宙に浮いた参照
ポインタのある言語では、誤ってダングリングポインタを生成してしまいやすいです。ダングリングポインタとは、 他人に渡されてしまった可能性のあるメモリを指すポインタのことであり、その箇所へのポインタを保持している間に、 メモリを解放してしまうことで発生します。対照的にRustでは、コンパイラが、 参照がダングリング参照に絶対ならないよう保証してくれます: つまり、何らかのデータへの参照があったら、 コンパイラは参照がスコープを抜けるまで、データがスコープを抜けることがないよう確認してくれるわけです。
ダングリング参照を作ってみて、コンパイラがどのようにこれをコンパイルエラーで阻止するか見てみましょう:
ファイル名: src/main.rs
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
こちらがエラーです:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子がありません)
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
| (名前付きのライフタイムパラメータが期待されていました)
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
= (助言: この関数の戻り値型は、借用した値を含んでいますが、借用される値がどこにもありません)
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
(助言: `'static`ライフタイムを使うことを検討してください、ただし`const`または`static`から借用した値を返すのでない限り、これはあまり使われません)
|
5 | fn dangle() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
(助言: または、所有権を持つ値を返す方がいいかもしれません)
|
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
|
For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
このエラーメッセージは、まだ講義していない機能について触れています: ライフタイムです。 ライフタイムについては第10章で詳しく議論しますが、ライフタイムに関する部分を無視すれば、 このメッセージは、確かにこのコードが問題になる理由に関する鍵を握っています:
this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
dangleコードの各段階で一体何が起きているのかを詳しく見ていきましょう:
ファイル名: src/main.rs
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangleはStringへの参照を返す
let s = String::from("hello"); // sは新しいString
&s // String sへの参照を返す
} // ここで、sはスコープを抜け、ドロップされる。そのメモリは消される。
// 危険だ
sは、dangle内で生成されているので、dangleのコードが終わったら、sは解放されてしまいますが、
そこへの参照を返そうとしました。つまり、この参照は無効なStringを指していると思われるのです。
よくないことです!コンパイラは、これを阻止してくれるのです。
ここでの解決策は、Stringを直接返すことです:
fn main() { let string = no_dangle(); } fn no_dangle() -> String { let s = String::from("hello"); s }
これは何の問題もなく動きます。所有権はムーブされ、何も解放されることはありません。
参照の規則
参照について議論したことを再確認しましょう:
- 任意のタイミングで、一つの可変参照か不変な参照いくつでものどちらかを行える。
- 参照は常に有効でなければならない。
次は、違う種類の参照を見ていきましょう: スライスです。