枚举通用结构

时间:2016-09-15 12:52:07

标签: generics struct rust

我想尝试使用struct来构建一个正确的Peano数字实现,但似乎我的泛型游戏还不够好,我可以使用一些帮助。我阅读了关于泛型的文档和some StackOverflow questions,但它们不适合我的情况。

我介绍了Peano特质以及ZeroSucc类型:

trait Peano {}

struct Zero;
struct Succ<T: Peano>(T);

并为两种类型实现了Peano特征,以便能够对两者进行抽象:

impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}

起初我想为std::ops::Add实现Peano,但我很快就发现我做的事情非常错误,所以我决定从更简单的事情开始 - 枚举:

trait Enumerate<T: Peano> {
    fn succ(&self) -> Succ<T>;
    fn pred(&self) -> Option<T>;
}

impl<T> Enumerate<T> for Zero where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) } // mismatched types: Zero instead of T
    fn pred(&self) -> Option<T> { None }
}

impl<T> Enumerate<T> for Succ<T> where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) } // mismatched types: Succ<T> instead of T
    fn pred(&self) -> Option<T> { Some(self.0) }
}

我错过了什么?我试验了拳击结果(尽管如果可能我想避免这种情况),但错误只是改为mismatched types: Box<Succ<T>> instead of Box<Peano>,所以我不确定这是否有用。

以下完整代码:

trait Peano {}

#[derive(Debug, Clone, Copy, PartialEq)]
struct Zero;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Succ<T: Peano>(T);

impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}

trait Enumerate<T: Peano> {
    fn succ(&self) -> Succ<T>;
    fn pred(&self) -> Option<T>;
}

impl<T> Enumerate<T> for Zero where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) }
    fn pred(&self) -> Option<T> { None }
}

impl<T> Enumerate<T> for Succ<T> where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) }
    fn pred(&self) -> Option<T> { Some(self.0) }
}

1 个答案:

答案 0 :(得分:4)

T中有一个Enumerate ...没有任何意义。

如果你回顾一下Peano特征,你会发现它没有TSucc的实现有一个参数,但特征本身没有。

这同样适用于此。

让我们从缩小的范围开始:Enumerate只能前进。

use std::marker::Sized;

trait Peano {}

#[derive(Debug, Clone, Copy, PartialEq)]
struct Zero;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Succ<T: Peano>(T);

impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}

trait Enumerate: Peano + Sized {
    fn succ(self) -> Succ<Self>;
}

impl Enumerate for Zero {
    fn succ(self) -> Succ<Self> { Succ(self) }
}

impl<T> Enumerate for Succ<T> where T: Peano {
    fn succ(self) -> Succ<Succ<T>> { Succ(self) }
}

一些兴趣点:

  • 您可以将当前类型称为Self,在定义特征时非常有用,因为实施者的类型事先是未知的
  • 您可以在特征名称
  • 之后使用: Peano + Sized语法来约束特征的实施者

现在,您还有一个我没有实现的prev方法。问题是,将prev应用于Zero是没有意义的。在这种情况下,我建议您将Enumerate重命名为Next,并且我将展示如何创建Prev特征:

trait Prev: Peano + Sized {
    type Output: Peano + Sized;
    fn prev(self) -> Self::Output;
}

impl<T> Prev for Succ<T> where T: Peano {
    type Output = T;
    fn prev(self) -> Self::Output { self.0 }
}

语法type Output: Peano + Sized关联类型,它允许每个实现者指定针对其特定情况使用的类型(并避免使用 user 特性,不得不猜测他们应该使用哪种类型。)

指定后,可以在特征中将其称为Self::Output,或从外部称为<X as Prev>::Output(如果X实施Prev)。

由于特征是独立的,因此您只有PrevPeano实现}实际上拥有前任的数字。

为什么Sized约束?

目前,Rust要求返回类型具有已知大小。这是一个实现限制:在实践中,调用者必须在堆栈上保留足够的空间,以便被调用者记下返回值。

然而......对于类型级计算,这是没用的!那么,我们该怎么办?

好吧,首先我们添加检查计算结果的方便方法(比Debug输出更漂亮):

trait Value: Peano {
    fn value() -> usize;
}

impl Value for Zero {
    fn value() -> usize { 0 }
}

impl<T> Value for Succ<T> where T: Value {
    fn value() -> usize { T::value() + 1 }
}

fn main() {
    println!("{}", Succ::<Zero>::value());
}

然后......让我们摆脱那些方法,它们什么都不带来;因此,重新设计的特征是:

trait Next: Peano {
    type Next: Peano;
}

impl Next for Zero {
    type Next = Succ<Zero>;
}

impl<T> Next for Succ<T> where T: Peano {
    type Next = Succ<Succ<T>>;
}

fn main() {
    println!("{}", <Zero as Next>::Next::value());
}

trait Prev: Peano {
    type Prev: Peano;
}

impl<T> Prev for Succ<T> where T: Peano {
    type Prev = T;
}

fn main() {
    println!("{}", <<Zero as Next>::Next as Prev>::Prev::value());
}

现在,您可以继续实施Add和co,但如果使用方法实现特征,则可能需要其他约束。