相互排斥的特征

Ant*_*lić 3 rust associated-types

我需要为操作序列创建操作。这些操作共享以下行为。它们可以被评估,并且在构造时它们可以由单个 i32 参数化(例如 Sum)或根本不参数化(例如 Id)。

我创建了一个特征操作。评估部分是微不足道的。

trait Operation {
    fn evaluate(&self, operand: i32) -> i32;
}
Run Code Online (Sandbox Code Playgroud)

但我不知道如何描述第二个需求。第一个选项是简单地让操作的具体实现处理该行为。

pub struct Id {}

impl Id {
    pub fn new() -> Id {
        Id {}
    }
}

impl Operation for Id {
    fn evaluate(&self, operand: i32) -> i32 {
        operand
    }
}

pub struct Sum {
    augend: i32,
}

impl Sum {
    pub fn new(augend: i32) -> Sum {
        Sum { augend }
    }
}

impl Operation for Sum {
    fn evaluate(&self, addend: i32) -> i32 {
        augend + addend
    }
}
Run Code Online (Sandbox Code Playgroud)

第二个选项是一个新功能,需要一个可选的 i32。然后具体实现处理可能的冗余输入。我发现这比第一个选择更糟糕。

trait Operation {
    fn evaluate(&self, operand: i32) -> i32;
    fn new(parameter: std::Option<i32>)
}
Run Code Online (Sandbox Code Playgroud)

谷歌让我发现了相互排斥的特征:https ://github.com/rust-lang/rust/issues/51774 。这看起来很有希望,但并不能完全解决我的问题。

有没有办法实现这种行为?

trait Operation = Evaluate + (ParametrizedInit or UnparametrizedInit)
Run Code Online (Sandbox Code Playgroud)

Luk*_*odt 5

使用关联类型来定义初始化数据怎么样?

trait Operation {
    type InitData;
    fn init(data: Self::InitData) -> Self;

    fn evaluate(&self, operand: i32) -> i32;
}

impl Operation for Id {
    type InitData = ();
    fn init(_: Self::InitData) -> Self {
        Id {}
    }

    fn evaluate(&self, operand: i32) -> i32 {
        operand
    }
}

impl Operation for Sum {
    type InitData = i32;
    fn init(augend: Self::InitData) -> Self {
        Sum { augend }
    }

    fn evaluate(&self, addend: i32) -> i32 {
        augend + addend
    }
}
Run Code Online (Sandbox Code Playgroud)

对于Id您指定的情况(),说初始化不需要数据。调用起来还是有点乏味Operation::init(()),但我认为这个特性至少很好地捕捉了逻辑。


要真正获得互斥的特征(这显然是您想要的),您必须使用一些解决方法。Rust 语言本身不支持互斥特征。但是您可以使用关联类型和一些标记类型来获得类似的东西。这有点奇怪,但目前有效。

trait InitMarker {}

enum InitFromNothingMarker {}
enum InitFromI32Marker {}

impl InitMarker for InitFromNothingMarker {}
impl InitMarker for InitFromI32Marker {}

trait Operation {
    type InitData: InitMarker;

    fn init() -> Self
    where
        Self: Operation<InitData = InitFromNothingMarker>;
    fn init_from(v: i32) -> Self
    where
        Self: Operation<InitData = InitFromI32Marker>;
}

trait UnparametrizedInit: Operation<InitData = InitFromNothingMarker> {}
trait ParametrizedInit: Operation<InitData = InitFromI32Marker> {}

impl<T: Operation<InitData = InitFromNothingMarker>> UnparametrizedInit for T {}
impl<T: Operation<InitData = InitFromI32Marker>> ParametrizedInit for T {}
Run Code Online (Sandbox Code Playgroud)

(理想情况下,您希望Sealed在板条箱的私有子模块中定义一个特征。这样,没有人(除了您)可以实现该特征。然后为 制作Sealed一个超级特征InitMarker。)

这是相当多的代码,但至少您可以确保 的实现者Operation完全实现ParametrizedInit和之一UnparametrizedInit

将来,您可能能够将标记类型替换为枚举,并将关联类型替换为关联常量。但目前,“const generics”还不够完善,所以我们必须走丑陋的路线,使用标记类型。我实际上正在我的硕士论文中讨论这些解决方案(第4.2节,只需搜索“互斥”)