Come probabilmente già sapete, i tipi in Rust possono essere dimensionati e non dimensionati. I tipi non standard, come suggerisce il loro nome, non hanno una dimensione necessaria per memorizzare valori di questo tipo che sono noti al compilatore. Ad esempio, [u32]
è una matrice non numerata di u32
s; poiché il numero di elementi non è specificato da nessuna parte, il compilatore non ne conosce la dimensione. Un altro esempio è un nudo tipo di oggetto tratto, ad esempio, Display
, quando viene utilizzato direttamente come Tipo:
let x: Display = ...;
In questo caso, il compilatore non sa quale tipo viene effettivamente utilizzato qui, si cancella, quindi non conosce la dimensione dei valori di questi tipi. La riga sopra non è valida - non è possibile creare una variabile locale senza conoscere la sua dimensione (per allocare abbastanza byte nello stack) e si non può passare il valore di un tipo non salvato in una funzione come argomento o restituirlo da uno.
I tipi non registrati possono essere utilizzati tramite un puntatore, tuttavia, che può contenere informazioni aggiuntive: la lunghezza dei dati disponibili per le porzioni (&[u32]
) o un puntatore a un tavolo virtuale (Box<SomeTrait>
). Poiché i puntatori hanno sempre una dimensione fissa e nota, possono essere memorizzati in variabili locali e passati o restituiti da funzioni.
Dato qualsiasi tipo di calcestruzzo si può sempre dire se è dimensionato o non dimensionato. Con i generici, tuttavia, sorge una domanda: alcuni parametri di tipo sono dimensionati o no?
fn generic_fn<T>(x: T) -> T { ... }
Se T
è non calibrati, allora tale definizione di funzione non è corretto, in quanto non è possibile passare i valori non calibrati in giro direttamente. Se è dimensionato, allora tutto è OK.
In Rust tutti i parametri di tipo generico vengono ridimensionati di default ovunque, nelle funzioni, nelle strutture e nei tratti.Hanno un limite implicito Sized
; Sized
è un tratto per la marcatura tipi di dimensioni:
fn generic_fn<T: Sized>(x: T) -> T { ... }
Questo perché nella stragrande maggioranza delle volte si desidera che i parametri generici per essere di dimensioni. A volte, tuttavia, che ci si vuole scegliere di sizedness, e questo può essere fatto con ?Sized
vincolati:
fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }
Ora generic_fn
può essere chiamato come generic_fn("abcde")
, e T
vengono istanziati con str
che è non calibrati, ma va bene - questa funzione accetta un riferimento a T
, quindi non succede niente di male.
Tuttavia, c'è un altro posto in cui la questione della grandezza è importante. Tratti a Rust sono sempre implementate per un certo tipo:
trait A {
fn do_something(&self);
}
struct X;
impl A for X {
fn do_something(&self) {}
}
Tuttavia, questo è necessaria solo per scopi di comodità e praticità. E 'possibile definire i tratti di prendere sempre un parametro di tipo e di non specificare il tipo di tratto è implementato per:
// this is not actual Rust but some Rust-like language
trait A<T> {
fn do_something(t: &T);
}
struct X;
impl A<X> {
fn do_something(t: &X) {}
}
Ecco come classi di tipo Haskell lavoro, e, in effetti, è così che i tratti sono effettivamente implementate in Ruggine a un livello inferiore.
Ogni tratto in Rust ha un parametro di tipo implicito, denominato Self
, che indica il tipo per cui questo tratto è implementato. È sempre disponibile nel corpo del carattere:
trait A {
fn do_something(t: &Self);
}
Ecco dove entra in gioco la questione della grandezza. Il parametro Self
è dimensionato?
Si scopre che no, Self
non è dimensionato per impostazione predefinita in Rust. Ogni tratto ha un numero implicito ?Sized
associato a Self
. Uno dei motivi per cui questo è necessario perché ci sono molti tratti che possono essere implementati per tipi non ancora registrati e funzionano ancora. Ad esempio, qualsiasi tratto che contiene solo metodi che accettano e restituiscono solo Self
per riferimento possono essere implementati per i tipi non registrati. Puoi leggere ulteriori informazioni sulla motivazione in RFC 546.
La dimensione non è un problema quando si definisce solo la firma del tratto e i suoi metodi. Perché non esiste un codice effettivo in queste definizioni, il compilatore non può assumere nulla. Tuttavia, quando inizi a scrivere codice generico che utilizza questa caratteristica, che include i metodi predefiniti perché prendono un parametro implicito Self
, devi prendere in considerazione la grandezza. Poiché Self
non è dimensionato per impostazione predefinita, i metodi di tratto predefiniti non possono restituire Self
in base al valore o prenderlo come parametro in base al valore. Di conseguenza, vi sia bisogno di specificare che Self
deve essere dimensionato in modo predefinito:
trait A: Sized { ... }
o è possibile specificare che un metodo può essere chiamato solo se Self
è di dimensioni:
trait WithConstructor {
fn new_with_param(param: usize) -> Self;
fn new() -> Self
where
Self: Sized,
{
Self::new_with_param(0)
}
}
Grazie per una risposta così completa. Non sapevo che "il predefinito è Sized ma Self non è" parte. Questa è la ragione principale per cui sono rimasto perplesso. – eulerdisk