Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to extend a default method implementation of a trait in a struct?

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?

like image 915
faiface Avatar asked Jul 16 '15 18:07

faiface


People also ask

How do you implement a trait in Rust?

Implementing a trait on a type is similar to implementing regular methods. The difference is that after impl , we put the trait name we want to implement, then use the for keyword, and then specify the name of the type we want to implement the trait for.

What is the point of traits in Rust?

A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.

What is Rust impl?

The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.


2 Answers

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.

like image 104
huon Avatar answered Oct 14 '22 13:10

huon


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

like image 45
Shanavas M Avatar answered Oct 14 '22 11:10

Shanavas M