HashMap または Vec から参照を返すと、借用がそのスコープを超えて継続することになりますか? 質問する

HashMap または Vec から参照を返すと、借用がそのスコープを超えて継続することになりますか? 質問する

可変借用をしようとしているのに、不変借用があると 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()
        }
    }
}

参照:

おすすめ記事