再借用について。

概要

再借用が行われると、既存の参照からその参照先のライフタイムや可変性のみを変え、新しい参照が再取得される。なお、再借用は型解釈の一環としてほぼ暗黙で処理されるため、普段はあまり意識する必要はない。しかし、知っておくと各所の理解の助けになる事がある。

用例

不変参照の再借用

不変参照の再借用では、得られる参照は元の参照の別名のようになる。

関連動作

不変参照 &T はトレイト Copy実装している。再借用時もこれが利用される。


impl<T> Copy for &T where T: ?Sized

サンプル

以下では、参照 data_ref の別名として、_data_ref2 を取得している。


fn main() {
    let data = 0;
    let data_ref = &data;
    let _data_ref2 = data_ref as &i32;
}

可変参照の再借用

可変参照の再借用では、得られる参照は元の参照の一時的な代理になる。

関連動作

可変参照 &mut T は不変参照 &T と異なり、トレイト Copy を実装しない。しかし、可変参照の使用のために毎回ムーブを行うと、その使用後に毎回ムーブの戻しが必要になる。ここで、再借用で作られた代理を使えば、そうしたムーブは必要なくなる。

サンプル

以下では、関数 use_mut の引数として、参照 val_mut の再借用が渡されている。


fn main() {
    let mut val = 42;
    let val_mut = &mut val;
    use_mut(val_mut);
    assert_eq!(*val_mut, 43)
}

fn use_mut(arg: &mut i32) {
    *arg += 1;
}

可変参照の不変参照化

再借用を利用して、可変参照を不変参照化できる。

サンプル

以下では、可変参照 data_mut を不変参照化して、_data_ref を取得している。


fn main() {
    let mut data = 0;
    let data_mut = &mut data;
    let _data_ref = data_mut as &i32;
}

フィールド参照

フィールドアクセス式経由での再借用について。

フィールドのライフタイムが短縮されるパターンがある。

不変参照の再借用

この場合、フィールドのライフタイムはそのまま使われる。

サンプル

以下では、self.parent の式は、ベース側よりフィールド側のライフタイムが長い。
そして、フィールドは不変参照なので、そのライフタイムがそのまま使われる。


fn main() {
    let parent = Parent();
    let child = Child::new(&parent);
    let parent_ref = child.parent_ref();
    parent_ref.do_something();
}

struct Parent();

impl Parent {
    fn do_something(&self) {}
}

struct Child<'a> {
    parent: &'a Parent,
}

impl<'a> Child<'a> {
    fn new(parent: &'a Parent) -> Self {
        Self { parent }
    }

    fn parent_ref(&self) -> &'a Parent {
        self.parent
    }
}

可変参照の再借用

この場合、結果のライフタイムはベース側のライフタイムを超えないよう短縮される。そうでなければ、ベース側から辿れる可変参照と、再借用による可変参照、両者が同時に存在する期間が生まれ、可変参照の排他ルールが破られてしまう。

詳しくは、『error - ライフタイムの不足 × 再借用』を参照。

サンプル

以下では、self.parent の式は、ベース側よりフィールド側のライフタイムが長い。
そして、フィールドは可変参照なので、そのライフタイムはベース側に制限される。
その結果、メソッドの戻り値の条件を満たせず、エラーが発生する。


fn main() {
    let mut parent = Parent();
    let mut child = Child::new(&mut parent);
    let parent_mut = child.parent_mut();
    parent_mut.do_something();
}

struct Parent();

impl Parent {
    fn do_something(&self) {}
}

struct Child<'a> {
    parent: &'a mut Parent,
}

impl<'a> Child<'a> {
    fn new(parent: &'a mut Parent) -> Self {
        Self { parent }
    }

    fn parent_mut(&mut self) -> &'a mut Parent {
        self.parent
    }
}

error: lifetime may not live long enough
  --> src\main.rs:24:9
   |
18 | impl<'a> Child<'a> {
   |      -- lifetime `'a` defined here
...
23 |     fn parent_mut(&mut self) -> &'a mut Parent {
   |                   - let's call the lifetime of this reference `'1`
24 |         self.parent
   |         ^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`

型推論との関係

型推論を使った初期化や代入では、再借用は行われない。

なぜなら、その場合はライフタイムや可変性が右辺と同じだと想定される。

つまり、型を省略するか否かで再借用か、もしくは通常のコピーや移動かが変化する。

サンプル

以下では、with_inferwith_move_y の初期化時の型推論の有無のみが異なる。


fn main() {
    with_infer();
    with_move();
}

fn use_val<T>(_: T) {}

fn with_infer() {
    let x = &mut 0;
    let _y = x as &mut _;
    use_val(x);
}

fn with_move() {
    let x = &mut 0;
    let _y = x;
    use_val(x);
}

error[E0382]: use of moved value: `x`
  --> src\main.rs:17:10
   |
15 |     let x = &mut 0;
   |         - move occurs because `x` has type `&mut i32`, which does not implement the `Copy` trait
16 |     let _y = x;
   |              - value moved here
17 |     use_val(x);
   |             ^ value used here after move