生锈特质与"简单"和"高级"版本

时间:2017-05-01 04:45:18

标签: rust traits api-design

我有两个基本相同的特征,但是一个提供了比另一个更低级别的界面。鉴于较高水平的特性,人们可以轻松实现较低水平的特质。我想写一个接受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)
         )
      )
}

这里,State trait提供了一个接口,您可以在其中定义.children()函数以列出子项,并使用.cull()函数来潜在地剔除状态。

RawState trait提供了一个接口,您可以在其中定义一个函数.children_with_cull(),它会遍历子节点并在单个函数调用中剔除它们。这允许RawState的实现永远不会生成它知道将被剔除的子项。

我想允许大多数用户只实现State特征,并根据状态实现自动生成RawState实现。但是,在实施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 {};
    }
}

会给出错误,因为类型&#34;成本&#34;在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;
}

然后编译器会抱怨模糊的重复定义。例如,在DummyState的{​​{1}}实施中,它会抱怨State含糊不清,因为它无法判断您是指Self::Cost还是{ {1}}。

1 个答案:

答案 0 :(得分:5)

考虑到RawStateState都不是object-safe(因为他们在回复类型中使用Self),我会假设你没有&RawState。 t打算为这些特征创建特征对象(即没有State: RawState)。

在处理特征对象时,超级边界Copy最重要,因为特征对象只能指定一个特征(加上标准库中没有方法的少数白名单特征,如SendSync&State)。特征对象引用的vtable仅包含指向该特征中定义的方法的指针。但是如果特征具有超级边界,那么这些特征的方法也包含在vtable中。因此,children_with_cull(如果合法)可以让您访问State

supertrait绑定很重要的另一种情况是子标记为某些方法提供默认实现。默认实现可以使用绑定的supertrait来访问另一个特征的方法。

由于您无法使用特征对象,并且由于您没有State: RawState中的方法的默认实现,我认为您不应该声明超级边界{{1}因为它不会增加任何东西(实际上会导致问题)。

使用这种方法,有必要按照您的建议从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;
}

(请注意,绑定State: Sized是必需的,因为我们在Self中使用ChildrenIteratorRawState也需要绑定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!() } }

<Self as State>

请注意消除名称冲突名称的语法:RawState。它已用于我们复制的两个成员,因此State推迟到set PATH=C:<OpenSSL Install Dir>\bin;%PATH% set PATH=C:<qtkeychain Clone Dir>;%PATH%