Можно ли расширить реализацию метода по умолчанию в структуре?

В традиционных объектно-ориентированных языках (например, Java) можно "расширить" функциональность метода в унаследованном классе, вызвав оригинальный метод из суперкласса в переопределенной версии, например:

class A {
    public void method() {
        System.out.println("I am doing some serious stuff!");
    }
}

class B extends A {
    @Override
    public void method() {
        super.method(); // here we call the original version
        System.out.println("And I'm doing something more!");
    }
}

Как вы можете видеть, в Java я могу вызвать исходную версию из суперкласса, используя ключевое слово super. Мне удалось получить эквивалентное поведение для унаследованных признаков, но не при реализации признаков для структур.

trait Foo {
    fn method(&self) {
        println!("default implementation");
    }
}

trait Boo: Foo {
    fn method(&self) {
        // this is overriding the default implementation
        Foo::method(self);  // here, we successfully call the original
                            // this is tested to work properly
        println!("I am doing something more.");
    }
}

struct Bar;

impl Foo for Bar {
    fn method(&self) {
        // this is overriding the default implementation as well
        Foo::method(self);  // this apparently calls this overridden
                            // version, because it overflows the stack
        println!("Hey, I'm doing something entirely different!");
        println!("Actually, I never get to this point, 'cause I crash.");
    }
}

fn main() {
    let b = Bar;
    b.method();     // results in "thread '<main>' has overflowed its stack"
}

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

Ответ 1

Это невозможно сразу.

Однако RFC 1210: impl специализация содержит различные аспекты, которые будут работать таким образом, например, что-то вроде этого должно работать:

trait Foo {
    fn method(&self) { println!("default implementation"); }
}
trait Bar: Foo { ... }

partial impl<T: Bar> Foo for T {
    default fn method(&self) { println!("Bar default"); }
}

Выполнение вызова super явно упоминается как расширение для него и поэтому не обязательно появится сразу, но может появиться в будущем.

В то же время обычно используемый подход заключается в определении отдельной функции для поведения по умолчанию и вызова этого метода по умолчанию, а затем пользователи могут эмулировать вызов super::..., просто вызывая эту функцию напрямую:

trait Foo {
    fn method(&self) { do_method(self) }
}

fn do_method<T: Foo>(_x: &T) {
    println!("default implementation");
}

impl Foo for Bar {
    fn method(&self) {
        do_method(self);
        println!("more");
    }
}

Тем не менее, Rust предпочитает композицию над наследованием: проекты, которые хорошо работают на Java, не могут и не должны быть принудительно от 1 до 1 в Rust.

    Foo::method(self);  // this apparently calls this overridden
                        // version, because it overflows the stack

Квалифицированный синтаксис пути, Trait::method(value) является сахаром для <Type as Trait>::method(value), где Type - тип value (или, возможно, тип после разыменования несколько раз). То есть, он вызывает метод по определенному типу, как вы узнали.

Ответ 2

Это проблема в Rust?

Нет, это работает как предполагается

Есть ли способ?

Вы можете переместить метод в свободную функцию, а затем вызвать его напрямую, один раз из метода по умолчанию и один раз из метода "переопределения".

fn the_default() {
    println!("default implementation");
}

trait Foo {
    fn method(&self) {
        the_default()    
    }
}

struct Bar;

impl Foo for Bar {
    fn method(&self) {
        the_default();
        println!("Hey, I'm doing something entirely different!");
    }
}

fn main() {
    let b = Bar;
    b.method();
}

Или я просто что-то пропустил?

Rust не является объектно-ориентированным языком, Rust может быть объектно-ориентированным языком, но не все языки OO создаются одинаково. Ржавчина может не вполне соответствовать традиционным парадигмам, которые вы ожидаете.

А именно, черты не существуют во время выполнения. Только когда они применяются и используются со структурой, генерируется код, который может быть вызван. Когда вы создаете собственную реализацию метода, который заменяет реализацию по умолчанию; там нигде не существует реализации метода по умолчанию.

Часто ваш код может быть написан по-другому. Возможно, действительно общий код должен быть извлечен как метод в новой структуре или, возможно, вы предоставляете закрытие метода для настройки поведения.

Ответ 3

Другой способ сделать это - поместить переопределяющий метод в блок impl

trait A {
    fn a(&self) {
        println!("trait default method");
    }
}

struct B;

impl B {
    fn a(&self) {
        println!("overridden method");
        // call default method here
        A::a(self);
    }
}

impl A for B {}

fn main() {
    let a = B;
    a.a();
} 

детская площадка