2015-07-07 3 views
8

Im folgenden Code (playground):Kann leiht nicht 'X' als wandelbar mehr als einmal zu einem Zeitpunkt

struct Node { 
    datum: &'static str, 
    edges: Vec<Node>, 
} 

fn add<'a>(node: &'a mut Node, data: &'static str) -> &'a Node { 
    node.edges 
     .push(Node { 
        datum: data, 
        edges: Vec::new(), 
       }); 
    &node.edges[node.edges.len() - 1] // return just added one 
} 

fn traverse<F>(root: &Node, callback: &F) 
    where F: Fn(&'static str) 
{ 
    callback(root.datum); 
    for node in &root.edges { 
     traverse(node, callback); 
    } 
} 

fn main() { 
    let mut tree = Node { 
     datum: "start", 
     edges: Vec::new(), 
    }; 

    let lvl1 = add(&mut tree, "level1"); 

    traverse(&mut tree, &|x| println!("{:}", x)); //I actually don't need mutability here 
} 

Ich habe diese Art von Fehlern:

error[E0499]: cannot borrow `tree` as mutable more than once at a time 
    --> src/main.rs:32:19 
    | 
30 |  let lvl1 = add(&mut tree, "level1"); 
    |       ---- first mutable borrow occurs here 
31 | 
32 |  traverse(&mut tree, &|x| println!("{:}", x)); //I actually don't need mutability here 
    |     ^^^^ second mutable borrow occurs here 
33 | } 
    | - first borrow ends here 

Meine Frage scheint zu sein, sehr ähnlich zu Why does Rust want to borrow a variable as mutable more than once at a time?, aber ich bin mir nicht sicher. Wenn ja, gibt es einen Workaround für diesen Fall?

Antwort

13

Dies geschieht, weil, wie add definiert:

fn add<'a>(node: &'a mut Node, data : &'static str) -> &'a Node 

Hier wird festgelegt, dass die Lebensdauer der resultierenden soll auf die Lebensdauer der eingehenden Referenz gleich sein. Die nur Weise ist es möglich (außer bei unsicherem Code) ist, dass die resultierende Soll irgendwie aus der ankommenden Referenz, beispielsweise abgeleitet wird, verweist es einige Felder innerhalb des Objekts, die eingehenden Referenzpunkte auf:

struct X { a: u32, b: u32 } 

fn borrow_a<'a>(x: &'a mut X) -> &'a mut u32 { 
    &mut x.a 
} 

Allerdings gibt es keine Weg für den Compiler zu wissen was genau von der eingehenden Struktur ist geliehen, indem nur auf die Funktion Signatur (die im Allgemeinen ist das einzige, was es tun kann, wenn Code kompilieren, die diese Funktion verwendet). Daher kann es nicht wissen, dass der folgende Code technisch korrekt ist:

let mut x = X { a: 1, b: 2 }; 
let a = borrow_a(&mut x); 
let b = &mut x.b; 

Wir wissen, dass a und b disjunkt sind, weil sie in verschiedenen Teilen der Struktur zeigen, aber der Compiler kann nicht wissen, dass weil es nichts in borrow_a Signatur gibt, die es vorschlagen würde (und es kann nicht sein, Rust unterstützt es nicht).

Daher ist die einzige vernünftige Sache der Compiler tun könnte, ist die ganzex betrachten entlehnt werden, bis die Referenz zurückgegeben von borrow_a() fallen gelassen wird.Andernfalls wäre es möglich, zwei veränderbare Referenzen für die gleichen Daten zu erstellen, was eine Verletzung der Alias-Garantien von Rust darstellt.

Beachten Sie, dass der folgende Code korrekt ist:

let mut x = X { a: 1, b: 2 }; 
let a = &mut x.a; 
let b = &mut x.b; 

Hier ist der Compiler kann sehen, dass a und b nie auf die gleichen Daten zeigen, auch wenn sie in der gleichen Struktur Punkt tun.

Also gibt es keine Problemumgehung dafür, und die einzige Lösung wäre, den Code neu zu strukturieren, so dass es solche Ausleihmuster nicht hat.

+0

Der Teufel ist das. Wenn der Compiler den Funktionskörper nicht verarbeiten kann, um betroffene Teile von Strukturen zu bestimmen, und es gibt keine Möglichkeit, einen Hinweis darauf zu geben ... Dann a) Warum haben sie das gemacht? (im Namen von Datenrennen?); b) Wie schlagen sie vor, in solchen Situationen zu programmieren? – tower120

+1

Der Compiler kann Funktionskörper verarbeiten, wenn sie sich in Ihrer Kiste befinden, aber wie schlagen Sie vor, externe * binäre * Abhängigkeiten zu behandeln? Sie enthalten nicht genügend Informationen für den Compiler. Daher ist dies der einzige Weg, das Modell des Eigentums und der Kreditaufnahme zu vervollkommnen, also ja, "sie haben das getan" für viele Dinge einschließlich der Bekämpfung von Datenrennen. Wie man in solchen Situationen programmieren kann, gibt es keine Möglichkeit, den Code neu zu strukturieren, zum Beispiel können Sie in Ihrem speziellen Fall die Einfügung und die Suche in zwei Methoden aufteilen, wobei die Einfügung nichts zurückgibt, wodurch das Ausleihen vermieden wird . –

+0

"Der Compiler kann Funktionskörper verarbeiten, wenn sie in Ihrer Kiste sind, aber wie schlagen Sie vor, externe binäre Abhängigkeiten zu behandeln?" - Als unsicher markieren, nicht behandeln (sie sind extern, afterall). "Wie man in solchen Situationen programmiert, es gibt keinen anderen Weg, als den Code neu zu strukturieren" - das mag ich nicht. Code umstrukturieren, nur um dem Compiler einen Sinn zu geben? Es hört sich überhaupt nicht gut an ... – tower120

2

Das Verhalten ist logisch. Überlegen Sie, was

fn add<'a>(node: &'a mut Node, data: &'static str) -> &'a Node 

bedeutet.

Dies besagt, dass &mut Node eine Lebensdauer gleich auf die Lebensdauer seines Rückgabewerts hat. Da Sie den Rückgabewert einem Namen zuweisen, bleibt er bis zum Ende des Bereichs bestehen. Daher lebt der veränderbare Kredit auch so lange.

Wenn Sie den Rückgabewert einfach verwerfen können, tun Sie dies. Sie können Drop einfach auf dem Boden:

let mut tree = Node{ datum : "start", edges: Vec::new() }; 

add(&mut tree, "level1"); 

traverse(&mut tree, &|x| println!("{:}", x)); 

oder Sie können einen lexikalischen Bereich verwenden, es zu beschränken, ohne es vollständig fallen zu lassen.

Wenn Sie die Rückkehr ohne ausleihen wollen, die das veränderbare Borgen zwingen, um auch zu leben, werden Sie wahrscheinlich die Funktion in zwei teilen müssen. Dies liegt daran, dass Sie nicht in der Lage sind, den Rückgabewert aus dem veränderbaren Kredit zu leihen, um dies zu tun.

+0

"Weil Sie die Lebensdauer des Rückgabewertes verlängern, indem Sie ihm einen Wert zuweisen ..." - Kann ich etwas wie 'tree:' tun (Knoten überschreitet nicht die Lebensdauer von Bäumen)? Und wenn es die Lebensdauer verlängert, warum leiht es Variable (Lebensdauer ist eine Lebenszeit, Variable ist variabel)? – tower120

+0

Die Lebensdauer ist für die Referenz, nicht für das referenzierte Objekt. Ich bin mir nicht sicher, ob das deine Frage beantwortet. – Veedrac

+0

Ok, schau hier https://play.rust-lang.org/?gist=9728baf56379ee4699ec&version=stable. Es scheint, dass Rost den Verweis auf das Element des Objekts als Ausleihe des Objekts selbst betrachtet. Vielleicht ist das das Problem? Oder liege ich falsch? – tower120

Verwandte Themen