2015-07-31 20 views
14

Dal Rust book su come mutare campi struct:Capire struct campo mutazione

let mut point = Point { x: 0, y: 0 }; 
point.x = 5; 

e versioni successive:

Mutabilità è una proprietà del legame, non di struttura stessa.

Questo sembra contro-intuitivo perché point.x = 5 non sembra come se fossi rebinding la variabile point. C'è un modo per spiegare questo, quindi è più intuitivo?

L'unico modo in cui posso avvolgere la mia testa intorno a questo è quello di "immaginare" che sto rebinding point ad una copia dell'originale Point con un valore diverso x (nemmeno sicuro che sia accurata).

risposta

5

Ho avuto la stessa confusione. Per me è venuto da due distinti equivoci. Per prima cosa, provengo da un linguaggio in cui le variabili (ovvero i collegamenti) erano implicitamente riferimenti a valori. In quel linguaggio era importante distinguere tra mutare il riferimento e mutare il valore a cui ci si riferiva. In secondo luogo, ho pensato per "la struttura stessa" il libro si riferiva al valore istanziato, ma per "la struttura" si intende la specifica/dichiarazione, non un particolare valore di quel tipo.

Le variabili in Ruggine sono diverse.Dalla reference:

Una variabile è un componente di uno stack frame ...

Una variabile locale (o stack-locale allocazione) possieda un valore direttamente, assegnati nell'ambito memoria dello stack. Il valore è una parte del frame dello stack .

Quindi una variabile è un componente di uno stack frame - un pezzo di memoria - che direttamente contiene il valore. Non c'è alcun riferimento per distinguere dal valore stesso, nessun riferimento a mutare. La variabile e il valore sono lo stesso pezzo di memoria.

Una conseguenza è che la riconnessione di una variabile nel senso di cambiarla in riferimento a un diverso blocco di memoria non è compatibile con il modello di memoria di Rust. (n. let x = 1; let x = 2; crea due variabili.)

Quindi il libro sottolinea che la mutabilità è dichiarata al livello "per hunk of memory" piuttosto che come parte della definizione di una struttura.

L'unico modo in cui posso avvolgere la mia testa intorno a questo è quello di "immaginare" che sono punto rebinding ad una copia del punto originale con un diverso valore di x (non sono nemmeno sicuro che sia accurato)

Immaginate invece di cambiare uno degli 0 in un blocco di memoria a 5; e che quel valore risieda nella memoria designata da point. Interpretare "il legame è mutabile" per significare che è possibile mutare il pezzo di memoria designato dal legame, inclusa la mutazione di una parte di esso, ad es. impostando un campo struct. Pensa di ribattere le variabili di ruggine nel modo in cui descrivi come non espressivo in Rust.

+0

Mi hai impostato sulla strada giusta Avevo bisogno di scrivere una demo per vederlo in azione (vedi la mia risposta) – Kelvin

+0

@ Kelvin Grazie per aver postato la tua demo. È un bel modo di metterlo. –

6

Qui "binding" non è un verbo, è un nome. Puoi dire che in Rust i bind sono sinonimi di variabili. Pertanto, puoi leggere quel passaggio come

La mutabilità è una proprietà della variabile, non della struttura stessa.

Ora, immagino, dovrebbe essere chiaro: si contrassegna la variabile come mutabile e quindi è possibile modificarne il contenuto.

+2

Ok, penso che questo aiuti un po '. Quindi è come se "mut" di fronte a una variabile applichi solo una proprietà * mutevole * alla variabile. E quella proprietà implica certe cose, ad es. rebinding e modifica del campo struct. È esatto? – Kelvin

+0

Sì, hai quasi sempre ragione (tranne il fatto che 'mut' non influisce sulla rebinding nel senso che puoi ancora scrivere' let x = 10; sia x = 12' anche se 'x' non è' mut'. mut 'effettivamente permette di assegnare il valore (o la variabile stessa o un campo al suo interno se si tratta di una struttura) e di prendere i riferimenti '& mut', di nuovo, al valore o ad un sottocampo. –

10

Mi sembra controintuitivo perché point.x = 5 non sembra che sto riconfigurando il punto variabile. C'è un modo per spiegare questo, quindi è più intuitivo?

Tutto questo sta dicendo è che se qualcosa è mutevole è determinato dal let - economico (il legame) della variabile, invece di essere una proprietà del tipo o ogni settore specifico.

Nell'esempio, point e suoi campi sono mutabili perché point viene introdotto in una dichiarazione let mut (in contrapposizione ad un semplice let dichiarazione) e non a causa di alcune proprietà del tipo Point in generale.

Come contrasto, di mostrare perché questo è interessante: in altre lingue, come OCaml, è possibile contrassegnare alcuni campi mutabili nella definizione del tipo:

type point = 
    { x: int; 
    mutable y: int; 
    }; 

Ciò significa che è possibile mutare la y campo di ogni valore point, ma non è mai possibile mutare x.

4

La risposta di @ m-n mi ha impostato sulla strada giusta. Si tratta di indirizzi stack! Ecco una dimostrazione che ha solidificato nella mia mente cosa sta realmente accadendo.

struct Point { 
    x: i64, 
    y: i64, 
} 

fn main() { 
    { 
     println!("== clobber binding"); 
     let a = 1; 
     println!("val={} | addr={:p}", a, &a); 
     // This is completely new variable, with a different stack address 
     let a = 2; 
     println!("val={} | addr={:p}", a, &a); 
    } 
    { 
     println!("== reassign"); 
     let mut b = 1; 
     println!("val={} | addr={:p}", b, &b); 
     // uses same stack address 
     b = 2; 
     println!("val={} | addr={:p}", b, &b); 
    } 
    { 
     println!("== Struct: clobber binding"); 
     let p1 = Point{ x: 1, y: 2 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     let p1 = Point{ x: 3, y: 4 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 
    } 
    { 
     println!("== Struct: reassign"); 
     let mut p1 = Point{ x: 1, y: 2 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     // each of these use the same addresses; no new addresses 
     println!(" (entire struct)"); 
     p1 = Point{ x: 3, y: 4 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     println!(" (individual members)"); 
     p1.x = 5; p1.y = 6; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 
    } 
} 

uscita (gli indirizzi sono ovviamente un po 'diverso per ogni corsa):

== clobber binding 
val=1 | addr=0x7fff6112863c 
val=2 | addr=0x7fff6112858c 
== reassign 
val=1 | addr=0x7fff6112847c 
val=2 | addr=0x7fff6112847c 
== Struct: clobber binding 
xval,yval=(1, 2) | pointaddr=0x7fff611282b8, xaddr=0x7fff611282b8, yaddr=0x7fff611282c0 
xval,yval=(3, 4) | pointaddr=0x7fff61128178, xaddr=0x7fff61128178, yaddr=0x7fff61128180 
== Struct: reassign 
xval,yval=(1, 2) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 
    (entire struct) 
xval,yval=(3, 4) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 
    (individual members) 
xval,yval=(5, 6) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 

I punti chiave sono questi:

  • Usa let a "clobber" un (nuovo indirizzo esistente vincolante pila). Ciò accade anche se la variabile è stata dichiarata mut, quindi fai attenzione.
  • Utilizzare mut per riutilizzare l'indirizzo stack esistente, ma non utilizzare let durante la riassegnazione.

Questo test rivela un paio di cose interessanti:

  • Se si riassegna un intero struct mutabile, è equivalente ad assegnare ogni membro singolarmente.
  • L'indirizzo della variabile che contiene la struct è uguale all'indirizzo del primo membro. Immagino che questo abbia senso se provieni da uno sfondo C/C++.