Rust: &mut自己借用はそれ自体と競合します。

作成日 2015年02月03日  ·  20コメント  ·  ソース: rust-lang/rust

なぜこれが起こるのかわかりませんが、IRCのrovarとXMPPwockyはそれがコンパイラのバグであると信じていました。

struct A {
    a: i32
}

impl A {
    fn one(&mut self) -> &i32{
        self.a = 10;
        &self.a
    }
    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

...次のエラーが発生します...

<anon>:12:21: 12:25 error: cannot borrow `*self` as mutable more than once at a time
<anon>:12             let k = self.one();
                              ^~~~
<anon>:12:21: 12:25 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
<anon>:12             let k = self.one();
                              ^~~~
<anon>:17:6: 17:6 note: previous borrow ends here
<anon>:10     fn two(&mut self) -> &i32 {
...
<anon>:17     }
              ^
error: aborting due to previous error
playpen: application terminated with error code 101

興味深いことに、2番目の方法がに変更された場合...

    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            return k;
        }
    }

これは正常にコンパイルされます。

プレイペン: http

A-NLL A-borrow-checker A-typesystem C-bug NLL-polonius T-compiler

最も参考になるコメント

不変の借用が長すぎるように見える本番回避策)。

また、 starwedのコードは最新の毎晩コンパイルされなくなったことにも言及する必要があります。 @oberienは「 regression-from-nightly-to-nightly 」タグを提案しました。また、これが緊急の問題である場合に備えて付けることも提案されました:)

全てのコメント20件

これは正しい行動だと思います。 理由を理解するために、明示的なライフタイムアノテーションを含むコードを検討してください。

struct A {
    a: i32
}

impl A {
    fn one<'a>(&'a mut self) -> &'a i32{
        self.a = 10;
        &self.a
    }
    fn two<'a>(&'a mut self) -> &'a i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

twoは、与えられた可変ボローと同じ存続期間を持つ参照を返す必要があります。 しかし、それが得られるので。 これは、 k有効期間が'aなければならないことを意味します。 ただし、 oneの戻り値の有効期間が'aであるためには、 oneへの入力の有効期間も'a必要があります。 これは、Rustがselfを「再借用」せず、 one移動することを強制されることを意味します。 可変参照は線形であるため、移動できるのは1回だけですが、 loopは、同じ可変参照を使用してone繰り返し呼び出す必要がある可能性があることを意味します。

2番目の例は、Rustがループが1回だけ実行されることを確認できるためにのみ機能します。したがって、 selfは1回だけ移動されます。

これは、非字句の借用の問題のように見えます。 ループなしで同様の動作を得ることができます。

struct Foo { data: Option<i32> }

fn main() {
    let mut x = Foo{data: Some(1)};

    foo(&mut x);
}

fn foo(x: &mut Foo) -> Option<&mut i32> {
    if let Some(y) = x.data.as_mut() {
        return Some(y);
    }

    println!("{:?}", x.data); 
    None
}
<anon>:14:22: 14:28 error: cannot borrow `x.data` as immutable because it is also borrowed as mutable
<anon>:14     println!("{:?}", x.data); 
                               ^~~~~~
note: in expansion of format_args!
<std macros>:2:43: 2:76 note: expansion site
<std macros>:1:1: 2:78 note: in expansion of println!
<anon>:14:5: 14:30 note: expansion site
<anon>:10:22: 10:28 note: previous borrow of `x.data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x.data` until the borrow ends
<anon>:10     if let Some(y) = x.data.as_mut() {
                               ^~~~~~
<anon>:16:2: 16:2 note: previous borrow ends here
<anon>:9 fn foo(x: &mut Foo) -> Option<&mut i32> {
...
<anon>:16 }
          ^
error: aborting due to previous error

Rustcは、条件付きの借入収益を「処理」することはできません。

CC @pcwalton

問題は、実際には戻り値の可変性ではなく、関数の引数の可変性が使用されていることだと思います。

次のコードは、不変の参照を返す変更関数を呼び出します。 その後、私は不変の参照を取ることができません。

struct Foo (u8);

impl Foo {
    fn bar(&mut self) -> &u8 {
        self.0 += 1;
        &self.0
    }
}

fn main() {
    let mut x = Foo(42);
    let a = x.bar(); // note: borrow of `x` occurs here
    let b = x.0; // error: cannot use `x.0` because it was mutably borrowed
}

ベビーサークル

私はまだ最新の夜にこの問題に遭遇しています。 リターンのない関数がボローチェッカーを通過した場合でも、リターンは関数の終了まで存続期間があるものとして扱われるようです。 関数を早期に終了することがリターンのポイントであることを考えると、これはちょっと奇妙に思えます。

@Gankroの例を見ると、結果の関数が正しく機能しなくても、returnキーワードを削除すると、ボローチェッカーに合格します。

これは非字句の有効期間でブロックされ、MIRでブロックされます。 理想的には、これは2016年末までに修正される予定ですが、修正はすぐには行われません。

内部に関するいくつかの議論: https

非字句借用機能の説明: https

したがって、[内部スレッド]を読むと、これは修正できないように感じます(NLLを使用しても)。 誰かが確認できますか? (@eddyb?)

cc @ rest-long / long

これは確かにNLLであり、私が間違っていなければ、私が行ったさまざまな提案によって修正されるでしょう。 これは、私の最初のブログ投稿の「問題ケース#3」にほぼ対応しています。 なぜそれが機能するのか(最新のブログ投稿からの定式化の観点ifブランチの'aの終わりまで延長する必要があるだけであるということですif return ifを引き起こすreturn

@nikomatsakisNLLがこの問題とどのように相互作用するかについて詳しく教えてください。 &mut参照の私のメンタルモデルでは、それらは移動または再借用のいずれかで渡すことができます。この問題の問題は、戻りに移動モードとself再利用が必要なことです。その後、再借用が必要です。 私の理解では、新しく再借用された&mut参照の有効期間は、 &mut保持する変数の有効期間によって制限されます。この場合、 self変数であるため、次のように制限されます。関数の本体であるため、関数呼び出しの外部に拡張することはできません。 NLLは再借入の制限を変更する予定ですか(またはそのような制限はないかもしれません)?

また、この問題の修正は本質的にNLLに関連するものなのか、それとも直交しているのでしょうか。 後者の場合、NLLの前に修正を加える価値があるのでしょうか。

また、NLLがこの問題を修正しようとしている場合、それはNLLの下で、移動と再借用のどちらかを手動で選択する必要がないことを意味しますか?

これはすぐに修正される予定ですか?

@krdln

NLLがこの問題とどのように相互作用するかについて詳しく教えてください。

要約すると、今日、値がいずれかのパスの関数から返される場合、ローンはすべてのパスの関数の残りの部分に対して有効である必要があります。 私が書いたNLLの提案では、その制限は解除されました。

より詳細には、ここで詳細な署名を取得すると、有効期間が'a参照を返す必要があることがわかります。

fn two<'a>(&'a mut self) -> &'a i32 { .. }

ここで、 let k = self.one()を呼び出して、このk返すことにkなければなりません&'a i32 。 これは、 self.one()を呼び出すときに、タイプ&'a mut Selfself借用を提供する必要があることを意味します。 したがって、 let k = self.one() self 、生涯'aselfローンを発行します。 この存続期間はループ(そして実際には関数全体)よりも大きいため、ループを一周しても存続し、エラーレポートが表示されます。 私が提案したことをNLLのルールの下では、寿命のk唯一に拡張するために必要とされる'a場合if取られています。 したがって、 ifが取られない場合、ローンはより早く終了する可能性があります。

それは役に立ちますか?


@ osa1

これはすぐに修正される予定ですか?

これを実行する前に、まだいくつかの手順がありますが、必要なリファクタリングを実行するための積極的な作業があります。

@nikomatsakis
おかげで、それは幾分助けました。 しかし、私が理解していない小さなことが1つあります。それは、コードにselfを記述したときに正確に何が起こるか、つまりselfの再借用がどのように機能するかです。 「問題ケース3」( get_default )の説明を読みました。ここでは、呼び出し元内にコードをインライン化しますが、そこでは、 selfすべての使用を借用に変更しました。 map変数は直接変数なので、脱糖しても問題は解決しませんでした。

ここで私は行き詰まっています: let k = self.one()を呼び出すと、 selfは移動できないため(後で必要になるため)、借用と見なされます。 後で、そのkを条件付きで返すため、借用には有効期間'aであり、これは関数呼び出しよりも長持ちします。 だが! 関数が終了するまでしか存続しないselfから借用しました。 その制限は'aを短縮するように思われるので、私のメンタルモデルでは、NLLの下でも、「十分に長く生きられない」というエラーが発生するはずです。

@krdln実際に*selfから借用しました。つまり、自己が参照するものを再借用しました。 selfのタイプは、生涯'a 「ロック」を意味するため、 self自体よりも生涯にわたって*selfを借りることができます。 したがって、 'aにselfが使用されなくなることを保証できる限り、結果は排他的な参照になります。この場合、fnが戻った後、selfがポップされ、したがって、使用されないので、fnの終わりまでselfが使用されないことを保証するだけで十分です。 (少なくともそれが本当だといいのですが。=)

これは非字句の有効期間でブロックされ、MIRでブロックされます。 理想的には、これは2016年末までに修正される予定ですが、修正はすぐには行われません。

それほど驚くことではありませんが、非字句の有効期間機能をオンにすると、最初の例が実際

不変の借用が長すぎるように見える本番回避策)。

また、 starwedのコードは最新の毎晩コンパイルされなくなったことにも言及する必要があります。 @oberienは「 regression-from-nightly-to-nightly 」タグを提案しました。また、これが緊急の問題である場合に備えて付けることも提案されました:)

非常に似たようなことをしようとしてこの問題に遭遇しました(現在のnllでは許可されていません):

fn f(vec: &mut Vec<u8>) -> &u8 {
    if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
        *n = 10;
        n
    } else {
        vec.push(10);
        vec.last().unwrap()
    }
}

fn main() {
    let mut vec = vec![1, 2, 3];
    f(&mut vec);
}
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
 --> src/main.rs:6:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- first mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
5 |     } else {
6 |         vec.push(10);
  |         ^^^ second mutable borrow occurs here

error[E0502]: cannot borrow `*vec` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
...
7 |         vec.last().unwrap()
  |         ^^^ immutable borrow occurs here

(NLL-fixed-by-NLLを削除したときにE-needstestを削除する必要がありました)

このページは役に立ちましたか?
0 / 5 - 0 評価