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)
使用关联类型来定义初始化数据怎么样?
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节,只需搜索“互斥”)。