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?
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
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
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
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
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
The syn::Visit
trait, for example, has similar needs and does this: for each trait method, there's a corresponding free-standing function, and all the default implementation does is call the corresponding free-standing function. If a trait implementation needs to do something else and delegate to the default behavior, it just does whatever it needs to do and calls that free-standing function itself.
For your example, it could look something like this:
// default implementation
fn say_hello<T: ?Sized + MyTrait>(t: &T) {
println!("Hello I am default");
}
trait MyTrait {
fn do_something(&self);
fn say_hello(&self) {
// use default behavior
say_hello(self);
}
}
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 {
// use default behavior
say_hello(self);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With