generics - tauhata - rust trait bounds




Como posso evitar um efeito cascata de alterar uma estrutura concreta para genérica? (2)

Embora os tipos genéricos pareçam "infectar" o resto do seu código, é exatamente por isso que eles são benéficos! O conhecimento do compilador sobre quão grande e especificamente o tipo é usado permite que ele tome melhores decisões de otimização.

Dito isto, pode ser chato! Se você tiver um pequeno número de tipos que implementam sua característica, você também pode construir um enum desses tipos e delegar às implementações filho:

struct FromUser;
impl ListBuilder for FromUser { /**/ }

struct FromFile;
impl ListBuilder for FromFile { /**/ }

enum MyBuilders {
    User(FromUser),
    File(FromFile),
}

impl ListBuilder for MyBuilders {
    fn build(&self, list: &mut Vec<String>) {
        use MyBuilders::*;
        match *self {
            User(ref u) => u.build(list),
            File(ref f) => f.build(list),
        }
    }
}

Agora o tipo concreto seria Conf<MyBuilders> , que você pode usar um alias de tipo para ocultar.

Eu usei isso com bons resultados quando quis injetar implementações de teste no código durante o teste, mas tinha um conjunto fixo de implementações que eram usadas no código de produção.

Eu tenho uma estrutura de configuração que se parece com isso:

struct Conf {
    list: Vec<String>,
}

A implementação foi preenchendo internamente o membro da list , mas agora decidi que gostaria de delegar essa tarefa a outro objeto. Então eu tenho:

trait ListBuilder {
    fn build(&self, list: &mut Vec<String>);
}

struct Conf<T: Sized + ListBuilder> {
    list: Vec<String>,
    builder: T,
}

impl<T> Conf<T>
where
    T: Sized + ListBuilder,
{
    fn init(&mut self) {
        self.builder.build(&mut self.list);
    }
}

impl<T> Conf<T>
where
    T: Sized + ListBuilder,
{
    pub fn new(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: lb,
        };
        c.init();
        c
    }
}

Isso parece funcionar bem, mas agora em todos os lugares que eu uso Conf , eu tenho que mudar isso:

fn do_something(c: &Conf) {
    // ...
}

torna-se

fn do_something<T>(c: &Conf<T>)
where
    T: ListBuilder,
{
    // ...
}

Como tenho muitas dessas funções, essa conversão é dolorosa, especialmente porque a maioria dos usos da classe Conf não se preocupa com o ListBuilder - é um detalhe de implementação. Eu estou preocupado que, se eu adicionar outro tipo genérico ao Conf , agora eu tenho que voltar e adicionar outro parâmetro genérico em todos os lugares. Existe alguma maneira de evitar isso?

Eu sei que eu poderia usar um encerramento em vez do construtor de lista, mas tenho a restrição adicional de que minha estrutura Conf precisa ser Clone , e a implementação real do construtor é mais complexa e tem várias funções e algum estado no construtor, o que torna uma abordagem de fechamento é difícil.


Você pode usar o objeto de caractere Box<dyn ListBuilder> para ocultar o tipo do construtor. Algumas das consequências são o despacho dinâmico (as chamadas para o método de build passarão por uma tabela de função virtual), a alocação de memória adicional (objeto de característica em caixa) e algumas restrições sobre o atributo ListBuilder .

trait ListBuilder {
    fn build(&self, list: &mut Vec<String>);
}

struct Conf {
    list: Vec<String>,
    builder: Box<dyn ListBuilder>,
}

impl Conf {
    fn init(&mut self) {
        self.builder.build(&mut self.list);
    }
}

impl Conf {
    pub fn new<T: ListBuilder + 'static>(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: Box::new(lb),
        };
        c.init();
        c
    }
}




traits