13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rustの可変参照の挙動がわかりにくい

Last updated at Posted at 2019-07-27

TL;DR

  • 「可変参照型」は通常の所有権ルールに従わない。
  • 「可変参照型」には、ライフタイムが異なる場合に reborrow という特殊な借用? move ? が存在する

本文

Rustの不変参照はCopyである。

sample1.rs
fn main() {
    let v: i32 = 120;
    let v_ref = &v;
    let v_ref2 = v_ref;

    println!("{}", *v_ref); // 120
}

一方、可変参照はCopyではないそうだ。つまり、一度moveされたら二度と使うことはできない。

sample2.rs
fn main() {
    let mut v: i32 = 120;
    let v_mut = &mut v;
    let v_mut2 = v_mut;

    println!("{}", *v_mut); // コンパイルエラー
}
sample3.rs
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されず、この参照はそのまま使い続けられる。

sample4.rs

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
}

それどころか、関数を経由して可変参照を持ち回れば、別の変数に可変参照を入れても、使い続けられる。

sample5.rs
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
}

別の変数に可変参照を入れて、それを使っても、元の変数の可変参照を使い続けることもできる。

sample6.rs
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
}

「可変な可変参照変数」でも発生する。

sample7.rs
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
}

ただし、一度元の可変参照を使ったら、複製された可変参照はもう使えない。

sample8.rs
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 なるものがあるので、再借用元が使用されるまでの区間を別のライフタイムとして扱っていると思われる。

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?