在一个结构体中,是否可能扩展一个trait的默认方法实现?

29
在传统的面向对象语言(如Java)中,可以通过在覆盖版本中调用来自超类的原始方法,以“扩展”继承类中方法的功能,例如:
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!");
    }
}

你可以看到,在Java中,我能够使用super关键字调用超类的原始版本。我已经成功地为继承特性获得了等效行为,但在实现结构体特性时却无法这样做。

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"
}

因此,对于继承的特性,调用原始默认实现没有问题,但是在实现结构体时使用相同语法会产生不同的行为。这是 Rust 中的问题吗?有没有解决方法?还是我漏掉了什么?

4个回答

20

目前无法直接实现这一点。

然而,RFC 1210:impl特化包含了各种方面,将使得这种行为能够发挥作用,例如,类似下面的代码应该可以工作:

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

进行super调用明确被提及为扩展,因此可能不会立即出现,但未来可能会出现。

与此同时,通常使用的方法是为默认行为定义一个单独的函数并在默认方法中调用该函数,然后用户可以通过直接调用该函数来模拟super::...调用:

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

话虽如此,Rust更偏爱组合而不是继承:在Java中良好运作的设计在Rust中不能并且不应该被强行1对1地移植。

    Foo::method(self);  // this apparently calls this overridden
                        // version, because it overflows the stack

合格的路径语法Trait::method(value)<Type as Trait>::method(value)的简写,其中Typevalue的类型(或者可能是解引用一定次数后的类型)。也就是说,它在特定类型上调用方法。


好的,我想我明白了,但仍然困扰我的是为什么对于继承的特征它也能工作?相同的语法Foo::method(self)在结构体的特征实现中调用特定类型的方法时,当在继承的特征内部调用时,会调用原始实现。这是为什么呢? - faiface
3
它没有按照你的期望运作。你正在定义一个新的 Boo::method 方法,与 Foo::method 方法不同。它仍然在特定类型上调用特定方法;我猜测你的测试代码没有覆盖 Foo 上的 method 方法,只有在 Bar 上覆盖了。 - huon
是的,谢谢,你说得对,现在我理解得更好了。 - faiface
RFC规定已经合并。那么,我们可以说现在是可能的吗? - Eray Erdin

9

另一种实现此目的的方法是将覆盖方法放在结构体的impl块中。

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

游乐场


3
哇,我真的没有预料到这种行为。谢谢你展示它。 - likebike
3
这并不是同样的事情。如果你从一个使用该特质作为类型参数的实例中调用方法a(),它只会调用特质的默认方法。Playground链接:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d2aa407048d01522dbc48a748b34d5f8 - ACyclic

7

这是 Rust 的问题吗?

不,这是按预期工作的。

有什么解决方法吗?

您可以将该方法移动到一个自由函数中,然后直接调用它,一次来自默认方法,一次来自“重写”方法。

fn the_default() {
    println!("default implementation");
}

trait Foo {
    fn method(&self) {
        the_default()    
    }
}

struct Bar;

impl Foo for Bar {
    fn method(&self) {
        the_default();
        println!("Hey, I'm doing something entirely different!");
    }
}

fn main() {
    let b = Bar;
    b.method();
}

还是我理解有误?

Rust不是面向对象语言, Rust可能是一种面向对象语言,但并非所有OO语言都相同。Rust可能无法完全适应您期望的传统范例。

换句话说,特性在运行时不存在。只有将它们应用于结构体并与之使用时,才会生成可调用的代码。当您创建自己的方法实现时,这将替代默认实现;没有默认方法实现的存在位置。

通常情况下,您可以以不同的方式编写代码。也许真正共享的代码应该作为新结构体上的方法提取出来,或者您可以向方法提供闭包以定制行为。


谢谢你的回答。我之所以问这个问题,是因为在快速应用程序开发中,扩展现有类的功能范例经常被使用并且相当有效。有时它会导致相当丑陋的代码,但通常非常有效,所以我想知道在Rust中是否可以获得等效的结果。将功能提取到单独的函数中似乎对我来说并不是很好,因为你必须事先考虑。一旦API已经确定,你就无法提取任何东西了,那么怎么办呢?但是,如果没有更好的答案出现,我当然会标记你的答案为正确的。 - faiface
1
@faiface 因为你必须提前思考 — 我认为我会继续将这视为Rust的优点,而不是缺点 ^_^ - Shepmaster
4
@Shepmaster说“Rust不是一种面向对象的语言。”这完全是错误的。 Rust并非采用完全基于类的OOP,但它肯定是一种面向对象的语言。(甚至连维基百科的文章都这么说!) - Chris Morgan
3
@ChrisMorgan 说得好,我已经更新了。但是,我担心人们听到“OO”就会将其翻译为“Java/C#可以做什么”。 - Shepmaster

0
trait Foo {

    fn base_method(&self) {
        println!("default implementation");
    }

    fn method(&self) { self.base_method() }
}

struct Bar;

impl Foo for Bar {
    fn method(&self) {
        self.base_method();
        println!("more");
    }
}

基本上是对被接受答案的一个增强版,如果你希望它作为一个“类”方法。

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