La libreria standard fornisce un tratto speciale, Deref
.
Normalmente lo si usa per sovraccaricare *
, l'operatore di dereferenziazione:
use std::ops::Deref; struct EsempioDeref<T> { valore: T, } impl<T> Deref for EsempioDeref<T> { type Target = T; fn deref(&self) -> &T { &self.valore } } fn main() { let x = EsempioDeref { valore: 'a' }; assert_eq!('a', *x); }
Questo è utile per scrivere dei tipi puntatore personalizzati. Però, c'è
una caratteristica del linguaggio correlata a Deref
: le ‘forzature deref’.
Ecco la regola: Se si ha un tipo U
, che implementa Deref<Target=T>
,
i valori di tipo &U
automaticamente potranno essere forzati al tipo &T
.
Ecco un esempio:
fn foo(s: &str) { // prendi in prestito una stringa per un secondo } // String implementa Deref<Target=str> let posseduta = "Hello".to_string(); // perciò, questo funziona: foo(&posseduta);
Usare una e-commerciale davanti a un valore prende un riferimento ad esso.
Perciò posseduta
è una String
, &posseduta
è un &String
, e dato che
esiste impl Deref<Target=str> for String
, si potrà chiamare deref
su
&String
, ottenendo un &str
, che è accettato da foo()
.
Ecco. Questa regola è un dei soli posti in cui Rust fa una conversione
automatica, ma ciò aggiunge molta flessibilità. Per esempio,
il tipo Rc<T>
implementa Deref<Target=T>
, e quindi questo funziona:
use std::rc::Rc; fn foo(s: &str) { // prende in prestito una stringa per un secondo } // String implementa Deref<Target=str> let posseduta = "Hello".to_string(); let contata = Rc::new(posseduta); // perciò, questo funziona: foo(&counted);
Quel che abbiamo fatto è avvolgere la nostra String
in un Rc<T>
. Ma adesso
possiamo passare in giro la Rc<String>
ovunque sia accettata una String
.
La firma di foo
non è cambiata, ma funziona altrettanto con entrambi i tipi.
Questo esempio esegue due conversioni: da Rc<String>
a String
, e poi
da String
a &str
. Rust lo farà tante volte quanto possibile, fino a che
i tipi combaciano.
Un'altra implementazione molto tipica fornita dalla libreria standard è:
fn main() { fn foo(s: &[i32]) { // prende in prestito una slice per un secondo } // Vec<T> implementa Deref<Target=[T]> let posseduta = vec![1, 2, 3]; foo(&posseduta); }fn foo(s: &[i32]) { // prende in prestito una slice per un secondo } // Vec<T> implementa Deref<Target=[T]> let posseduta = vec![1, 2, 3]; foo(&posseduta);
I vettori possono essere convertiti da Deref
in una slice.
Deref
scatterà anche quando si chiama un metodo. Si consideri il seguente
esempio.
struct Foo; impl Foo { fn foo(&self) { println!("Foo"); } } let f = &&Foo; f.foo();
Anche se f
è un &&Foo
e foo
prende invece un &self
, questo funziona.
È perché le seguenti espressioni sono equivalenti:
f.foo(); (&f).foo(); (&&f).foo(); (&&&&&&&&f).foo();
Si possono chiamare su valore di tipo &&&&&&&&&&&&&&&&Foo
anche metodi
definiti per Foo
, perché il compilatore inserià tante operazioni *
quante ne servono per avere il tipo appropriato. E dato che sta inserendo
operatori *
, usa Deref
.