28

In traditional object-oriented languages (e.g. Java), it is possible to "extend" the functionality of a method in an inherited class by calling the original method from the super class in the overridden version, for example:

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!");
    }
}

As you can see, in Java, I am able to call the original version from the super class using the super keyword. I was able to get the equivalent behavior for inherited traits, but not when implementing traits for structs.

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"
}

So, in case of inherited traits, calling the original default implementation is no problem, however, using the same syntax when implementing structs exhibits different behavior. Is this a problem within Rust? Is there a way around it? Or am I just missing something?

Zoe
  • 27,060
  • 21
  • 118
  • 148
faiface
  • 457
  • 1
  • 5
  • 8

4 Answers4

19

This isn't possible directly now.

However, RFC 1210: impl specialization contains various aspects that will make this sort of behaviour work, for example, something like this should work:

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"); }
}

Doing a super call is explicitly mentioned as an extension to it and so won't necessarily appear immediately, but may appear in future.

In the mean time, the approach generally used is to define a separate function for the default behaviour and call that in the default method, and then users can emulate the super::... call by just calling that function directly:

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");
    }
}

That said, Rust favours composition over inheritance: designs that work well in Java can't and shouldn't be forced 1-to-1 into Rust.

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

The qualified path syntax, Trait::method(value) is sugar for <Type as Trait>::method(value) where Type is the type of value (or, possibly, the type after dereferencing some number of times). That is, it's calling the method on the specific type as you found out.

huon
  • 94,605
  • 21
  • 231
  • 225
  • Ok, I think I understand, what still bugs me is why does it work for inherited traits? The same syntax `Foo::method(self)` that calls the method on the specific type when called within the trait implementation for a struct calls the original implementation when called inside an inherited trait. Why is this? – faiface Jul 16 '15 at 19:12
  • 3
    It's not doing what you expect. You're defining a new method `Boo::method` that's different to the `Foo::method` one. It is still calling the specific method on the type; I guess your test code didn't override `method` on `Foo`, only on `Bar`. – huon Jul 16 '15 at 21:25
  • Yeah, thanks, you're correct, now I understand it a lot better. – faiface Jul 16 '15 at 22:05
  • RFC states it's merged. So, can we say it is possible right now? – Eray Erdin Jul 01 '21 at 08:39
9

Another way of accomplishing this would be to place the overriding method in impl block of struct

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();
} 

playground

Shanavas M
  • 1,581
  • 1
  • 17
  • 24
  • 3
    Wow, I really did not expect this behavior. Thanks for showing this. – likebike Nov 06 '19 at 18:29
  • 2
    This does not do the same thing. If you call the method a() from an instance that uses the trait as a type parameter, it will only call the trait default method. Playground link : https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d2aa407048d01522dbc48a748b34d5f8 – ACyclic May 25 '21 at 03:04
7

Is this a problem within Rust?

No, this is working as intended

Is there a way around it?

You can move the method to a free function and then call it directly, once from the default method and once from the "overridden" method.

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();
}

Or am I just missing something?

Rust is not an object-oriented language, Rust may be an object-oriented language, but not all OO languages are created the same. Rust may not quite fit into the traditional paradigms that you expect.

Namely, traits don't exist at run time. Only when they are applied to and used with a struct is code generated that is callable. When you create your own implementation of a method, that supersedes the default implementation; there's nowhere for the default method implementation to exist.

Often times, your code can be written in a different way. Perhaps the truly shared code should be extracted as a method on a new struct, or maybe you provide a closure to a method to customize the behavior.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for the answer. I'm asking this because this paradigm of extending functionality of existing classes is often and quite effectively used in rapid application development. Sometimes it results in a pretty ugly code, but it's often very effective, so I was wondering if I can get an equivalent in Rust. Extracting the functionality to a separate function doesn't seem all that good to me, because you have to think in advance when doing it. Once the API is already settled, you can't extract anything and what then? But of course, I'll mark your answer correct if nothing better appears. – faiface Jul 16 '15 at 18:57
  • 1
    @faiface *because you have to think in advance* — I think I'll continue to tout that as a benefit of Rust, not a downside ^_^ – Shepmaster Jul 16 '15 at 18:59
  • 4
    @Shepmaster: “Rust is not an object-oriented language.” This is completely false. Rust doesn’t go for full *class-based* OOP, but it’s *definitely* an object-oriented language. (Why, even the Wikipedia article says so! ) – Chris Morgan Jul 16 '15 at 22:25
  • 3
    @ChrisMorgan fair point, and I've updated. However, I fear that people hear "OO" and translate that as "what Java/C# can do". – Shepmaster Jul 16 '15 at 22:41
0
trait Foo {

    fn base_method(&self) {
        println!("default implementation");
    }

    fn method(&self) { self.base_method() }
}

struct Bar;

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

Basically a 1up of the accepted answer, if you want it as a "class" method.

Wolfey
  • 1
  • 3