rustpolymorphismtraitsdefault-implementation

Rust calling default implementation of function in specialized version


I have a trait in Rust that offers a few default implementations for its functions.

trait MyTrait {
    fn do_something(&self);
    fn say_hello(&self) {
        println!("Hello I am default");
    }
}

Some implementors extend this trait and use the provided defaults

struct MyNormalImplementor {}

impl MyTrait for MyNormalImplementor {
    fn do_something(&self) {
        // self.doing_some_normal_stuff();
    }
}

Now I would like to have an implementor that extends the behavior of the trait, but still uses the default implementation sometimes. Of course the default implementationis more complex and I want to follow the DRY principle.

struct MySpecializedImplementor(bool)

impl MyTrait for MySpecializedImplementor {
    fn do_something(&self) {
        // self.doing_some_wild_stuff();
    }
    fn say_hello(&self) {
        if self.0 {
            println!("hey, I am special");
        } else {
           MyTrait::say_hello(self);
        }
    }
}

Here MyTrait::say_hello(self); immediately calls the specialized function in an endless loop. I did not find any way to qualify the function call so that the default implementation in MyTrait is called instead. Is there any way to achieve that, or do I have to create a proxy function (that will be in the public interface of my trait as well) for that case?


Solution

  • Free-standing generic function

    Defer the default implementation to a free-standing generic function:

    fn say_hello<T: Trait + ?Sized>(t: &T) {
        println!("Hello I am default")
    }
    
    trait Trait {
        fn say_hello(&self) {
            say_hello(self);
        }
    }
    
    struct Normal;
    
    impl Trait for Normal {}
    
    struct Special(bool);
    
    impl Trait for Special {
        fn say_hello(&self) {
            if self.0 {
                println!("Hey I am special")
            } else {
                say_hello(self)
            }
        }
    }
    
    fn main() {
        let normal = Normal;
        normal.say_hello(); // default
    
        let special = Special(false);
        special.say_hello(); // default
    
        let special = Special(true);
        special.say_hello(); // special
    }
    

    playground

    Two default trait methods

    Another approach could be defining two trait methods, one as a default implementation and the other which defers to the default implementation unless it is overwritten:

    trait Trait {
        fn say_hello_default(&self) {
            println!("Hello I am default");
        }
        fn say_hello(&self) {
            self.say_hello_default();
        }
    }
    
    struct Normal;
    
    impl Trait for Normal {}
    
    struct Special(bool);
    
    impl Trait for Special {
        fn say_hello(&self) {
            if self.0 {
                println!("Hey I am special");
            } else {
                self.say_hello_default();
            }
        }
    }
    
    fn main() {
        let normal = Normal;
        normal.say_hello(); // default
    
        let special = Special(false);
        special.say_hello(); // default
        
        let special = Special(true);
        special.say_hello(); // special
    }
    

    playground


    Default associated consts

    Although this is a tad more clunky, if the difference between the default and specialized implementations be reduced down to const values then you can use default associated const trait items for your trait:

    trait Trait {
        const MSG: &'static str = "Hello I am default";
        fn say_hello(&self) {
            println!("{}", Self::MSG);
        }
    }
    
    struct Normal;
    
    impl Trait for Normal {}
    
    struct Special(bool);
    
    impl Trait for Special {
        const MSG: &'static str = "Hey I am special";
        fn say_hello(&self) {
            let msg = if self.0 {
                Self::MSG
            } else {
                <Normal as Trait>::MSG
            };
            println!("{}", msg);
        }
    }
    
    fn main() {
        let normal = Normal;
        normal.say_hello(); // default
    
        let special = Special(false);
        special.say_hello(); // default
    
        let special = Special(true);
        special.say_hello(); // special
    }
    

    playground


    Call Default implementation via AsRef

    If the only thing that differentiates Special from Normal is a few extra fields, and the Special type can otherwise function as a Normal then you may want to implement AsRef<Normal> for Special and call the default implementation that way:

    trait Trait {
        fn say_hello(&self) {
            println!("Hello I am default");
        }
    }
    
    struct Normal;
    
    impl Trait for Normal {}
    
    struct Special(bool);
    
    impl AsRef<Normal> for Special {
        fn as_ref(&self) -> &Normal {
            &Normal
        }
    }
    
    impl Trait for Special {
        fn say_hello(&self) {
            if self.0 {
                println!("Hey I am special");
            } else {
                <Normal as Trait>::say_hello(self.as_ref());
            }
        }
    }
    
    fn main() {
        let normal = Normal;
        normal.say_hello(); // default
    
        let special = Special(false);
        special.say_hello(); // default
    
        let special = Special(true);
        special.say_hello(); // special
    }
    

    playground


    Default macro implementation

    As usual, if all else fails, the most brute force way to make your code DRY is to use macros:

    macro_rules! default_hello {
        () => {
            println!("Hello I am default");
        }
    }
    
    trait Trait {
        fn say_hello(&self) {
            default_hello!();
        }
    }
    
    struct Normal;
    
    impl Trait for Normal {}
    
    struct Special(bool);
    
    impl Trait for Special {
        fn say_hello(&self) {
            if self.0 {
                println!("Hey I am special");
            } else {
                default_hello!();
            }
        }
    }
    
    fn main() {
        let normal = Normal;
        normal.say_hello(); // default
    
        let special = Special(false);
        special.say_hello(); // default
    
        let special = Special(true);
        special.say_hello(); // special
    }
    

    playground