TL;DR
- 「可変参照型」は通常の所有権ルールに従わない。
- 「可変参照型」には、ライフタイムが異なる場合に
reborrow
という特殊な借用? move ? が存在する
本文
Rustの不変参照はCopy
である。
fn main() {
let v: i32 = 120;
let v_ref = &v;
let v_ref2 = v_ref;
println!("{}", *v_ref); // 120
}
一方、可変参照はCopyではないそうだ。つまり、一度moveされたら二度と使うことはできない。
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
let v_mut2 = v_mut;
println!("{}", *v_mut); // コンパイルエラー
}
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
let mut v_mut2 = v_mut;
println!("{}", *v_mut); // コンパイルエラー
}
エラーメッセージとして、
move occurs because `v_mut` has type `&mut i32`, which does not implement the `Copy` trait
が表示されることからも、「可変参照の所有権」がmoveされていると理解できる。
しかし、関数の引数として渡しても、「可変参照の所有権」はmoveされず、この参照はそのまま使い続けられる。
fn mut_fn(v_mut: &mut i32) -> &mut i32 { v_mut }
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
mut_fn(v_mut);
println!("{}", *v_mut); // 120
}
それどころか、関数を経由して可変参照を持ち回れば、別の変数に可変参照を入れても、使い続けられる。
fn mut_fn(v_mut: &mut i32) -> &mut i32 { v_mut }
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
let v_mut2 = mut_fn(v_mut);
println!("{}", *v_mut); // 120
}
別の変数に可変参照を入れて、それを使っても、元の変数の可変参照を使い続けることもできる。
fn mut_fn(v_mut: &mut i32) -> &mut i32 { v_mut }
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
let v_mut2 = mut_fn(v_mut);
let v_mut3 = mut_fn(v_mut2);
println!("{}", *v_mut3); // 120
println!("{}", *v_mut2); // 120
mut_fn(v_mut);
println!("{}", *v_mut); // 120
}
「可変な可変参照変数」でも発生する。
fn main() {
let mut v: i32 = 120;
let mut u: i32 = 150;
let v_mut = &mut v;
let mut u_mut = &mut u;
u_mut = v_mut;
println!("{}", *u_mut); // 120
println!("{}", *v_mut); // 120
}
ただし、一度元の可変参照を使ったら、複製された可変参照はもう使えない。
fn mut_fn(v_mut: &mut i32) -> &mut i32 { v_mut }
fn main() {
let mut v: i32 = 120;
let v_mut = &mut v;
let v_mut2 = mut_fn(v_mut);
println!("{}", *v_mut2);
println!("{}", *v_mut);
println!("{}", *v_mut2); // コンパイルエラー
}
以上からわかる結論。
- 可変参照はlet文で変数自体を直接代入すると「可変参照の所有権」がmoveされ、元の変数からは使えなくなる。
- 関数に渡したり、別の可変参照で初期化された可変変数に渡した場合
- 「可変参照の所有権」は一時的に預けることができる。
- しかし元の変数から可変参照の完全に所有権が失われているわけではなく、元の変数を使用した時点で所有権が「回収」される。
- 値のmoveではこのようなことは起こらない。
#[derive(Debug)]
struct Foo {}
fn foo_fn(foo: Foo) -> Foo { foo }
fn main() {
let f = Foo {};
let f2 = foo_fn(f);
println!("{:?}", f); // コンパイルエラー
}
可変参照を取る関数やメソッドに可変参照を一度渡したら、関数やメソッドから可変参照を返却して取得しなおさなきゃいけないとなったら不便だから、こういう挙動になるのが妥当なのはわかるけど、なんだか直感的ではない動きで少し困惑。
きっと理解が間違っていると思うので詳しい方見ていたら教えてください。
追記
こちらに回答がありました。
元の参照と異なるライフタイムに対しては「再借用」なるものが行われ、この場合は再借用が終了したら所有権が返ってくるようだ。
sample6.rs
には明示的なスコープはないけど、最近のRustには Non-lexical lifetime
なるものがあるので、再借用元が使用されるまでの区間を別のライフタイムとして扱っていると思われる。