Подтвердить что ты не робот

Почему Rust не поддерживает повышение уровня объекта?

С учетом этого кода:

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?

Кто-нибудь знает обоснование такого дизайна?

4b9b3361

Ответ 1

На самом деле, я думаю, у меня есть причина. Я нашел элегантный способ добавить поддержку upcasting к любому признаку, который этого желает, и таким образом программист может выбрать, добавлять ли эту дополнительную запись в таблицу или использовать ее, или предпочитаете не делать этого, что является аналогичным компромиссом, как в Виртуальные и виртуальные методы С++: элегантность и правильность модели и производительность.

Код может быть реализован следующим образом:

trait Base : AsBase {
    ...
}

trait AsBase {
    fn as_base(&self) -> &Base;
}

impl<T: Base> AsBase for T {
    fn as_base(&self) -> &Base { self }
}

Конечно, можно добавить дополнительные методы для приведения указателя &mut или Box (который добавляет требование, чтобы T должен быть типом 'static), но это общая идея. Это обеспечивает безопасный и простой (хотя и неявный) рост каждого производного типа без шаблона для каждого производного типа.

Ответ 2

Я столкнулся с той же стеной, когда начал с Руста. Теперь, когда я думаю о чертах, у меня другое представление в виду, чем когда я думаю о классах.

trait X : Y {} означает, что при реализации trait X для struct S вам также нужен для реализации признака Y для S.

Конечно, это означает, что a &X знает, что это также &Y, и поэтому предлагает соответствующие функции. Для этого потребуется некоторое время выполнения (больше разметки указателя), если вам нужно сначала перенести указатели на Y vtable.

И снова, текущий дизайн + дополнительные указатели на другие vtables, вероятно, не повредит, и позволит легко выполнить кастинг. Так может быть, нам нужны оба? Это будет обсуждаться на internal.rust-lang.org

Ответ 3

По состоянию на июнь 2017 года статус этого "субтрактного принуждения" (или "супер-характерное принуждение" ) выглядит следующим образом:

  • В принятом RFC # 0401 упоминается это как часть принуждения. Поэтому это преобразование должно быть сделано неявно.

    coerce_inner (T) = U где T - это признак U;

  • Однако это еще не реализовано. Существует соответствующая проблема # 18600.

Существует также дублирующаяся проблема # 5665. Комментарии там объясняют, что мешает этому реализовать.

  • В основном проблема заключается в том, как выводить vtables для супер-признаков. Текущая компоновка vtables следующая (в случае x86-64):
    +-----+-------------------------------+
    | 0- 7|pointer to "drop glue" function|
    +-----+-------------------------------+
    | 8-15|size of the data               |
    +-----+-------------------------------+
    |16-23|alignment of the data          |
    +-----+-------------------------------+
    |24-  |methods of Self and supertraits|
    +-----+-------------------------------+
    
    Он не содержит vtable для супер-признака в качестве подпоследовательности. У нас есть, по крайней мере, некоторые настройки с помощью vtables.
  • Конечно, есть способы смягчить эту проблему, но многие из них имеют разные преимущества/недостатки! У одного есть преимущество для размера vtable, когда есть наследование алмаза. Другой должен быть быстрее.

Там @typelist говорит, что они подготовили проект RFC которые выглядят хорошо организованными, но после этого они исчезли (ноябрь 2016 года).