Jer*_*wen 3 api-design traits rust
我有两个基本相同的特征,但是一个提供了比另一个更低级别的接口.鉴于较高水平的特性,人们可以轻松实现较低水平的特质.我想写一个接受任一trait实现的库.
我的具体案例是遍历树的特征:
// "Lower level" version of the trait
pub trait RawState {
type Cost: std::cmp::Ord + std::ops::Add<Output = Self::Cost> + std::marker::Copy;
type CulledChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn cull(&self) -> Option<Self::Cost>;
fn children_with_cull(&self) -> Self::CulledChildrenIterator;
}
// "Higher level" version of the trait
pub trait State: RawState {
type ChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn children(&self) -> Self::ChildrenIterator;
}
// Example of how RawState could be implemented using State
fn state_children_with_cull<S: State> (s: S)
-> impl Iterator<Item = (S, S::Cost)>
{
s.children()
.filter_map(move |(state, transition_cost)|
state.cull().map(move |emission_cost|
(state, transition_cost + emission_cost)
)
)
}
Run Code Online (Sandbox Code Playgroud)
在这里,State trait提供了一个接口,您可以在其中定义.children()函数以列出子项,以及.cull()可能剔除状态的函数.
该RawState特征提供了一个接口,您可以在其中定义一个函数.children_with_cull(),该函数遍历子节点并在单个函数调用中剔除它们.这允许实现RawState永远不会生成它知道将被剔除的子项.
我想允许大多数用户只实现State特征,并RawState根据他们的State实现自动生成实现.然而,在实施时State,特征的某些部分仍然是RawState例如的一部分
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct DummyState {}
impl State for DummyState {
type Cost = u32;
type ChildrenIterator = DummyIt;
fn emission(&self) -> Option<Self::Cost> {
Some(0u32)
}
fn children(&self) -> DummyIt {
return DummyIt {};
}
}
Run Code Online (Sandbox Code Playgroud)
会出错,因为"Cost"类型是在RawState中定义的,而不是在State中定义的.在潜在的解决方法上,重新定义State内RawState的所有相关部分,即将State定义为
pub trait State: RawState {
type Cost: std::cmp::Ord + std::ops::Add<Output = Self::Cost> + std::marker::Copy;
type ChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn cull(&self) -> Option<Self::Cost>;
fn children(&self) -> Self::ChildrenIterator;
}
Run Code Online (Sandbox Code Playgroud)
但是编译器会抱怨模糊的重复定义.例如,在DummyState实现中State,它会抱怨Self::Cost含糊不清,因为它无法判断你是否指的是<Self as State>::Cost,或者<Self as RawState>::Cost.
考虑到这两个RawState和State没有对象的安全(因为他们使用Self的返回类型),我会假设你不打算创建对象的特质,这些特质(即无&RawState).
State: RawState在处理特征对象时,supertrait边界最重要,因为特征对象只能指定一个特征(加上标准库中没有方法的选择的少数白名特征,例如Copy,Send和Sync).特征对象引用的vtable仅包含指向该特征中定义的方法的指针.但是如果特征具有超级边界,那么这些特征的方法也包含在vtable中.因此,a &State(如果它是合法的)将允许您访问children_with_cull.
supertrait绑定很重要的另一种情况是子标记为某些方法提供默认实现.默认实现可以使用绑定的supertrait来访问另一个特征的方法.
由于你不能使用特征对象,并且由于你没有方法的默认实现State,我认为你应该简单地不要声明超级边界State: RawState,因为它不会增加任何东西(实际上会导致问题).
使用这种方法,有必要按照您的建议从RawState我们需要实现的成员中复制成员State.State因此将被定义为这样:
pub trait State: Sized {
type Cost: std::cmp::Ord + std::ops::Add<Output = Self::Cost> + std::marker::Copy;
type ChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn cull(&self) -> Option<Self::Cost>;
fn children(&self) -> Self::ChildrenIterator;
}
Run Code Online (Sandbox Code Playgroud)
(注意,绑定State: Sized是必需的,因为我们使用Selfin ChildrenIterator.RawState也需要绑定RawState: Sized.)
最后,我们可以提供一揽子impl的RawState用于实现所有类型State.有了这个impl,任何实现的类型State都会自动实现RawState.
impl<T> RawState for T
where
T: State
{
type Cost = <Self as State>::Cost;
type CulledChildrenIterator = std::iter::Empty<(Self, Self::Cost)>; // placeholder
fn cull(&self) -> Option<Self::Cost> { <Self as State>::cull(self) }
fn children_with_cull(&self) -> Self::CulledChildrenIterator {
unimplemented!()
}
}
Run Code Online (Sandbox Code Playgroud)
请注意消除名称冲突名称的语法:<Self as State>.它用于我们复制的两个成员,以便RawState遵循State.