参照は存在しているだけで、それと競合する各種の操作をコンパイルエラーにする。そして、参照のライフタイムは、基本的にはその参照の最後の使用までだが、状況によってはさらに延長される。ここではその延長されるパターンについて紹介する。
以下は関連するエラー。
一つのライフタイムが複数の参照で共有される場合、そのライフタイムの範囲と制限は、それぞれの参照が要求する和になる。これにより、参照の最後の使用後も、他の参照の影響でライフタイムが継続し、その制限が残る事がある。
参照から同じライフタイムの新しい参照を取得するパターン。
新しい参照が残っている間は、元の参照による制限も残る。
これは、元の参照と新しい参照の参照先が、データ構造における全体と部分、またはその逆のような重複した関係となるため、必須の措置である。つまり、新しい参照から部分側の更新を制限をしても、元の参照への制限がなければ、全体側からの更新ができてしまう。
以下では、参照 &data
から参照 sub_ref
を得ている。つまり、sub_ref
への最後のアクセスまでは、&data
も維持され、それと衝突する操作は行えなくなる。
fn main() {
let mut data = Data { sub: 0 };
let sub_ref = ref_to_ref(&data);
&mut data;
println!("{}", sub_ref);
}
fn ref_to_ref(data: &Data) -> &i32 {
&data.sub
}
struct Data {
sub: i32,
}
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable --> src\main.rs:4:2 | 3 | let sub_ref = ref_to_ref(&data); | ----- immutable borrow occurs here 4 | &mut data; | ^^^^^^^^^ mutable borrow occurs here 5 | println!("{}", sub_ref); | ------- immutable borrow later used here
可変参照から同じライフタイムの新しい参照を取得するパターン。
基本は『参照からの参照』のとおりで、新しい参照が残っている間は、元の参照による制限も残る。さらに、元の参照からの操作は全て禁止される。そのため、新しい参照が存在している間は、全ての操作がそこからに一本化される。
以下では、参照 &mut data
から sub_ref
を得ている。そのため、sub_ref
への最後のアクセスまでは、&mut data
も維持され、それと衝突する操作は行えなくなる。
fn main() {
let mut data = Data { sub: 0 };
let sub_ref = mut_to_ref(&mut data);
&data;
println!("{}", sub_ref);
}
fn mut_to_ref(data: &mut Data) -> &i32 {
&data.sub
}
struct Data {
sub: i32,
}
error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable --> src\main.rs:4:2 | 3 | let sub_ref = mut_to_ref(&mut data); | --------- mutable borrow occurs here 4 | &data; | ^^^^^ immutable borrow occurs here 5 | println!("{}", sub_ref); | ------- mutable borrow later used here
可変参照を経由して、同じライフタイムの新しい不変参照を取得した場合について。
これも『可変参照からの参照』で説明したとおりである。
ただ、このパターンについてのよくある疑問があるため取り上げておく。
可変参照からの参照では、新しい参照が残っている間は、元の参照からの操作は全て禁止されてしまう。可変参照からの不変参照の場合、これは少し厳しすぎるように思える。
代わりに、元の参照からは読取のみを許可するのはどうだろう。つまり、元の可変参照は不変参照へとダウングレードしたと見なす。そして、新しい参照も不変参照である。よって、書込はどこからも行えないため、読取は常に安全に行える…はずである。
しかし、この代案は以下のレアケースの考慮が漏れている。
内部可変型には get_mut
, from_mut
関数を持つものがある。これらは、内部可変型と可変参照型とを相互変換できる (参考:『内部可変性と get_mut, from_mut 系の関数』)。
この変換において、変換先を得る代わりに、変換元への操作は全て禁止しなければならない。そうしないと、変換元か変換先のどちらかは内部可変型への参照型のため、一方からの読取と他方からの書込がともに許される区間ができてしまう。
つまり、元の参照への操作を全て禁止にするルールがここで必要になる。
以下は RefCell
による例。不変参照中の値の更新をコンパイルエラーで予防している。
use std::cell::RefCell;
fn main() {
let mut target = RefCell::new(0);
let immutable_ref = get_ref_cell_value(&mut target);
assert_eq!(*immutable_ref, 0);
set_ref_cell_value(&target, 1);
assert_eq!(*immutable_ref, 0);
}
fn get_ref_cell_value(mutable_ref: &mut RefCell<i32>) -> &i32 {
&*mutable_ref.get_mut()
}
fn set_ref_cell_value(immutable_ref: &RefCell<i32>, value: i32) {
*immutable_ref.borrow_mut() = value;
}
error[E0502]: cannot borrow `target` as immutable because it is also borrowed as mutable --> src\main.rs:8:24 | 5 | let immutable_ref = get_ref_cell_value(&mut target); | ----------- mutable borrow occurs here ... 8 | set_ref_cell_value(&target, 1); | ^^^^^^^ immutable borrow occurs here 9 | assert_eq!(*immutable_ref, 0); | ----------------------------- mutable borrow later used here
&'a A<'a>
のような指定について。つまり、構造体への参照と構造体からの参照とが、同じライフタイム注釈を持つ場合について。
このような借用を行った場合、その借用は、その構造体が破棄されるまで返却されなくなる。なぜなら、[構造体への参照のライフタイム] ⊆ [構造体の存在期間] ⊆ [構造体からの参照のライフタイム] の包含関係において、両サイドが同じになるため、全体での等号が成立する。これにより、参照のライフタイムが構造体の存在期間と一致する事になる。
以下では、参照 &mut data
と構造体の変数 data
で問題の処理が発生する。そのため、data
がある間は、参照 &mut data
も残り、それと衝突する操作は行えなくなる。
fn main() {
let context = 0;
let mut data = Data { context: &context };
use_data_mut(&mut data);
&data;
}
fn use_data_mut<'a>(_data: &'a mut Data<'a>) {
/* ... */
}
struct Data<'a> {
context: &'a i32,
}
error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable --> src\main.rs:5:5 | 4 | use_data_mut(&mut data); | --------- mutable borrow occurs here 5 | &data; | ^^^^^ | | | immutable borrow occurs here | mutable borrow later used here
Drop
トレイトの影響詳しくは『Drop トレイトのライフタイムへの影響 - ライフタイムの延長』を参照。