Rust言語のミュータブル参照(&mut T
)の振る舞いについてメモ。[本記事はRust 1.10/Stable準拠]
ミュータブル参照m1
から新しいミュータブル参照m2
を変数束縛(variable binding)するとき、型の明示有無によって(a)ミュータブル参照の所有権が移動(ムーブ)するケースと、(b)ミュータブル参照の再借用(reborrow)が行われるケースが存在する。
// m2の変数束縛で型推論 let mut x: i32 = 0; let m1: &mut i32 = &mut x; { let m2 = m1; // (a1) m1からm2へムーブ *m2 = 42; } assert!(*m1 == 42); // (a2) NG: m1は既に所有権を持たない // error: use of moved value: `*m1` // note: `m1` moved here because it has type `&mut i32`, which is moved by default // let m2 = m1;
// m2の変数束縛で型(&mut i32)を明示 let mut x: i32 = 0; let m1: &mut i32 = &mut x; { let m2: &mut i32 = m1; // (b1) m2からm1へ再借用 *m2 = 42; //assert!(*m1 == 42); // error: cannot use `*m1` because it was mutably borrowed } // (b1') m2の借用期間おわり assert!(*m1 == 42); // (b2) OK: 所有権はm1に残ったまま
いずれのケースでもm2
の型は&mut i32
に相当するが、厳密には参照のライフタイム(lifetime)が異なる別の型である。つまりlet束縛における&mut i32
という型表記ではライフタイムが省略されており、Rust言語における具象型ではないために起きている。次の疑似コード例では*1、m2
が参照する先のライフタイムが異なる(&'a mut i32
と&'b mut i32
)。
// m2の変数束縛で型推論 { // ライフタイムaの開始 let mut x: i32 = 0; let m1: &'a mut i32 = &mut x; // m1の厳密な型は &'a mut i32 { let m2 = m1; // (a1) m2は &'a mut i32 に型推論される // m1とm2は同じ &'a mut i32 型のため所有権がムーブされる *m2 = 42; } // m1は既に所有権を持たない assert!(*m1 == 42); // (a2) NG } // ライフタイムaの終了
// m2の変数束縛で型(&mut i32)を明示 { // ライフタイムaの開始 let mut x: i32 = 0; let m1: &'a mut i32 = &mut x; // m1の厳密な型は &'a mut i32 { // ライフタイムbの開始 let m2: &mut i32 = m1; // (b1) m2は &'b mut i32に型推論される // m2はm1より狭いライフタイムを参照するため再借用が行われる *m2 = 42; } // ライフタイムbの終了 // m2のスコープを抜けて借用はm1に返される assert!(*m1 == 42); // (b2) OK } // ライフタイムaの終了
前掲ルールは、ジェネリック関数におけるパラメータ記述方法の違いによっても顕在化する。ミュータブル参照をジェネリックパラメータT
で単純に受け取ると(1)、関数呼び出しに指定した変数束縛v1
はムーブにより所有権を失う。
#[derive(Debug)] struct S; // 型パラメータT による T型 fn sink1<T>(_: T) {} // ライフタイムパラメータ'a と 型パラメータT による &'a mut T型 fn sink2<'a,T>(_: &'a mut T) {} // sink2と同義:入力ライフタイムパラメータを省略した記法 fn sink3<T>(_: &mut T) {} fn main() { let (v1, v2, v3) = (&mut S, &mut S, &mut S); // (1) Tはv1と同じライフタイムのミュータブル参照型に推論される sink1(v1); //print!("{:?}", v1); // error: use of moved value: `v1` // (2) 'aは関数スコープ相当のライフタイムを表し TはS型へと推論されるため // &'a mut T型はv2と異なるライフタイムのミュータブル参照型となる sink2(v2); print!("{:?}", v2); // OK // (3) (2)と同じ型推論が行われる sink3(v3); print!("{:?}", v3); // OK }
関連URL
- The Rustonomicon, References
- Non-lexical Lifetimes: Introduction
- rust - Why is the mutable reference not moved here? - Stack Overflow
- Rustでmatch内部の所有権について考える(暗黙の借用 vs. ムーブ) - Qiita
*1:プログラム全域を指す特殊なライフタイム 'static を除き、Rust言語の構文上はlet変数束縛で参照のライフタイムを明示できない。