可変借用をしようとしているのに、不変借用があると Rust が警告するコンパイル エラーが永続的に発生しますが、不変借用は別のスコープからのものであり、そこから何も持ち込んでいません。
マップ内の値をチェックし、存在する場合はそれを返します。そうでない場合は、さまざまな方法でマップを変更する必要があります。問題は、2 つの操作が完全に分離されているにもかかわらず、Rust で両方を実行できるようにする方法が見つからないことです。
ここに、私のコードと同じ構造に従い、問題を示す無意味なコードを示します。
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
// extra scope in vain attempt to contain the borrow
{
// borrow immutably
if let Some(key) = map.get(&key) {
return Some(key);
}
}
// now I'm DONE with the immutable borrow, but rustc still thinks it's borrowed
map.insert(0, 0); // borrow mutably, which errors
None
}
次のエラーが発生します:
error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable
--> src/lib.rs:14:5
|
3 | fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
| - let's call the lifetime of this reference `'1`
...
7 | if let Some(key) = map.get(&key) {
| --- immutable borrow occurs here
8 | return Some(key);
| --------- returning this value requires that `*map` is borrowed for `'1`
...
14 | map.insert(0, 0); // borrow mutably, which errors
| ^^^^^^^^^^^^^^^^ mutable borrow occurs here
これは私には意味がわかりません。不変の借用がどのようにしてそのスコープより長く存続するのでしょうか?! の 1 つのブランチはmatch
を介して関数を終了しreturn
、もう 1 つは何もせずにスコープを離れます。
以前にも、借用を誤ってスコープ外の別の変数に密輸したときにこのようなことが発生したことがありますが、今回はそうではありません。
確かに、借用はステートメントを介してスコープをエスケープしますreturn
が、関数内のさらに下の借用をブロックするのはばかげています。プログラムは、戻って続行することはできません。そこで何か他のものを返すと、エラーは消えるので、借用チェッカーが停止しているのはこのためだと思います。これはバグのように感じます。
残念ながら、同じエラーを発生させずにこれを書き直す方法を見つけることができませんでした。もしそうだとしたら、これは特に厄介なバグです。
ベストアンサー1
これは既知の問題これは、将来の反復によって解決されるだろう非語彙的寿命、しかし、現在取り扱っておりませんRust 1.57 時点。
検索しているキーと同じキーに挿入する場合は、エントリーAPIを使用するその代わり。
今のところ、これを回避するには、少しの非効率性を加えることができます。非効率性が許容できない場合は、もっと深い回避策がある。
HashMap
一般的な考え方は、値が存在するかどうかを示すブール値を追加することです。このブール値は参照を保持しないため、借用はありません。
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
if map.contains_key(&key) {
return map.get(&key);
}
map.insert(0, 0);
None
}
fn main() {
let mut map = BTreeMap::new();
do_stuff(&mut map, 42);
println!("{:?}", map)
}
Vec
同様のケースは、参照の代わりに要素のインデックスを使用することで解決できます。上記の場合と同様に、スライスの境界を再度確認する必要があるため、少し非効率になる可能性があります。
の代わりに
fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
match container.iter_mut().find(|e| **e == 5) {
Some(element) => element,
None => {
container.push(5);
container.last_mut().unwrap()
}
}
}
あなたは書ける:
fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| {
container.push(5);
container.len() - 1
});
&mut container[idx]
}
非語彙的ライフタイム
こうした例は、NLL RFC:問題ケース3: 関数間の条件付き制御フロー。
-Zpolonius
残念ながら、この特定のケースは Rust 1.57時点ではまだ準備ができていません。nightly ( )の実験的な機能を有効にするとRUSTFLAGS="-Z polonius" cargo +nightly check
、これらの元の例はそれぞれそのままコンパイルされます。
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
if let Some(key) = map.get(&key) {
return Some(key);
}
map.insert(0, 0);
None
}
fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 {
match container.iter_mut().find(|e| **e == 5) {
Some(element) => element,
None => {
container.push(5);
container.last_mut().unwrap()
}
}
}
参照:
-
これは同じ問題ですそれなし参照を返します。これは、Rust 1.32 で利用可能な NLL の実装で機能します。
-
この問題は、少し複雑なケースです。
Rust の借用チェッカーを回避する必要があるのはいつですか?
究極の脱出ハッチ。