Como posso implementar `De <algum tipo associado de característica>?`
Estou escrevendo uma nova caixa e quero que seja utilizável com qualquer implementação de uma característica (definida em outra caixa). O traço é mais ou menos assim:
pub trait Trait {
type Error;
...
}
Eu tenho meu próprio Errortipo, mas às vezes quero apenas encaminhar o erro subjacente sem modificações. Meu instinto é definir um tipo como este:
pub enum Error<T: Trait> {
TraitError(T::Error),
...
}
Isso é semelhante ao padrão encorajado por thiserror e parece ser idiomático. Funciona bem, mas também quero usar ?na minha implementação, então preciso implementar From:
impl<T: Trait> From<T::Error> for Error<T> {
fn from(e: T::Error) -> Self { Self::TraitError(e) }
}
Isso falha, porque entra em conflito com impl<T> core::convert::From<T> for T. Acho que entendo por que - algum outro implementador de Traitpoderia definir de type Error = my_crate::Errorforma que ambos implse aplicassem - mas de que outra forma posso conseguir uma semântica semelhante?
Eu olhei para alguns outros engradados e eles parecem lidar com isso tornando seu Error(ou equivalente) genérico sobre o tipo de erro em si, em vez da implementação de característica. Isso funciona, é claro, mas:
- até que tenhamos tipos inerentes associados , é muito mais detalhado.
TNa verdade, meu implementa vários traços, cada um com seus própriosErrortipos, então agora tenho que retornar tipos comoResult<..., Error<<T as TraitA>::Error, <T as TraitB>::Error>>etc; - é indiscutivelmente menos expressivo (porque o relacionamento com
Traitfoi perdido).
Tornar meu Errorgenérico em vez de tipos individuais é a melhor opção (mais idiomática) hoje?
Respostas
Em vez de implementar Frompara seu Errorenum, considere usar Result::map_errem combinação com ?para especificar qual variante retornar. Isso funciona até mesmo para enums genéricos sobre um tipo usando tipos associados, como:
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(())
}
No parquinho
Aqui, os bits importantes são aqueles que Errornão têm Fromimplementações (exceto as gerais) e que a variante é especificada usando map_err. Isso funciona como Error::DoStuffpode ser interpretado como um fn(<T as TraitA>::Error) -> Errorquando passado para map_err. O mesmo acontece com Error::DoOtherStuff.
Essa abordagem é escalonável com quantas variantes Errortem e sejam ou não do mesmo tipo. Também pode ficar mais claro para quem está lendo a função, pois eles podem descobrir que um determinado erro vem de um determinado local sem a necessidade de verificar as Fromimplementações e de onde o tipo que está sendo convertido aparece na função.