在Rust中如何实现多态的mixin类似行为?

5

抱歉我有一种“面向对象”的思维方式(在某种程度上),但我很难想象如何在Rust中创建以下类似物:

在Python中,我可以定义一个基类,并将其方法定义为抽象/未实现状态(raise NotImplementedException)或具有默认实现。然后我可以创建一个mixin类以覆盖基类的行为。最后,我可以将基类和mixin类组合成一个子类,其中mixin方法是绑定到子类的方法。我可以完成所有这些操作,以尝试满足另一个代码片段的接口需求,该代码片段与这些对象进行交互。我想在Rust中完成这个过程,而又不使用需要C++ vtables等价物的动态运行时多态。在Python中,我会像以下示例代码一样处理愚蠢的接口do_it

```python from abc import ABC, abstractmethod
class Base(ABC): @abstractmethod def do_it(self): pass
class Mixin: def do_it(self): print("Mixin here")
class Child(Base, Mixin): pass
c = Child() c.do_it() ```
def do_it(inst):
    inst.must_implement_me()

class Base:
    def must_implement_me(self):
        print('base default implementation')
        
class Mixin:
    def must_implement_me(self):
        print('mixin override new default')

class ChildClass(Mixin, Base):
    pass



do_it(Base())       # prints "base default implementation"
do_it(ChildClass()) # prints "mixin override new default"

当我在 Rust 中尝试达到相同的目标时:

trait DoIt {
  fn must_implement_me(&self);
}

fn do_it<T: DoIt>(inst: T) {
  inst.must_implement_me();
}


trait Base {}

impl<T> DoIt for T where T: Base {
  fn must_implement_me(&self) {
    println!("base default implementation");
  }
}

trait Mixin {}
impl<T> DoIt for T where T: Mixin {
  fn must_implement_me(&self) {
    println!("mixin override new default");
  }
}

struct BaseClass();
impl Base for BaseClass{}


struct ChildClass();
impl Base for ChildClass {}
impl Mixin for BaseClass {}


fn main() {
  do_it(BaseClass());
  do_it(ChildClass());
}

cargo run 提示错误:

error[E0119]: conflicting implementations of trait `DoIt`:
  --> src/main.rs:20:1
   |
13 | impl<T> DoIt for T where T: Base {
   | -------------------------------- first implementation here
...
20 | impl<T> DoIt for T where T: Mixin + Base {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

有没有一种方法可以告诉Rust,让Mixin实现胜出?我知道这可能存在歧义,但是我能不能明确地告诉Rust哪个方法实现胜出?如果不行,还有什么其他方法可以实现这个目标吗?


我不确定我完全理解你最终想要实现什么。虽然听起来你只是想为你的特质提供默认方法定义。 - IInspectable
1
@IInspectable,这不仅仅是一个默认方法。它是多层默认方法。我可能应该使用我们都熟知和喜爱的动物示例,其中BaseAnimalMixinDog(或更可能是HasLegs,代表"mixin"的想法),而ChildClassDalmationDalmation获取了所有覆盖AnimalDog方法,但不必实际实现Dog方法。无论如何,我知道我的思维方式非常面向对象,但在某些情况下,这种模式是有成效的,所以我需要你们的帮助来翻译这个思维模型。 - Ross Rogers
2个回答

4
上面的代码在 Rust 中确实不可行。与 Python 相比,Rust 没有特质绑定的顺序,因此您无法确定应该调用哪个实现(例如来自 Base 还是来自 Mixin)。您的特定问题可以通过特质默认实现来解决 [文档链接] (演示链接):
trait DoIt {
    fn must_implement_me(&self) {
        println!("base default implementation");
    }
}

// ...

struct BaseClass();
impl Mixin for BaseClass {}

struct ChildClass();
impl DoIt for ChildClass {}

很快(希望如此),可以使用夜间特性(playground)实现更加复杂的静态“重载”。
#![feature(specialization)]

impl<T> DoIt for T {
    default fn must_implement_me(&self) {
        println!("base default implementation");
    }
}

// ...


struct BaseClass();
impl Mixin for BaseClass {}

struct ChildClass();

尽管您可以接受使用夜间编译器,但我建议避免使用最后一个代码示例,因为该功能尚不完整。


好的,你很有帮助,你的第一句话就说明了问题。当然,我的例子只是一个玩具。实际上,我正在使用一个库的特性,因此(我认为)我无法覆盖基本特性以提供默认实现。我希望看到一个通用的基本特性实现,然后将这个其他专业特性混入其中,以覆盖我的基本特性的通用实现。无论如何,我相信我对这个问题的思考是错误的。 - Ross Rogers
对于实现了MySpecializeTrait特质的类型T,覆盖泛型impl<t> DoIt for T会非常有用。我正在使用的基础库已经有了通用的impl<t> DoIt for T,因此我无法对其进行更严格的专门化。 - Ross Rogers
专业化特性是专为此目的设计的。抱歉,我不知道任何其他替代方案。 - Kitsu
1
我真的很想知道你回答中的重点。专业化正是我正在寻找的。 - Ross Rogers
1
@Kitsu 我可以建议你添加专业领域,并且可能附上你的回答链接吗? - undefined

0
我觉得你在问如何重用部分特质实现。在Python中,你可以使用类层次结构或混入(mixin),不同之处在于你可以将行为与混入组合在一起。
想象一下,我们有一个类,在0-100的区间内生成一系列数字(请忍受我复杂的示例和伪代码):
class NumberGenerator:
    def generate_numbers(): ...

# We can reuse code with inheritance
class IntervalGenerator(NumberGenerator):
    def generate_numbers():
        return range(0, 100, self.interval)

class MultipleOf3Generator(IntervalGenerator): ...
class MultipleOf5Generator(IntervalGenerator): ...

# We can also combine mixins for greater flexibility
class IntervalMixin:
    def generate_numbers():
        return range(0, 100, self.interval)

class ContainsDigitMixin:
    def generate_numbers():
        return (n for n in super().generate_numbers() if self.digit in str(n))

class MultipleOf3Containing7Generator(
    ContainsDigitMixin,
    IntervalMixin,
    NumberGenerator,
): ...

你问题中的例子可以通过继承来解决,但在Rust中无法实现真正的继承。最接近的方法可能是使用另一个trait,并使用具有默认实现的泛型:
trait NumberGenerator {
    fn generate_numbers(&self) -> Vec<usize>;
}

// We need another trait to emulate subclassing
trait IntervalGenerator {
    fn get_interval(&self) -> usize;
}

// This is where we make each implementation of our trait
// an implementation of the original trait
impl<T> NumberGenerator for T
    where T: IntervalGenerator
{
    fn generate_numbers(&self) -> Vec<usize> {
        (0..100).step_by(self.get_interval()).into()
    }
}

// We only need to implement the second trait
struct MultipleOf3Generator {};
impl IntervalGenerator for MultipleOf3Generator {
    fn get_interval(&self) -> usize { 3 }
}

在Rust中,这种方法无法扩展到混入(mixins),因为你不需要实现每一种组合。
我认为对于Rust来说,有简单的解决方案,取决于你想要实现什么:
- 将逻辑放在一个函数中,并在每个实现中调用该函数。 - 你可以将方法改为在trait对象上工作的函数吗?你不想使用动态分发,但是为了如此微小的性能优势而使代码变得更复杂是否值得呢? - 你还可以扭转思路,使用超trait(supertraits)- 如果你有一组固定的默认参数集合:
trait Student {
    fn university(&self) -> String { "Columbia University".to_string() }
}

trait Programmer {
    fn fav_language(&self) -> String { "Rust".to_string() }
}

trait CompSciStudent: Programmer + Student {
    fn hello(&self) -> String {
       format!(
           "I study at {} and my favorite language is {}",
           self.university(), self.fav_language()
       )
    }
}

struct Francesco {};
// Here, we can only override what we need, the rest is defined by default.
impl Student for Francesco {
    fn university(&self) -> String { "University of Rotterdam".to_string() }
}
impl Programmer for Francesco {}
impl CompSciStudent for Francesco {}

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