Come posso implementare "From <some trait's associated type>?"
Sto scrivendo una nuova cassa e voglio che sia utilizzabile con qualsiasi implementazione di un tratto (definito in un'altra cassa). Il tratto è simile a questo:
pub trait Trait {
type Error;
...
}
Ho il mio Errortipo, ma a volte voglio solo inoltrare l'errore sottostante senza modifiche. Il mio istinto è definire un tipo come questo:
pub enum Error<T: Trait> {
TraitError(T::Error),
...
}
Questo è simile al modello incoraggiato da questo errore e sembra essere idiomatico. Funziona bene, ma voglio anche usarlo ?nella mia implementazione, quindi devo implementare From:
impl<T: Trait> From<T::Error> for Error<T> {
fn from(e: T::Error) -> Self { Self::TraitError(e) }
}
Ciò fallisce, perché è in conflitto con impl<T> core::convert::From<T> for T. Penso di capire perché - qualche altro implementatore di Traitpotrebbe impostare in modo type Error = my_crate::Errortale che implsi applichino entrambe le s - ma in quale altro modo posso ottenere una semantica simile?
Ho esaminato alcune altre casse e sembrano gestirlo rendendo il loro Error(o equivalente) generico sul tipo di errore stesso, piuttosto che sull'implementazione del tratto. Funziona, ovviamente, ma:
- finché non abbiamo tipi associati intrinseci , è molto più prolisso. In
Trealtà, My implementa più tratti, ciascuno con i propriErrortipi, quindi ora dovrei restituire tipi comeResult<..., Error<<T as TraitA>::Error, <T as TraitB>::Error>>ecc; - è probabilmente meno espressivo (perché la relazione con
Traitè persa).
Rendere il mio Errorgenerico rispetto ai singoli tipi è l'opzione migliore (più idiomatica) oggi?
Risposte
Invece di implementare Fromper la tua Errorenumerazione, considera invece di utilizzare Result::map_errin combinazione con ?per specificare quale variante restituire. Funziona anche per enumerazioni generiche su un tipo che utilizza tipi associati, in quanto tali:
trait TraitA {
type Error;
fn do_stuff(&self) -> Result<(), Self::Error>;
}
trait TraitB {
type Error;
fn do_other_stuff(&self) -> Result<(), Self::Error>;
}
enum Error<T: TraitA + TraitB> {
DoStuff(<T as TraitA>::Error),
DoOtherStuff(<T as TraitB>::Error),
}
fn my_function<T: TraitA + TraitB>(t: T) -> Result<(), Error<T>> {
t.do_stuff().map_err(Error::DoStuff)?;
t.do_other_stuff().map_err(Error::DoOtherStuff)?;
Ok(())
}
Sul campo di gioco
Qui i bit importanti sono che Errornon ha Fromimplementazioni (a parte quelle generali ) e che la variante è specificata usando map_err. Funziona come Error::DoStuffpuò essere interpretato come fn(<T as TraitA>::Error) -> Errorquando viene passato a map_err. Lo stesso accade con Error::DoOtherStuff.
Questo approccio è scalabile con tutte le varianti disponibili Errore indipendentemente dal fatto che siano o meno dello stesso tipo. Potrebbe anche essere più chiaro a qualcuno che sta leggendo la funzione, poiché può capire che un certo errore proviene da un certo punto senza bisogno di controllare le Fromimplementazioni e dove il tipo da convertire appare nella funzione.