3

要素がベクトル内の最後の要素よりも大きい場合にのみ、並べ替えられたベクトルの末尾に要素をプッシュする関数を作成しようとしています。それ以外の場合は、最大の要素への参照でエラーを返します。私が知る限り、これは借用規則に違反しているようには見えませんが、借用チェッカーはそれを好まないのです。理由がわかりません。

struct MyArray<K, V>(Vec<(K, V)>);

impl<K: Ord, V> MyArray<K, V> {
    pub fn insert_largest(&mut self, k: K, v: V) -> Result<(), &K> {
        {
            match self.0.iter().next_back() {
                None => (),
                Some(&(ref lk, _)) => {
                    if lk > &k {
                        return Err(lk);
                    }
                }
            };
        }
        self.0.push((k, v));
        Ok(())
    }
}

error[E0502]: cannot borrow `self.0` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:9
   |
6  |             match self.0.iter().next_back() {
   |                   ------ immutable borrow occurs here
...
15 |         self.0.push((k, v));
   |         ^^^^^^ mutable borrow occurs here
16 |         Ok(())
17 |     }
   |     - immutable borrow ends here

なぜこれが機能しないのですか?


パオロ・ファラベラの答えに応えて。

次のように、return ステートメントのある関数を return ステートメントのない関数に変換できます。

fn my_func() -> &MyType {
    'inner: {
        // Do some stuff
        return &x;
    }
    // And some more stuff
}

の中へ

fn my_func() -> &MyType {
    let res;
    'outer: {
        'inner: {
            // Do some stuff
            res = &x;
            break 'outer;
        }
        // And some more stuff
    }
    res
}

このことから、借用が のスコープを超えて存続することが明らかになり'innerます。

ボローチェックの目的で代わりに次の書き換えを使用することに問題はありますか?

fn my_func() -> &MyType {
    'outer: {
        'inner: {
            // Do some stuff
            break 'outer;
        }
        // And some more stuff
    }
    panic!()
}

return ステートメントは、それ以外の場合は借用規則に違反する可能性のあるその後の発生を排除することを考慮してください。

4

2 に答える 2

7

ライフタイムに明示的に名前を付けると、の署名は にinsert_largestなりfn insert_largest<'a>(&'a mut self, k: K, v: V) -> Result<(), &'a K>ます。したがって、戻り値の型を作成する&Kと、その寿命は と同じになり&mut selfます。

そして、実際には、あなたはlk中から取って帰っていselfます。コンパイラは、への参照がlk一致のスコープをエスケープしていることを確認しており (関数の戻り値に割り当てられているため、関数自体よりも長く存続する必要があります)、一致が終了したときに借用を終了させることはできません。

コンパイラはより賢く、返されなかったself.0.push場合にのみ到達できることを認識する必要があると言っていると思います。lkそうではありません。そして、そのような分析を教えるのがどれほど難しいかはわかりません。今日の借用チェッカーの理由を理解する方法よりも少し洗練されているからです。

現在、コンパイラは参照を見て、基本的に 1 つの質問 (「これはどのくらい存続しますか?」) に答えようとします。あなたの戻りlk値がlk'a

つまり、要するに:

  • 早期復帰は自己の可変借用を終了する必要がありますか? いいえ。前述のように、借用は関数の外に拡張し、その戻り値に従う必要があります
  • 関数の初期リターンから終了までのコードで、借用チェッカーは少し厳密すぎますか? はい、そう思います。関数が早期に返されなかった場合にのみ、早期リターンの後と関数の終了前の部分に到達できるため、そのコードの特定の領域での借用のチェックがそれほど厳密ではない可能性があるという点があると思います
  • そのパターンを有効にするためにコンパイラを変更することは実行可能/望ましいと思いますか? 私は見当もつかない。借用チェッカーは Rust コンパイラの最も複雑な部分の 1 つであり、私はそれについて答える資格がありません。これは、非レキシカル ボロー スコープに関する議論に関連しているように思われます (そしてそのサブセットでさえあるかもしれません)ので、このトピックに興味がある場合は、それを調べて貢献することをお勧めします。

当面は、可能であれば、参照の代わりにクローンを返すことをお勧めします。を返すことErrは一般的なケースではないと想定しているため、パフォーマンスは特に心配する必要はありませんが、K:Clone使用している型でバインドがどのように機能するかはわかりません。

impl <K, V> MyArray<K, V> where K:Clone + Ord { // 1. now K is also Clone
    pub fn insert_largest(&mut self, k: K, v: V) -> 
                                    Result<(), K> { // 2. returning K (not &K)
        match self.0.iter().next_back() {
            None => (),
            Some(&(ref lk, _)) => {
                if lk > &k {
                    return Err(lk.clone()); // 3. returning a clone
                }
            }
        };
        self.0.push((k, v));
        Ok(())
    }
}
于 2016-01-18T11:11:20.250 に答える