Можно ли расширить реализацию метода по умолчанию в структуре?
В традиционных объектно-ориентированных языках (например, 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();
}
детская площадка