Ricerca…
introduzione
I tratti sono un modo per descrivere un "contratto" che una struct
deve implementare. I tratti in genere definiscono le firme dei metodi, ma possono anche fornire implementazioni basate su altri metodi del tratto, a condizione che i limiti dei tratti lo consentano.
Per chi ha familiarità con la programmazione orientata agli oggetti, i tratti possono essere pensati come interfacce con alcune sottili differenze.
Sintassi
- tratto Trait {metodo fn (...) -> ReturnType; ...}
- tratto Trait: Bound {fn method (...) -> ReturnType; ...}
- impl Trait for Type {fn method (...) -> ReturnType {...} ...}
- impl <T> Trait for T where T: Bounds {fn method (...) -> ReturnType {...} ...}
Osservazioni
- I tratti sono comunemente paragonati alle interfacce, ma è importante fare una distinzione tra i due. Nei linguaggi OO come Java, le interfacce sono parte integrante delle classi che li estendono. In Rust, il compilatore non sa nulla dei tratti di una struttura a meno che questi tratti non vengano utilizzati.
Nozioni di base
Creare un tratto
trait Speak {
fn speak(&self) -> String;
}
Implementazione di un tratto
struct Person;
struct Dog;
impl Speak for Person {
fn speak(&self) -> String {
String::from("Hello.")
}
}
impl Speak for Dog {
fn speak(&self) -> String {
String::from("Woof.")
}
}
fn main() {
let person = Person {};
let dog = Dog {};
println!("The person says {}", person.speak());
println!("The dog says {}", dog.speak());
}
Invio statico e dinamico
È possibile creare una funzione che accetta oggetti che implementano un tratto specifico.
Spedizione statica
fn generic_speak<T: Speak>(speaker: &T) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person);
generic_speak(&dog);
}
In questo caso viene utilizzato il dispatch statico, il che significa che il compilatore di Rust genererà versioni specializzate della funzione generic_speak
per i tipi Dog
e Person
. Questa generazione di versioni specializzate di una funzione polimorfica (o di qualsiasi entità polimorfica) durante la compilazione si chiama monomorfizzazione .
Invio dinamico
fn generic_speak(speaker: &Speak) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person as &Speak);
generic_speak(&dog); // gets automatically coerced to &Speak
}
Qui, esiste solo una singola versione di generic_speak
nel binario compilato e la chiamata speak()
viene effettuata utilizzando una ricerca vtable in fase di runtime. Pertanto, l'utilizzo di risultati di invio dinamico consente una compilazione più rapida e dimensioni più ridotte del binario compilato, pur essendo leggermente più lento in fase di runtime.
Oggetti di tipo &Speak
o Box<Speak>
sono chiamati oggetti tratto .
Tipi associati
- Utilizzare il tipo associato quando esiste una relazione uno-a-uno tra il tipo che implementa la caratteristica e il tipo associato.
- Talvolta è anche noto come tipo di output , poiché si tratta di un elemento assegnato a un tipo quando applichiamo un tratto.
Creazione
trait GetItems {
type First;
// ^~~~ defines an associated type.
type Last: ?Sized;
// ^~~~~~~~ associated types may be constrained by traits as well
fn first_item(&self) -> &Self::First;
// ^~~~~~~~~~~ use `Self::` to refer to the associated type
fn last_item(&self) -> &Self::Last;
// ^~~~~~~~~~ associated types can be used as function output...
fn set_first_item(&mut self, item: Self::First);
// ^~~~~~~~~~~ ... input, and anywhere.
}
L'implementazione
impl<T, U: ?Sized> GetItems for (T, U) {
type First = T;
type Last = U;
// ^~~ assign the associated types
fn first_item(&self) -> &Self::First { &self.0 }
fn last_item(&self) -> &Self::Last { &self.1 }
fn set_first_item(&mut self, item: Self::First) { self.0 = item; }
}
impl<T> GetItems for [T; 3] {
type First = T;
type Last = T;
fn first_item(&self) -> &T { &self[0] }
// ^ you could refer to the actual type instead of `Self::First`
fn last_item(&self) -> &T { &self[2] }
fn set_first_item(&mut self, item: T) { self[0] = item; }
}
Riferendosi ai tipi associati
Se siamo sicuri che un tipo T
implementa GetItems
ad esempio in generici, potremmo semplicemente usare T::First
per ottenere il tipo associato.
fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
// ^~~~~~~~ refer to an associated type
(obj.first_item(), obj.last_item())
}
Altrimenti, devi dire esplicitamente al compilatore quale caratteristica sta implementando il tipo
let array: [u32; 3] = [1, 2, 3];
let first: &<[u32; 3] as GetItems>::First = array.first_item();
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [u32; 3] may implement multiple traits which many
// of them provide the `First` associated type.
// thus the explicit "cast" is necessary here.
assert_eq!(*first, 1);
Limitazione con tipi associati
fn clone_first_and_last<T: GetItems>(obj: &T) -> (T::First, T::Last)
where T::First: Clone, T::Last: Clone
// ^~~~~ use the `where` clause to constraint associated types by traits
{
(obj.first_item().clone(), obj.last_item().clone())
}
fn get_first_u32<T: GetItems<First=u32>>(obj: &T) -> u32 {
// ^~~~~~~~~~~ constraint associated types by equality
*obj.first_item()
}
Metodi predefiniti
trait Speak {
fn speak(&self) -> String {
String::from("Hi.")
}
}
Il metodo verrà chiamato di default eccetto se è sovrascritto nel blocco impl
.
struct Human;
struct Cat;
impl Speak for Human {}
impl Speak for Cat {
fn speak(&self) -> String {
String::from("Meow.")
}
}
fn main() {
let human = Human {};
let cat = Cat {};
println!("The human says {}", human.speak());
println!("The cat says {}", cat.speak());
}
Produzione :
L'umano dice Ciao.
Il gatto dice Meow.
Posizionare un limite su un tratto
Quando si definisce una nuova caratteristica, è possibile applicare i tipi che desiderano implementare questa caratteristica per verificare una serie di vincoli o limiti.
Prendendo un esempio dalla libreria standard, il tratto DerefMut
richiede che un tipo implementi innanzitutto il suo tratto di Deref
:
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Questo, a sua volta, consente a DerefMut
di utilizzare il tipo associato Target
definito da Deref
.
Mentre la sintassi potrebbe ricordare l'ereditarietà:
- porta in tutti gli elementi associati (costanti, tipi, funzioni, ...) del tratto associato
- abilita il polimorfismo da
&DerefMut
a&Deref
Questo è diverso in natura:
- è possibile utilizzare una durata (come
'static
) come limite - non è possibile sovrascrivere gli elementi tratti associati (nemmeno le funzioni)
Quindi è meglio pensarlo come un concetto separato.
Più tipi di oggetti associati
È anche possibile aggiungere più tipi di oggetto a una funzione di rilascio statico .
fn mammal_speak<T: Person + Dog>(mammal: &T) {
println!("{0}", mammal.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
mammal_speak(&person);
mammal_speak(&dog);
}