Por que o Rust não suporta upcasting de objetos de característica?

Dado este código:

trait Base {
    fn a(&self);
    fn b(&self);
    fn c(&self);
    fn d(&self);
}

trait Derived : Base {
    fn e(&self);
    fn f(&self);
    fn g(&self);
}

struct S;

impl Derived for S {
    fn e(&self) {}
    fn f(&self) {}
    fn g(&self) {}
}

impl Base for S {
    fn a(&self) {}
    fn b(&self) {}
    fn c(&self) {}
    fn d(&self) {}
}

Infelizmente, não posso transmitir&Derived para&Base:

fn example(v: &Derived) {
    v as &Base;
}
error[E0605]: non-primitive cast: `&Derived` as `&Base`
  --> src/main.rs:30:5
   |
30 |     v as &Base;
   |     ^^^^^^^^^^
   |
   = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

Por que é que? oDerived A tabela deve fazer referência aoBase métodos de uma maneira ou de outra.

A inspeção do IR LLVM revela o seguinte:

@vtable4 = internal unnamed_addr constant {
    void (i8*)*,
    i64,
    i64,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*
} {
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
    i64 0,
    i64 1,
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}

@vtable26 = internal unnamed_addr constant {
    void (i8*)*,
    i64,
    i64,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*
} {
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
    i64 0,
    i64 1,
    void (%struct.S*)* @_ZN9S.Derived1e20h9992ddd0854253d1WaaE,
    void (%struct.S*)* @_ZN9S.Derived1f20h849d0c78b0615f092aaE,
    void (%struct.S*)* @_ZN9S.Derived1g20hae95d0f1a38ed23b8aaE,
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}

Todas as tabelas de ferrugem contêm um ponteiro para o destruidor, tamanho e alinhamento nos primeiros campos, e as tabelas de subtítulo não as duplicam ao fazer referência a métodos de super-estreito, nem usam referência indireta às tabelas de super-estreito. Eles apenas têm cópias dos ponteiros do método literalmente e nada mais.

Dado esse design, é fácil entender por que isso não funciona. Uma nova vtable precisaria ser construída em tempo de execução, que provavelmente residiria na pilha, e essa não é exatamente uma solução elegante (ou ótima).

Existem algumas soluções alternativas, é claro, como adicionar métodos explícitos de upcast à interface, mas isso exige bastante clichê (ou frenesi de macro) para funcionar corretamente.

Agora, a pergunta é: por que não é implementado de alguma maneira que permitiria upcasting de objeto de característica? Como adicionar um ponteiro à tabela do supertrait na tabela do subtrait. Por enquanto, o despacho dinâmico de Rust não parece satisfazer aPrincípio de substituição de Liskov, que é um princípio muito básico para o design orientado a objetos.

É claro que você pode usar o envio estático, que é realmente muito elegante para usar no Rust, mas facilmente leva ao inchaço do código, que às vezes é mais importante que o desempenho computacional - como em sistemas embarcados, e os desenvolvedores do Rust afirmam apoiar esses casos de uso do língua. Além disso, em muitos casos, você pode usar com sucesso um modelo que não seja puramente orientado a objetos, o que parece ser incentivado pelo design funcional do Rust. Ainda assim, o Rust suporta muitos dos padrões OO úteis ... então, por que não o LSP?

Alguém sabe a justificativa para esse projeto?

questionAnswers(3)

yourAnswerToTheQuestion