Prima di tutto, tutti gli elementi elencati sono cose molto diverse, anche se sono correlati ai puntatori. Box
è un tipo di puntatore intelligente definito dalla libreria; ref
è una sintassi per la corrispondenza del modello; &
è un operatore di riferimento, che raddoppia come un sigillo nei tipi di riferimento; *
è un operatore di dereferenziazione, che raddoppia come un sigillo nei tipi di puntatore non elaborati. Vedi sotto per ulteriori spiegazioni.
Ci sono quattro tipi di puntatore di base in Rust che possono essere suddivisi in due gruppi - i riferimenti e puntatori prime:
&T - immutable (shared) reference
&mut T - mutable (exclusive) reference
*const T - immutable raw pointer
*mut T - mutable raw pointer
Differenza tra gli ultimi due è molto sottile perché o possono essere fusa ad un altro senza alcuna restrizione , quindi la distinzione const
/mut
serve principalmente come sfilacciamento. Puntatori grezzi possono essere creati liberamente per qualsiasi cosa, e possono anche essere creati dal nulla dagli interi, ad esempio.
Naturalmente, non è così per i riferimenti: i tipi di riferimento e la loro interazione definiscono una delle caratteristiche chiave di Rust, prendendo in prestito. I riferimenti hanno molte restrizioni su come e quando potrebbero essere creati, come potrebbero essere utilizzati e come interagiscono tra loro. In cambio possono essere utilizzati senza blocchi unsafe
. Tuttavia, quale prestito è esattamente e come funziona è fuori dallo scopo di questa risposta.
Entrambi i riferimenti e puntatori prime possono essere creati utilizzando &
operatore:
let x: u32 = 12;
let ref1: &u32 = &x;
let raw1: *const u32 = &x;
let ref2: &mut u32 = &mut x;
let raw2: *mut u32 = &mut x;
Entrambi i riferimenti e puntatori prime possono essere dereferenziato utilizzando *
operatore, anche se per i puntatori prime richiede un unsafe
blocco:
*ref1; *ref2;
unsafe { *raw1; *raw2; }
L'operatore di riferimento viene spesso omesso perché un altro operatore, denominato punto .
, fa automaticamente riferimento o dereferenzia l'argomento di sinistra.Così, per esempio, se abbiamo queste definizioni:
struct X { n: u32 };
impl X {
fn method(&self) -> u32 { self.n }
}
allora, nonostante che method()
prende self
per riferimento, self.n
dereferenziazioni automaticamente, in modo da non dover digitare (*self).n
. cosa simile accade quando method()
si chiama:
let x = X { n: 12 };
let n = x.method();
Qui il compilatore fa riferimento automaticamente x
in x.method()
, in modo da non dovrà scrivere (&x).method()
.
Il penultimo codice mostra anche la sintassi speciale &self
. Significa solo self: &Self
o, più precisamente, self: &X
in questo esempio. &mut self
, *const self
, *mut self
funzionano anche.
Quindi, i riferimenti sono il tipo di puntatore principale in Rust e dovrebbero essere utilizzati quasi sempre. I puntatori grezzi, che non hanno restrizioni di riferimento, dovrebbero essere usati nel codice di basso livello che implementa le astrazioni di alto livello (collezioni, puntatori intelligenti, ecc.) E in FFI (interagendo con le librerie C).
Rust ha anche dynamically-sized (or unsized) types. Questi tipi non hanno una dimensione definita staticamente definita e quindi possono essere utilizzati solo tramite un puntatore/riferimento, tuttavia, solo un puntatore non è sufficiente - sono necessarie informazioni aggiuntive, ad esempio, la lunghezza per le sezioni o un puntatore a metodi virtuali tavolo per oggetti tratti. Questa informazione è "incorporata" nei puntatori ai tipi non registrati, rendendo questi puntatori "grassi".
Un puntatore di grasso è fondamentalmente una struttura che contiene il puntatore effettivo al pezzo di dati e alcune informazioni aggiuntive (lunghezza per sezioni, puntatore a vtable per oggetti tratto). Ciò che è importante qui è che Rust gestisce questi dettagli sul contenuto del puntatore in modo assolutamente trasparente per l'utente: se si superano i valori o *mut SomeTrait
, le informazioni interne corrispondenti verranno automaticamente trasferite.
Box<T>
è uno dei puntatori intelligenti nella libreria standard di Rust. Fornisce un modo per allocare memoria sufficiente sull'heap per memorizzare un valore del tipo corrispondente e quindi funge da handle, un puntatore a quella memoria. Box<T>
possiede i dati a cui punta; quando viene rilasciato, il corrispondente pezzo di memoria sull'heap viene deallocato.
Un modo molto utile di pensare alle scatole è considerarle valori normali, ma con dimensioni fisse. Cioè, Box<T>
equivale a solo T
, tranne che prende sempre un numero di byte che corrispondono alla dimensione del puntatore del tuo computer. Diciamo che le scatole (di proprietà) forniscono la semantica valore. Internamente sono implementati usando puntatori grezzi, come quasi ogni altra astrazione di alto livello.
Box
es (in realtà, questo è vero per quasi tutti gli altri puntatori intelligenti, come Rc
) possono anche essere presi in prestito: è possibile ottenere un &T
di Box<T>
. Questo può succedere automaticamente con .
operatore o si può farlo in modo esplicito dereferenziando e riferimento di nuovo:
let x: Box<u32> = Box::new(12);
let y: &u32 = &*x;
A questo proposito Box
es sono simili a built-in puntatori - è possibile utilizzare dereference all'operatore di raggiungere i loro contenuti.Ciò è possibile perché l'operatore di dereferenziazione in Rust è sovraccarico ed è sovraccarico per la maggior parte (se non tutti) dei tipi di puntatore intelligente. Ciò consente un facile prestito di questi contenuti di puntatori.
Infine, ref
è solo una sintassi nei modelli per ottenere una variabile di tipo di riferimento anziché valore. Ad esempio:
let x: u32 = 12;
let y = x; // y: u32, a copy of x
let ref z = x; // z: &u32, points to x
let ref mut zz = x; // zz: &mut u32, points to x
Mentre l'esempio di cui sopra può essere riscritta con operatori di riferimento:
let z = &x;
let zz = &mut x;
(che potrebbe anche rendere più idiomatica), ci sono casi in cui ref
s indispensabili, per esempio, quando prendendo riferimenti in varianti enum:
let x: Option<Vec<u32>> = ...;
match x {
Some(ref v) => ...
None => ...
}
nell'esempio sopra x
è preso in prestito solo all'interno dell'intero match
istruzione, che consente di utilizzare x
dopo questo match
. Se scriviamo come tale:
match x {
Some(v) => ...
None => ...
}
poi x
saranno consumati da questa match
e diventerà inutilizzabile dopo di esso.
Risposta straordinaria, grazie. Sono stato in grado di fare un po 'più di ricerca per conto mio, e ho scoperto che il modo per sovraccaricare l'operazione di dereferenziazione è implementare la caratteristica 'Deref'. (Lasciando questo qui per chiunque altro legga la risposta ed è curioso). – zrneely