前回、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を付ける位置で意味が変わる。
- 借用が終わるタイミングは関数が終わる時。
スポンサーリンク