【Rust入門】変数の参照と借用

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