Rust在专门版本中调用函数的默认实现

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

现在我希望有一个实现者来扩展特征的行为,但仍然有时使用默认实现。当然,默认实现更复杂,我想遵循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 的公共接口中)?

2个回答

10

独立通用函数

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

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
}

两个默认的trait方法

另一种方法是定义两个trait方法,一个作为默认实现,另一个则在未被覆盖时使用默认实现:

playground

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值,则可以使用默认关联const特质项来定义您的特质:

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
}

游乐场


通过 AsRef 调用默认实现

如果唯一区别于 Normal 的是少数额外字段,而 Special 类型在其他方面可以像 Normal 一样运行,则可以为 Special 实现 AsRef<Normal> 并以这种方式调用默认实现:

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 访问器特性只需一堆独立的自由函数,而特性方法的默认实现只是调用这些函数。 - Frxstrem
谢谢@pretzelhammer。当然,我希望可能会有更好的解决方案,就像可以明确选择来自不同特征的竞争方法一样。但我相信我会用你建议的一个解决方案让我的程序运行起来。 - Rüdiger Ludwig

3
例如,syn::Visit 特质也有类似的需求:对于每个特质方法,都有相应的独立函数,所有默认实现只是调用相应的独立函数。如果特质实现需要做一些其他事情,并委托给默认行为,它只需执行其所需操作并自行调用该独立函数即可。
对于你的示例,可能看起来像这样:
// 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);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接