具有"简单"和"高级"版本的Rust特性

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.

Fra*_*gné 5

考虑到这两个RawStateState没有对象的安全(因为他们使用Self的返回类型),我会假设你不打算创建对象的特质,这些特质(即无&RawState).

State: RawState在处理特征对象时,supertrait边界最重要,因为特征对象只能指定一个特征(加上标准库中没有方法的选择的少数白名特征,例如Copy,SendSync).特征对象引用的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.)

最后,我们可以提供一揽子implRawState用于实现所有类型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.