Подтипирование классов в Rust

C++ позволяет создавать подтипы классов, что очень удобно, поскольку вы можете использовать функции, реализованные для базового класса, с производным классом. У Rust, кажется, нет ничего подобного. Эта функция, кажется, была доступна в какой-то момент, но с тех пор была удалена. Разве это невозможно в Rust? Если да, то есть ли планы по внедрению этой функции?

Что я хочу сделать, так это определить структуру, которая наследуется от другой структуры, которая в C++ будет выглядеть так:

struct Base {
    int x;
    int y;
    void foo(int x, int y) { this->x = x; this->y = y; }
}

struct Derived: public Base {
    ...
}

void main() {
    Derived d;
    d.foo();
}

На мой взгляд, в Rust вам нужно написать что-то вроде этого, чтобы сделать одну и ту же функциональность доступной для всех «производных» структур:

struct Base<T> {
     x: i32,
     y: i32,
     derived: T
}
impl<T> Base<T> {
    fn foo(&mut self, x: i32, y: i32) {
         self.x = x;
         self.y = y;
    }
}

Я думаю, что выполнение impl<T> for Base<T> приведет к созданию тонны копий одной и той же функции, поэтому композиция на самом деле не вариант.

Я, вероятно, должен указать, что реализация выше была выбрана по той простой причине, что она позволяет более безопасную версию повышения приведения, что мне все равно нужно сделать.


person eugene2k    schedule 18.06.2016    source источник
comment
В Rust даже нет классов. Он использует основанную на признаках систему для полиморфизма во время выполнения. Не могли бы вы добавить пример того, что именно вы хотите (возможно, с рабочим кодом, написанным на C++)? Тогда мы могли бы рассказать вам, как это можно написать на Rust :)   -  person Lukas Kalbertodt    schedule 18.06.2016
comment
Отредактировал исходный пост, чтобы было понятнее.   -  person eugene2k    schedule 19.06.2016
comment
Как программист на C++, я считаю необходимым указать, что правилом является Предпочитать композицию, а не наследование, и ваш пример обычно рассматривается как анти-шаблон. Наследование следует использовать только для переопределения поведения (также известного как virtual функции), наследование от неполиморфных классов, как правило, является ошибкой (смешивает две концепции: отношение is-a и повторное использование кода). К счастью, у Rust есть преимущество задним числом, в Rust нет наследования :)   -  person Matthieu M.    schedule 20.06.2016
comment
Я категорически не согласен с утверждением, что наследование следует использовать только для переопределения поведения виртуальных функций. Наследование (или создание подтипов) позволяет пользователю производного класса вызывать методы базового класса без необходимости реализации дополнительной функции в производном классе.   -  person eugene2k    schedule 20.06.2016


Ответы (2)


Используйте композицию вместо наследования.

struct Base {
    x: u8,
    y: u8,
}

impl Base {
    fn foo(&mut self, x: u8, y: u8) {
        self.x = x;
        self.y = y;
    }
}

struct Derived {
    base: Base,
}

impl Derived {
    fn foo(&mut self, x: u8, y: u8) {
        self.base.foo(x, y);
    }
}

fn main() {
    let mut d = Derived { base: Base { x: 1, y: 3 } };
    d.foo(5, 6);
}

Однако Rust мог бы сделать композицию на клавиатуре такой же простой, как и наследование. Существует RFC, направленный на улучшение, а также предыдущие обсуждения 1, 2.

Я думаю, что выполнение impl<T> for Base<T> приведет к созданию тонны копий одной и той же функции, поэтому композиция на самом деле не вариант.

Я не уверен, что этот синтаксис пытается показать, поскольку Base не имеет универсального типа. Это правда, что универсальные типы мономорфизированы для каждого конкретного набора параметров типа, но я не вижу/не знаю, чем это отличается от C++. Я думаю, что версия Rust будет немного легче, так как вы не можете преобразовать Derived в Base, поэтому компилятору не нужно поддерживать эти инварианты.

Все это предполагает, что есть какой-то код для повторного использования. Во многих случаях вам лучше программировать интерфейс, который представлен < em>traits в Rust. Вы можете комбинировать два подхода — извлекать небольшие повторно используемые фрагменты кода в виде компонентов, объединять их вместе в агрегаты и реализовывать трейты для агрегатных типов.

person Shepmaster    schedule 19.06.2016
comment
Это можно упростить, если у вас есть более пары производных классов. Если вы напишете общий struct Base<T> и добавите к нему derived: T, вам не нужно будет писать base: Base в каждой производной структуре, которую вы хотите реализовать. Затем вы можете добавить импликаторы для своих производных структур, используя impl Base<Derived>. Я делаю именно это. Проблема в том, что происходит, когда у вас есть сотня производных структур? Насколько я понимаю, для каждой структуры Derived компилятор будет генерировать новую функцию, которая делает то же самое. - person eugene2k; 20.06.2016
comment
Я еще раз уточнил пост. Надеюсь, теперь это имеет больше смысла. - person eugene2k; 20.06.2016

Думаю, ответ в том, что в Rust пока нет подтипов. Проблемы, которые решает подтип, не всегда могут быть решены с помощью композиции, дженериков или признаков. Проблема известна разработчикам, и были предложены решения, но ничего не найдено. решено еще.

person eugene2k    schedule 24.06.2016