【问题标题】:Rust trait with "simple" and "advanced" versions具有“简单”和“高级”版本的 Rust 特征
【发布时间】:2017-09-28 14:09:47
【问题描述】:

我有两个基本相同的特征,但一个提供的接口比另一个低。给定较高级别的特征,可以轻松实现较低级别的特征。我想编写一个接受任一特征实现的库。

我的具体情况是遍历树的特征:

// "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 {};
    }
}

会报错,因为类型“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;
}

但是编译器会抱怨模棱两可的重复定义。例如,在StateDummyState 实现中,它会抱怨Self::Cost 不明确,因为它无法判断您指的是&lt;Self as State&gt;::Cost,还是&lt;Self as RawState&gt;::Cost

【问题讨论】:

  • 你确定你需要两个特征,而不仅仅是一个具有default implementationchildren_with_cull 的特征吗?
  • @trentcl:那些宁愿直接实现RawState的类型仍然必须实现children,即使children没有被直接使用。

标签: rust traits api-design


【解决方案1】:

考虑到RawStateState 都不是object-safe(因为它们在返回类型中使用Self),我假设您不打算为这些特征创建特征对象(即没有&amp;RawState)。

超级特征绑定State: RawState在处理特征对象时最重要,因为特征对象只能指定一个特征(加上从标准库中选择的一些没有方法的白名单特征,如CopySendSync)。 trait 对象引用的 vtable 仅包含指向该 trait 中定义的方法的指针。但是,如果特征具有超特征边界,则这些特征的方法也包含在 vtable 中。因此,&amp;State(如果合法)将使您能够访问children_with_cull

超特征绑定很重要的另一种情况是,当子特征为某些方法提供默认实现时。默认实现可以利用绑定的超特征来访问另一个特征的方法。

由于您不能使用 trait 对象,并且由于您没有 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;
}

(注意绑定State: Sized是必需的,因为我们在ChildrenIterator中使用SelfRawState也需要绑定RawState: Sized。)

最后,我们可以为实现State 的所有类型提供RawState 的一揽子impl。有了这个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!()
    }
}

注意消除冲突名称歧义的语法:&lt;Self as State&gt;。它用于我们复制的两个成员,以便 RawState 推迟到 State

【讨论】:

  • 非常酷!正是我正在寻找的。我需要阅读对象安全性,但我认为我的代码工作正常,因为我的库使用模板函数而不是特征对象。
猜你喜欢
  • 1970-01-01
  • 2017-05-12
  • 2019-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多