如何在实现trait时对类型施加trait约束?

3

我有一个迭代器,可以生成斐波那契数列。我将类型限制为u32,但现在我很难使其适用于任何数值类型。

下面是可工作的、非通用代码:

struct Fib {
    value: u32,
    next: u32,
}

impl Fib {
    fn new( a : u32, b : u32 ) -> Fib {
        Fib { value : a, next : b }
    }
}

impl Iterator for Fib {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        let value = self.value;
        let next = self.value + self.next;
        self.value = self.next;
        self.next = next;
        Some( value )
    }
}


//////////////////////////////////////////////////

fn main() {

  let fib = Fib::new( 1, 2 );

  let sum = fib.filter( |x| { x % 2 == 0 })
      .take_while( |&x| { x <= 4000000 })
      .fold( 0, |sum, x| { sum + x });

  println!("{}", sum);
}

问题在于实现Iterator需要对Num进行约束,但我不知道如何表达这个约束:
 impl <T : Num> Iterator for Fib<T> { ... }

生成:

 use of undeclared trait name `Num`

当我尝试使用use std::num::{Num}或者use num::traits::{Num}时,系统提示这些模块不存在。


哦...那个特性不存在。这里的错误信息非常准确。 - Shepmaster
我在这里找到了它:https://doc.rust-lang.org/num/num/traits/trait.Num.html 我如何知道哪些文档已经过时? - Peter Hall
2
这是来自 num crate,而不是 stdlib ~> https://crates.io/crates/num - contradictioned
1个回答

3

我认为您希望Fib不仅仅是针对数字类型的通用函数,而是适用于实现了+运算符的类型。可以像这样实现:

use std::ops::Add;

struct Fib<N>
where N: Add<Output = N> + Copy {
    value: N,
    next: N,
}

impl<N> Iterator for Fib<N>
where N: Add<Output = N> + Copy {
    type Item = N;

    fn next(&mut self) -> Option<N> {
        let next = self.value + self.next;
        self.value = self.next;
        self.next = next;
        Some(next)
    }
}

fn main() {
    let fib_seq = Fib {
        value: -1,
        next: 1,
    };

    for thing in fib_seq.take(10) {
        println!("{}", thing);
    }
}
Add是一种特性,它允许您使用+运算符并生成Output。在这种情况下,N实现了Add<Output = N>特性,这意味着N + N将产生一个N类型的结果。
听起来是这样的,但当您尝试执行self.next + self.value时,您会将valuenextself移出,这会导致错误。
您不能不移动这些值,因为add的定义具有以下方法签名:
fn add(self, rhs: RHS) -> Self::Output;

在Add函数中,RHS就是Self。因此为了将N限制为只能使用开销较小的可复制类型,我添加了Copy trait作为一个限制。
OP提到了一个有趣的点:是否可以给trait取别名?简而言之,不行。 你可以创建一个新的Trait:
trait SimpleAdd: Add<Output = Self> + Copy {
}

但是你需要为所有想要的类型实现这个特性。例如,i32 不会自动实现 SimpleAdd。但是如果你想要,可以使用泛型来实现:

impl<N> SimpleAdd for N
where N: Add<Output = N> + Copy {
}

所以以上两个块将获得与trait别名相同的东西,但似乎很麻烦。

谢谢。我刚刚意识到我需要除了Num之外还要引入Copy,但是我还在研究看这是否是正确的方法。不过Add肯定是我需要的 - 我曾在“官方”文档中找到了另一个版本,但现在已不再是该语言的一部分,这很让人恼火。 - Peter Hall
另外,我不知道where语法。太棒了! - Peter Hall
2
要求 NCopy 并不是非常通用。当然,你可以将其更改为 Clone,但那会使代码变得丑陋。我可能更喜欢 where for<'a,'b> &'a N : Add<&'b N, Output = N>。这仍然适用于内置类型,但也支持更复杂的用户定义类型,既不是 Copy 也不便宜克隆(例如一些 BigInt 类型)。 - sellibitze
1
@sellibitze但是,您将不得不从“next”方法返回N的借用,我认为这使得它成为流迭代器,在rust中很难实现。 - Michael Eden
1
请注意,core::ops::AddAssign在夜间版本中可用,可以重载 +=,而且 += 不需要移动其参数,因此对于 BigInt 的实现更有效率。 - Matthieu M.
显示剩余3条评论

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