С учетом этого кода:
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) {}
}
К сожалению, я не могу использовать &Derived
для &Base
. Мне было интересно, почему это так, потому что Derived
vtable должен ссылаться на методы Base
так или иначе.
Ну, проверка IRL LLVM показывает следующее:
@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
}
Все Rust vtables содержат указатель на деструктор, размер и выравнивание в первых полях, а субтитры vtables не дублируют их при ссылках на методы supertrait и не используют косвенную ссылку на supertrait vtables. У них просто есть копии указателей метода дословно и ничего.
Учитывая этот дизайн, легко понять, почему это не работает. Новая версия vtable должна быть построена во время выполнения, которая, вероятно, будет находиться в стеке, и это не совсем элегантное (или оптимальное) решение.
Есть некоторые способы обхода, конечно, такие как добавление явных методов upcast к интерфейсу, но для этого требуется довольно много шаблонов (или макросреды).
Теперь возникает вопрос: почему это не реализовано каким-то образом, что позволило бы повысить качество объекта? Например, добавив указатель на supertrait vtable в подпрограмме vtable. На данный момент динамическая диспетчеризация Rust не удовлетворяет LSP, что является основным принципом объектно-ориентированного проектирования.
Конечно, вы можете использовать статическую диспетчеризацию, которая действительно очень элегантна для использования в Rust, но она легко приводит к раздуванию кода, что иногда более важно, чем вычислительная производительность - например, на встроенных системах, и разработчики Rust заявляют, что поддерживают такое использование случаев языка. Кроме того, во многих случаях вы можете успешно использовать модель, которая не является чистой OO, что, по-видимому, поощряется функциональным дизайном Rust. Тем не менее, Rust поддерживает многие полезные шаблоны OO... так почему же LSP?
Кто-нибудь знает обоснование такого дизайна?