Rust: & mut selbst ausleihen, die mit sich selbst in Konflikt stehen.

Erstellt am 3. Feb. 2015  ·  20Kommentare  ·  Quelle: rust-lang/rust

Ich weiß nicht, warum das passiert, aber Rovar und XMPPwocky im IRC glaubten, es sei ein Compiler-Fehler.

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;
            }
        }
    }
}

... gibt folgenden Fehler aus ...

<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

Interessanterweise, wenn die zweite Methode auf ...

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

Dies wird gut kompiliert.

Laufstall: http://is.gd/mTkfw5

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

Hilfreichster Kommentar

Ich glaube, ich bin in der Produktion auf ein ähnliches Problem gestoßen, bei dem ein unveränderlicher Kredit zu lange zu leben scheint ( Problemumgehung ).

Ich sollte auch erwähnen, dass der Code von starwed nicht mehr spätestens nachts kompiliert wird. @oberien schlug ein " regression-from-nightly-to-nightly " -Tag vor, und es wurde auch vorgeschlagen, @nikomatsakis zu taggen, falls dies ein dringendes Problem war :)

Alle 20 Kommentare

Ich glaube, das ist korrektes Verhalten. Um zu sehen, warum, betrachten Sie den Code mit expliziten Anmerkungen zur Lebensdauer:

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 muss eine Referenz mit der gleichen Lebensdauer wie die veränderliche Ausleihe zurückgeben. Da es aber seine bekommt. Dies bedeutet, dass k eine Lebensdauer von 'a . Damit der zurückgegebene Wert von one eine Lebensdauer von 'a , muss die Eingabe von one auch eine Lebensdauer von 'a . Dies bedeutet, dass Rust gezwungen ist, self nicht "neu auszuleihen" und in one . Da veränderbare Referenzen linear sind, können sie nur einmal verschoben werden. loop bedeutet jedoch, dass one möglicherweise wiederholt mit derselben veränderlichen Referenz aufgerufen werden muss.

Das zweite Beispiel funktioniert nur, weil Rust sehen kann, dass die Schleife nur einmal ausgeführt wird, sodass self nur einmal verschoben wird.

Dies scheint ein nicht lexikalisches Problem zu sein. Sie können ein ähnliches Verhalten ohne Schleifen erzielen:

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 kann mit bedingten Kreditrenditen nicht "umgehen".

CC @pcwalton

Ich denke, das Problem ist tatsächlich, dass nicht die Veränderbarkeit des Rückgabewerts verwendet wird, sondern die Veränderlichkeit des Funktionsarguments.

Der folgende Code ruft eine Mutationsfunktion auf, die eine unveränderliche Referenz zurückgibt. Danach kann ich keine unveränderlichen Referenzen mehr nehmen.

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
}

Laufstall

Ich stoße immer noch spätestens jeden Abend auf dieses Problem. Es scheint, dass Retouren bis zum Ende der Funktion als lebenslang behandelt werden, selbst wenn die Funktion ohne Retoure den Ausleihprüfer besteht. Dies scheint seltsam, wenn man bedenkt, wie der Sinn einer Rückkehr darin besteht, die Funktion vorzeitig zu beenden.

Wenn Sie sich das Beispiel von @Gankro ansehen, besteht es die

Dies ist bei nicht lexikalischen Lebensdauern blockiert, was wiederum bei MIR blockiert ist. Idealerweise wird dies bis Ende 2016 behoben sein, aber ein Fix wird nicht bald landen.

Diskussion über nicht lexikalische Ausleihen: https://github.com/rust-lang/rfcs/issues/811

Wenn ich also den [internen Thread] lese, habe ich das Gefühl, dass dies nicht behoben werden kann (selbst mit NLL). Kann jemand bestätigen? (@eddyb?)

cc @ rest-long / long

Dies ist in der Tat NLL, und wenn ich mich nicht irre, würde dies durch die verschiedenen Vorschläge, die ich gemacht habe, behoben. Dies entspricht in etwa "Problemfall Nr. 3" aus meinem ersten Blog-Beitrag . Die Grundidee (in Bezug auf die Formulierung aus dem neuesten Blog-Beitrag ), warum es funktionieren würde, ist, dass die Untertypisierung des Ausleihs nur bis zum Ende von 'a im Zweig des if , das ein return .

@nikomatsakis Könnten Sie bitte näher erläutern, wie NLL mit diesem Problem interagiert? In meinem mentalen Modell von &mut Referenzen können sie entweder durch Verschieben oder erneutes Ausleihen übergeben werden. Das Problem in dieser Ausgabe besteht darin, dass für die Rückgabe der Verschiebemodus und die Wiederverwendung von self erforderlich sind danach muss neu ausgeliehen werden . Nach meinem Verständnis ist die Lebensdauer der neu &mut -Referenz durch die Lebensdauer der Variablen begrenzt, die die &mut -Referenz enthält - in diesem Fall die Variable self , daher ist sie begrenzt durch Der Funktionskörper kann sich daher nicht über den Funktionsaufruf hinaus erstrecken. Wird die NLL diese Einschränkung der erneuten Ausleihe ändern (oder gibt es möglicherweise keine solche Einschränkung)?

Ich frage mich auch, ob die Behebung dieses Problems von Natur aus mit NLL zusammenhängt oder vielleicht orthogonal ist. Wenn es das letztere ist, lohnt es sich vielleicht, einen Fix vor NLL zu landen?

Wenn NLL dieses Problem beheben soll, bedeutet dies dann, dass Sie unter NLL niemals manuell zwischen einem Umzug und einem erneuten Ausleihen wählen müssen?

Wird dies bald behoben sein?

@krdln

Könnten Sie bitte näher darauf eingehen, wie NLL mit diesem Problem interagiert?

Die Zusammenfassung lautet: Wenn heute auf einem Pfad ein Wert aus der Funktion zurückgegeben wird, muss das Darlehen für den Rest der Funktion auf allen Pfaden gültig sein. Nach dem von mir verfassten NLL-Vorschlag wird diese Einschränkung aufgehoben.

Wenn Sie die ausgearbeitete Signatur hier verwenden, können Sie genauer sehen, dass wir eine Referenz mit einer Lebensdauer von 'a :

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

Beachten Sie nun, dass wir let k = self.one() aufrufen und dann diese k . Dies bedeutet, dass der Typ von k &'a i32 . Dies bedeutet wiederum, dass wir, wenn wir self.one() , einen Kredit von self mit dem Typ &'a mut Self bereitstellen müssen. Daher geben wir bei let k = self.one() ein Darlehen von self mit einer Lebensdauer von 'a . Diese Lebensdauer ist größer als die Schleife (und in der Tat die gesamte Funktion), sodass sie beim Umrunden der Schleife bestehen bleibt und zum Fehlerbericht führt. Nach den von mir vorgeschlagenen NLL-Regeln muss die Lebensdauer von k nur dann auf 'a wenn die if genommen werden. Wenn die if nicht genommen werden, kann das Darlehen daher früher enden.

Hilft das?


@ osa1

Wird dies bald behoben sein?

Es sind noch einige Schritte zu tun, bevor wir dies tun können, aber es wird aktiv daran gearbeitet, die erforderlichen Refactorings durchzuführen.

@ Nikomatsakis
Danke, es hat etwas geholfen. Aber es gibt eine Kleinigkeit, die ich nicht verstehe - was genau passiert, wenn Sie self in Ihren Code schreiben - wie das erneute Ausleihen von self funktioniert. Ich habe Ihre Erklärung zum "Problemfall 3" ( get_default ) gelesen, in dem Sie den Code im Anrufer einfügen, aber dort haben Sie jede Verwendung von self in Ausleihen des geändert map Variable direkt, so dass Desugaring es für mich nicht geklärt hat.

Hier stecke ich fest: Wenn wir let k = self.one() , können die self nicht verschoben werden (weil sie später benötigt werden), daher wird sie als ausgeliehen betrachtet. Später geben wir diese k Bedingungen zurück, sodass die Ausleihe eine Lebensdauer von 'a , was den Funktionsaufruf überlebt. Aber! Wir haben uns von self geliehen, das nur bis zum Ende der Funktion gültig ist. Diese Einschränkung scheint 'a zu verkürzen, daher sollten wir in meinem mentalen Modell selbst unter NLL den Fehler "lebt nicht lange genug" erhalten.

@krdln wir haben uns tatsächlich von *self geliehen - das heißt, wir haben neu ausgeliehen, worauf sich das Selbst bezieht. Wir erlauben Ihnen, *self für ein Leben lang auszuleihen, das self selbst überlebt, da die Art von self eine 'Sperre' für das gesamte Leben 'a impliziert. Solange wir also garantieren können, dass während 'a self nicht mehr verwendet wird, sollte das Ergebnis eine exklusive Referenz sein - in diesem Fall wurde self nach der Rückkehr von fn gepoppt und könnte es daher nicht verwendet werden, also nur garantieren, dass das Selbst nicht verwendet wird, bis das Ende des fn ausreicht. (Zumindest hoffe ich, dass das stimmt. =)

Dies ist bei nicht lexikalischen Lebensdauern blockiert, was wiederum bei MIR blockiert ist. Idealerweise wird dies bis Ende 2016 behoben sein, aber ein Fix wird nicht bald landen.

Nicht sehr überraschend, aber ich habe bestätigt, dass das erste Beispiel tatsächlich jede Nacht kompiliert wird, wenn Sie die Funktion für nicht lexikalische Lebensdauern aktivieren.

Ich glaube, ich bin in der Produktion auf ein ähnliches Problem gestoßen, bei dem ein unveränderlicher Kredit zu lange zu leben scheint ( Problemumgehung ).

Ich sollte auch erwähnen, dass der Code von starwed nicht mehr spätestens nachts kompiliert wird. @oberien schlug ein " regression-from-nightly-to-nightly " -Tag vor, und es wurde auch vorgeschlagen, @nikomatsakis zu taggen, falls dies ein dringendes Problem war :)

Stolperte über dieses Problem beim Versuch, etwas sehr Ähnliches zu tun (was von der aktuellen nll nicht erlaubt ist):

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

(Wir hätten E-Needstest entfernen sollen, als wir NLL-Fixed-by-NLL entfernt haben.)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen