Rust 在专用版本中调用函数的默认实现 独立的泛型函数两个默认的 trait 方法默认关联常量通过 AsRef 调用默认实现默认宏实现

问题描述

我在 Rust 中有一个 trait,它为其函数提供了一些认实现。

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

一些实现者扩展了这个特性并使用提供的认值

struct MynormalImplementor {}

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

现在我想要一个实现者来扩展 trait 的行为,但有时仍然使用认实现。当然认实现更复杂,我想遵循DRY原则。

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

这里的 MyTrait::say_hello(self); 立即在无限循环中调用专用函数。我没有找到任何方法来限定函数调用,以便调用 MyTrait 中的认实现。有什么方法可以实现这一点,还是我必须为这种情况创建一个代理函数(也将在我的 trait 的公共接口中)?

解决方法

独立的泛型函数

将默认实现推迟到独立的通用函数:

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

两个默认的 trait 方法

另一种方法可能是定义两个 trait 方法,一个作为默认实现,另一个遵循默认实现,除非它被覆盖:

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


默认关联常量

虽然这有点笨拙,但如果默认实现和专用实现之间的差异减少到 const 值,那么您可以为您的 trait 使用默认关联的 const 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


通过 AsRef 调用默认实现

如果 SpecialNormal 的唯一区别是一些额外的字段,并且 Special 类型可以作为 Normal 否则您可能需要实现AsRef<Normal> 用于 Special 并以这种方式调用默认实现:

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


默认宏实现

像往常一样,如果所有其他方法都失败了,使代码 DRY 的最强力方法是使用宏:

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

,

例如,syn::Visit trait 也有类似的需求,并且这样做:对于每个 trait 方法,都有一个对应的独立函数,所有默认实现所做的就是调用相应的独立函数。如果 trait 实现需要做其他事情并且委托给默认行为,它只会做它需要做的任何事情并调用该独立函数本身。

对于您的示例,它可能如下所示:

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