2015-07-07 11 views
8

Nel codice seguente (playground):non possono prendere in prestito `x` come mutabile più di una volta in un momento

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 
} 

ho questo tipo di errore:

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 

La mia domanda sembra essere molto simile a Why does Rust want to borrow a variable as mutable more than once at a time?, ma non ne sono sicuro. Se è così, c'è una soluzione alternativa per questo caso?

risposta

13

Questo avviene a causa di come è definito add:

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

Qui si precisa che la durata del riferimento risultante dovrebbe essere uguale alla durata del riferimento in ingresso. L'unico modo è possibile (eccetto in pericolo) è che il riferimento risultante è in qualche modo deriva dal riferimento in ingresso, per esempio, si fa riferimento a qualche campo all'interno dell'oggetto punti di riferimento in ingresso a:

struct X { a: u32, b: u32 } 

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

Tuttavia, c'è no modo per il compilatore di sapere cosa viene preso in prestito esattamente dalla struttura in entrata guardando solo la firma della funzione (che, in generale, è l'unica cosa che può fare quando compila il codice che usa questa funzione). Pertanto, non si può sapere che il codice è tecnicamente corretto:

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

Abbiamo sappiamo che a e b sono disgiunti che puntano a diverse parti della struttura, ma il compilatore non può sapere che perché non c'è nulla nella firma borrow_a che lo suggerisca (e non ci può essere, Rust non lo supporta).

Pertanto, l'unica cosa sensata il compilatore potrebbe fare è quello di considerare l'intera x per essere presi in prestito fino a quando viene eliminato il riferimento restituito da borrow_a().Altrimenti sarebbe possibile creare due riferimenti mutabili per gli stessi dati, che è una violazione delle garanzie di aliasing di Rust.

Nota che il seguente codice è corretto:

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

Qui il compilatore può vedere che a e b Non puntare mai agli stessi dati, anche se lo fanno punto all'interno della stessa struttura.

Quindi, non c'è soluzione per questo, e l'unica soluzione sarebbe quella di ristrutturare il codice in modo che non abbia tali modelli di prestito.

+0

L'inferno è che se il compilatore non può elaborare il corpo della funzione per determinare le parti interessate delle strutture, e non c'è modo di dare un indizio su questo ... Quindi a) Perché hanno fatto questo? (nel nome delle gare di dati?); b) Come suggeriscono di codificare in tali situazioni? – tower120

+1

Il compilatore può elaborare i corpi delle funzioni quando sono nella tua cassa, ma come suggerisci di gestire le dipendenze esterne * binary *? Non contengono informazioni sufficienti per il compilatore. Pertanto, questo è l'unico modo in cui il modello di proprietà e il prestito possono essere resi sani, quindi sì, "hanno fatto questo" per un sacco di cose, compresa la lotta alle corse di dati. Per quanto riguarda il codice in tali situazioni, beh, non c'è modo di ristrutturare il codice, ad esempio, nel tuo caso specifico puoi dividere l'inserimento e la ricerca in due metodi, dove insert non restituirà nulla, evitando così il prestito . –

+0

"Il compilatore può elaborare i corpi delle funzioni quando sono nella tua cassa, ma come suggerisci di gestire le dipendenze binarie esterne?" - Segna come non sicuro, non gestirlo (sono esterni, dopotutto). "Per quanto riguarda come codificare in tali situazioni, beh, non c'è modo di ristrutturare il tuo codice" - Non mi piace. Ristruttura codice solo per avere un senso per il compilatore? Non suona affatto ... – tower120

2

Il comportamento è logico. Considerate ciò che

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

mezzi.

Questo dice che &mut Node ha una durata pari a uguale a fino alla durata del suo valore restituito. Poiché si assegna il valore restituito a un nome, esso rimane attivo fino alla fine dell'ambito. Quindi, il prestito mutabile vive anche così a lungo.

Se è possibile scartare facilmente il valore restituito, farlo. Si può solo cadere sul pavimento:

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

add(&mut tree, "level1"); 

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

oppure è possibile utilizzare un ambito lessicale per vincolare senza cadere completamente.

Se si desidera prendere in prestito il ritorno senza forzando il prestito mutabile a vivere così a lungo, probabilmente si dovrà dividere la funzione in due. Questo perché non si può prendere in prestito il valore restituito dal mutuo mutabile per farlo.

+0

"Perché si prolunga la durata del valore di ritorno assegnandolo a un valore ..." - Posso fare qualcosa come "albero:" nodo (il nodo non supera la durata dell'albero)? E se estende la durata, perché prende in prestito variabili (la vita è una vita, la variabile è variabile)? – tower120

+0

La durata è per il riferimento, non per l'oggetto di riferimento. Non sono sicuro che questo risponda alla tua domanda. – Veedrac

+0

Ok, guarda qui https://play.rust-lang.org/?gist=9728baf56379ee4699ec&version=stable. Sembra che la ruggine consideri il riferimento all'elemento dell'oggetto, come il prestito dell'oggetto stesso. Forse questo è il problema? O mi sbaglio? – tower120

Problemi correlati