はじめに
これはあくまでメモなので、基本的にググってわかるようなことは詳しく書きません。
よろしくお願いします。
また、以下のプログラムでは面倒なのでfn main() {}
を省略することがあります。
ご承知おきください。
おさらい
前回は2版の4章の4.1に目を通して、以下のことを学びました。
- 所有権とは何なのか?
以下のリンクで参照できます。
https://qiita.com/task4233/items/29ca871662ad4f405c5c
この記事の目的
Rust Tutorialの各章ごとでメモを残しておくことにより、一通り目を通した後に見返せるようにすることを目的としています。
そのため、各章の最初に何を目的とするか、最後に小さなまとめをメモしています。
なお、Rust Tutorial2版のリンクはこちらです。
(https://doc.rust-jp.rs/book/second-edition/foreword.html)
4.2 参照と借用
借用にどのようなメリットがあるのかを理解すること。
どのような借用をするとなぜエラーがおきるのかを理解すること。
参照と借用
String
オブジェクトをそのまま呼び出すと、呼び出し元のString
オブジェクトはムーブされて使えなくなります。
しかし、以下のようにオブジェクトへの参照を取れば使えなくなることはありません。
このように、関数の引数に参照を取ることを借用
と言います。
fn main() {
let s1 = String::from("hello");
let len = get_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn get_length(s: &String) -> usize { // sはStringへの参照
s.len()
} // sはスコープ外になるが、参照であるため何も起こらない
// [output]
// The length of 'hello' is 5.
このコードではget_length
関数の引数として&String
を受け取ることで、値を借用しています。
ただし、借用した値はイミュータブルです。
したがって、以下のように借用した値を変更しようとするとエラーが発生します。
fn main() {
let s1 = String::from("hello");
let new_s = add_world(&s1);
println!("{}", new_s);
}
fn add_world(s: &String) -> &String {
s.push_str("world"); // 借用した値を変更するとエラーが発生する
s
}
// [output]
// error: cannot borrow immutable borrowed content `*s` as mutable
// (借りてきたイミュータブルな`*s`をミュータブルに出来ないよ)
// --> prog.rs:9:3
// |
//8 | fn add_world(s: &String) -> &String {
// | ------- use `&mut String` here to make mutable
// (&mut Stringを使ってね)
//9 | s.push_str("world");
// | ^
//
// error: aborting due to previous error
可能な参照
さきほどの借用では値を変更しようとしてエラーが発生していました。
その原因は、借用した値がイミュータブルだったからです。
したがって、エラーを解消するにはイミュータブルな値をミュータブルにすれば問題ないでしょう。
したがって、以下のように引数にmut
を追加することでエラーは解消されます。
fn main() {
let mut s1 = String::from("hello"); // 借用してから変更するためミュータブルにしておく
let new_s = add_world(&mut s1); // ミュータブルな参照にする
println!("{}", new_s);
}
fn add_world(s: &mut String) -> &String { // 引数はミュータブルにする
s.push_str("world");
s
}
// [output]
// helloworld
借用のルール
ただし、「1つのスコープ内の1つのデータに対して、ミュータブルな参照をもてるのは1つだけ」です。
以下のように、1つのスコープ内の1つのデータに対して、2つのミュータブルな参照を持たせるとエラーが発生します。
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
}
// [output]
// error[E0499]: cannot borrow `s` as mutable more than once at a time
// (1度に2つ以上のミュータブルな借用は出来ないよ)
// --> prog.rs:5:17
// |
//4 | let r1 = &mut s;
// | - first mutable borrow occurs here
// (ここで1つ目のミュータブルな借用が起きてるよ)
//5 | let r2 = &mut s;
// | ^ second mutable borrow occurs here
// (ここで2つ目のミュータブルな借用が起きてるよ)
//6 | }
// | - first borrow ends here
// (ここで1つ目のミュータブルな借用が終わってるよ)
//
//error: aborting due to previous error
この制約により、コンパイル時にデータ競合を防ぐことができます。
データ競合は、以下の3つの振る舞いが起きると発生します。
- 2つ以上のポインタが同じデータに同時にアクセスする。
- 少なくとも一つのポインタがデータに書き込みを行っている。
- データへのアクセスを同期する機構が使用されていない。
データ競合が起きると未定義の振る舞いを引き起こすので、実行時に特定して解決するのが困難です。
しかし、Rustではデータ競合が怒るコードをエラーで弾き返すので、このような問題は起きません。
さまざまな借用
先ほど書いた通り、「1つのスコープ内の1つのデータに対して、ミュータブルな参照をもてるのは1つだけ」です。
したがって、以下のように新しいスコープを作れば1つ前のコードはエラーが発生しません。
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
}
let r2 = &mut s;
}
しかし、以下のように同じスコープで宣言された変数に対して、新しく作ったスコープ内でミュータブルな参照を持つとエラーが発生します。
fn main() {
let mut s = String::from("hello");
let r1: &mut String;
{
r1 = &mut s; // 1つ目のミュータブルな参照
}
let r2 = &mut s; // 2つ目のミュータブルな参照
}
// [output]
// error[E0499]: cannot borrow `s` as mutable more than once at a time
// (1度に2つ以上の借用は出来ないよ)
// --> prog.rs:7:17
// |
//5 | r1 = &mut s;
// | - first mutable borrow occurs here
// (ここで1つ目の借用が起きてるよ)
//6 | }
//7 | let r2 = &mut s;
// | ^ second mutable borrow occurs here
// (ここで2つ目の借用が起きてるよ)
//8 | }
// | - first borrow ends here
// (ここで1つ目の借用が終わってるよ)
//
//error: aborting due to previous error
また、以下のようにイミュータブルな参照に関しては、1つのスコープ内で何度行っても問題はありません。
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
しかし、以下のようにミュータブルな参照とイミュータブルな参照を組み合わせてもエラーになります。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
let r3 = &mut s; // NG
// (ミュータブルな参照とイミュータブルな参照が混在している)
}
// [output]
//error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
// (イミュータブルな参照をしてるからミュータブルな参照は出来ないよ)
// --> prog.rs:5:17
// |
//3 | let r1 = &s;
// | - immutable borrow occurs here
// (ここでイミュータブルな借用が起きてるよ)
//4 | let r2 = &s;
//5 | let r3 = &mut s;
// | ^ mutable borrow occurs here
// (ここでミュータブルな借用が起きてるよ)
//6 | }
// | - immutable borrow ends here
// (ここでイミュータブルな借用が終わってるよ)
//
//error: aborting due to previous error
ただし、これに関しても、以下のように新しいスコープを作ればエラーは解消されます。
fn main() {
let mut s = String::from("hello");
{
let r1 = &s;
let r2 = &s;
} // ここでスコープを抜けるので、ミュータブルな参照を作ることができる
let r3 = &mut s;
}
宙に浮いた参照
ポインタのある言語では、誤ってダングリングポインタを生成してしまいがちです
ダングリングポインタとは、他人に渡されてしまった可能性のあるメモリをさすポインタのことであり、その箇所へのポインタを保持している間にメモリを解放してしまうことで発生します。
ただし、Rustではコンパイラがそのような参照が起きないことを保証しています。
実際に以下のように、ダングリング参照を試してみますがコンパイルエラーを吐かれます。
fn main() {
let r = dangle();
println!("{}", r);
}
fn dangle() -> &String { // dangleはStringへの参照を返す
let s = String::from("hello"); // sは新しいString
&s // String sへの参照を返す
} // ここでsはスコープを抜けてドロップされ、メモリは吹き飛ぶ。
// ここではもう参照できない
// [output]
//error[E0106]: missing lifetime specifier
// (ライフタイム指定子が無いよ)
// --> prog.rs:5:16
// |
//5 | fn dangle() -> &String {
// | ^ expected 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 giving it a 'static lifetime
// (助言: 'staticライフタイムを与えることを考慮したら?)
//
//error: aborting due to previous error
この問題を解決するには、以下のように参照ではなく所有権をムーブすれば良いです。
そうすれば、スコープを抜けてメモリが吹き飛んでも問題ありません。
fn main() {
let r = dangle();
println!("{}", r);
}
fn dangle() -> String {
let s = String::from("hello");
s
}
// [output]
// hello
借用をすることで、オブジェクトを何度もムーブする必要がなくなります。
また、1つのスコープ内の1つのデータに対するミュータブル/イミュータブルな参照については以下の表の通りである。
1つのスコープ内の1つのデータに対する参照の形態 | 1つ | 2つ以上 |
---|---|---|
ミュータブルな参照のみ | OK | NG |
イミュータブルな参照のみ | OK | OK |
ミュータブルな参照とイミュータブルな参照 | NG | NG |
おわりに
2版4章4.2では以下のことが書かれていました。
- 参照の方法
- 借用に関するルール
借用
という呼び名は、Rustの所有権という概念からするとしっくりきました。
所有権という概念は面倒な部分もあるが、安全性でみれば重要なのだなと改めて感じました。