Rust特质有“简单”和“高级”版本

4

我有两个基本等效的特质,但其中一个提供比另一个更低级别的接口。通过使用更高级别的特质,可以轻松实现更低级别的特质。我想编写一个库,该库接受任意一种特质的实现。

我的具体情况是用于遍历树的特质:

// "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 trait,而RawState的实现则根据他们的State实现自动生成。然而,在实现State时,trait的某些部分仍是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;
}

但是,编译器会抱怨关于模糊的重复定义。例如,在State实现中的DummyState,它会抱怨Self::Cost是不明确的,因为它无法确定您是指<Self as State>::Cost还是<Self as RawState>::Cost


1
你确定需要两个特质,而不是一个带有 children_with_cull默认实现的特质吗? - trent
2
@trentcl:那些更愿意直接实现RawState的类型仍然必须实现children,即使children没有被直接使用。 - Francis Gagné
1个回答

6
考虑到RawStateState都不是对象安全的(因为它们在返回类型中使用了Self),我假设您不打算为这些特征创建特征对象(即没有&RawState)。
当处理特性对象时,超级特性绑定State: RawState大多数情况下很重要,因为特性对象只能指定一个特性(加上一些来自标准库的少数经过白名单筛选的没有方法的特性,例如CopySendSync)。特性对象所引用的虚函数表仅包含该特性中定义的方法的指针。但如果特性具有超级特性限制,则这些特性中的方法也包括在虚函数表中。因此,如果&State(如果合法)将使您可以访问children_with_cull
另一个超级特性限制很重要的情况是,子特性为某些方法提供了默认实现。默认实现可以利用超级特性限制访问另一个特性中的方法。
由于您不能使用特性对象,并且由于State中的方法没有默认实现,因此我认为您应该简单地不声明超级特性限制State: RawState,因为它没有任何作用(实际上会引起问题)。
采用这种方法,需要复制我们需要实现State的成员来自RawState,就像您建议的那样。因此,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 中使用了 Self。同样,RawState 也需要 RawState: Sized 的限制。)
最后,我们可以为所有实现了 State 的类型提供一个 RawState 的全局实现。通过这个 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

非常酷!正是我在寻找的。我需要了解对象安全性,但我认为我的代码工作得很好,因为我的库使用模板函数而不是特质对象。 - Jeremy Salwen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接