前回、Rustの変数の代入には、MoveとCopyがあるという話をしました。
Moveの場合は所有権が移ってしまい、以降はその変数が使えなくなります。しかし、時には代入後も所有権を渡したくない時もありますよね。
今回は変数の所有権を渡さない方法である借用についてです。
※注意
借用はとても複雑です。
そのため、今回は基本的な部分のみ説明します。
変数の参照と借用
変数の代入時、所有権を渡さすないようにするには「借用」という方法を使います。
借用は英語で「borrow」と言います。これはコンパイルエラーの時によく出てくる言葉なので覚えておいてください。
以下が借用の例になります。
fn main() {
let value1 = 100;
let value2 : &i32;
value2 = &value1;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
}
value1 is 100 value2 is 100
参照と借用、言葉の違いは?
5行目で変数value1に「&」をつけてvalue2に代入しています。
&を付けて代入をすることを「参照」といいます。
「参照」とは実体を受け取るのではなく、メモリの位置を教えてもらうことです。今回の場合、100という数をvalue2にコピーするのではなく、100という数字がメモリ上のどこにあるのかという情報(アドレス)をvalue2に入れる、ということです。
参照を使った場合、「基本的に」所有権の移動は行われません。このように参照を使って所有権を移動させない仕組みを「借用」と言います。
参照型とは?
参照を受けるには、変数の型を参照型にしないといけません。
それを行っているのが3行目です。
let value : &i32;
各変数の型の前に「&」をつけることで参照型にすることができます。
複数回に渡る借用
次に、以下の例文のようにvalue1を複数の変数に借用をすることはできるのでしょうか。
fn main() {
let value1 = 100;
let value2 : &i32;
let value3 : &i32;
value2 = &value1;
value3 = &value1;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
println!("value3 is {}", value3);
}
これは問題なくできます。
結果は以下の通りです。
value1 is 100 value2 is 100 value3 is 100
また、以下の例文のように参照を代入することも可能です。
fn main() {
let value1 = 100;
let value2 : &i32;
let value3 : &i32;
value2 = &value1;
value3 = value2;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
println!("value3 is {}", value3);
}
この場合、value2はvalue1の所有権を持っていないので、当然ながらvalue3に所有権が移ることはありません。
借用した変数の書き換えは可能か?
ミューダブル、イミューダブルのところでお話しましたが、変数にmutを付けると書き換え可能な変数になります。
しかし、例えmutがついた変数であっても、借用中は値の書き換えができません。
fn main() {
let mut value1 = 100;
let value2 : &i32;
//↓まだ借用していないので代入可能
value1 = 50;
value2 = &value1;
//↓借用中なので書き換え不可
// コンパイルエラーになる
value1 = 150;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
}
参照から変数の値を変更する
参照から変数の値を書き換えるには以下のようにします。
fn main() {
let mut value1 = 100;
let value2 : &mut i32;
value2 = &mut value1;
(*value2) = 150;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
}
value2を「書き換え可能な参照型」として宣言します。(3行目)
そして、参照を渡す時にmutを付け、書き換え可能な参照と明示して渡します。(5行目)
最後に「*」をつけて値を代入します。(7行目)
value2 = &mut value1;
参照経由で変数の値を書き換える時は「*」を付けて代入します。
「*」はアドレスの中身を書き換えるという意味になります。
(*value2) = 150;
ここで注意点です。
Rustは所有権を持っていないと値の書き換えができません。
そのため、書き換え可能な参照を渡す場合は所有権が移動してしまいます。
上記の例文の場合、5行目の時点で所有権はvalue2になります。
value2 = &mut value1;
そして、以降、value1は使えなくなります。
結果、10行目のprintln!の時点でコンパイルエラーになります。
mutを付ける位置での動作の違い
参照は、mutを付ける位置で動作が違うので注意が必要です。
参照型の宣言の仕方は以下の3種類があります。
mutの位置に着目してください。
1. let value : &mut i32; 2. let mut value : &i32; 3. let mut value : &mut i32;
| 値の書き換え | 参照先の変更 | 所有権の移動 | |
|---|---|---|---|
| 1. | 可能 | 不可 | 無し |
| 2. | 不可 | 可能 | 有り |
| 3. | 可能 | 可能 | 有り |
1. 型指定にmutを付けた場合
「:」の後の型指定にmutを付けると書き換え可能な参照型になります。(3行目)
fn main() {
let mut value1 = 100;
let value2 : &mut i32;
let mut value3 = 200;
value2 = &mut value1;
(*value2) = 150;
println!("value2 is {}", value2);
//↓コンパイルエラーになる
value2 = &mut value3;
println!("value2 is {}", value1);
しかし、value2はmutがついていないので、参照先、つまりアドレスを書き換えることはできません。(12行目)
2. letの後ろにmutを付けた場合
letの後ろにmutがあると参照経由で値の書き換えはできません。(3行目)
fn main() {
let mut value1 = 100;
let mut value2 : &i32;
let mut value3 = 200;
value2 = &mut value1;
//↓コンパイルエラーになる
(*value2) = 150;
println!("value2 is {}", value2);
//↓これは大丈夫
value2 = &mut value3;
println!("value2 is {}", value2);
}
しかし、アドレスの書き換えはできるようになります。(13行目)
2. 型指定にmutを付け、さらにletの後ろにmutを付けた場合
letの後ろにmutを付け、さらに型の部分にもmutを付けると参照経由で値の書き換えができる上に、アドレスの書き換えもできるようになります。
fn main() {
let mut value1 = 100;
let mut value2 : &mut i32;
let mut value3 = 200;
value2 = &mut value1;
//↓これは大丈夫
(*value2) = 150;
println!("value2 is {}", value2);
//↓これも大丈夫
value2 = &mut value3;
println!("value2 is {}", value2);
}
「書き換え可能な参照型」と「参照型」は全く別物という認識を持つと理解しやすいと思います。
借用が終わるタイミング
借用は直近の関数が終わると終了します。
fn main() {
let value1 = 100;
let value2 : &i32;
//↓ここから借用開始
value2 = &value1;
println!("value1 is {}", value1);
println!("value2 is {}", value2);
//ここで借用終了
}
まとめ
- 参照とは変数のアドレスを受け取ること。
- 借用とは参照を使って所有権を移動させない方法。
- 参照型はmutを付ける位置で意味が変わる。
- 借用が終わるタイミングは関数が終わる時。
スポンサーリンク