Storia della domanda
In Rust, i tipi incapsulano frequentemente altri valori (ad esempio, Box, Rc), e gli sviluppatori desiderano un accesso trasparente ai valori nidificati, come nei puntatori in C++. È stato aggiunto il trait Deref (e DerefMut) e una logica speciale di auto-deref per un accesso comodo attraverso metodi e operatori.
Problema
La confusione su quando e come i puntatori e gli smart pointer vengono automaticamente espansi porta a errori di compilazione, comportamenti inattesi dei metodi e, a volte, anche a codice non ottimale a causa di copie o borrow superflui.
Soluzione
Il trait Deref consente di descrivere la propria regola di dereferenziazione (ad esempio, Box<T> si comporta come T grazie all'implementazione di Deref). Il sistema di auto-deref cerca di "espandere" i riferimenti a tipi Deref durante le chiamate a metodi e operatori. Questo consente di scrivere:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
Caratteristiche chiave:
*, così come nella corrispondenza di tipi di riferimentoL'auto-deref avviene sempre quando lo "attende" lo sviluppatore?
No: la deref coercion avviene solo quando si chiamano metodi o quando è richiesto un riferimento a un altro tipo specificato nella firma tramite Deref Target. Non funziona in tutte le espressioni, ad esempio nella corrispondenza dei modelli.
La deref coercion può rompere le regole di borrowing e ownership?
No. La deref coercion rispetta pienamente le regole di borrowing: non è possibile ottenere due riferimenti mutabili tramite Deref, violando la sicurezza dell'ownership in Rust.
È possibile l'auto-deref da &T a &U, se T: Deref<Target=U> e entrambi i tipi non sono esplicitamente correlati?
Sì, quando si chiama una funzione che si aspetta &U, passare &T, dove T implementa Deref<Target=U>, porterà a una trasformazione automatica.
Esempio di codice:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
Uno sviluppatore ha implementato il proprio smart pointer, ma non ha aggiunto l'implementazione di Deref, aspettandosi che il suo tipo si comportasse come un valore normale.
Pro:
Il tipo si compila e si può fare riferimento esplicitamente al valore nidificato.
Contro:
L'auto-deref smette di funzionare durante le chiamate ai metodi, creando difficoltà nell'utilizzo delle funzioni della stdlib e delle librerie esterne.
La propria struttura di wrapping implementa Deref e DerefMut in modo simile a Box, integrazione senza soluzione di continuità con tutte le funzioni standard.
Pro:
Comodità, funzionamento trasparente della maggior parte delle funzioni del linguaggio, pulizia e integrazione morbida con il resto del codice.
Contro:
Rischio di complicare l'interfaccia di Deref se il tipo target non è ovvio.