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?