Peter Hall
Peter Hall

Reputation: 58875

How can I put a trait constraint on a type while implementing a trait?

I have an Iterator that produces Fibonacci numbers. I restricted the type to u32, but now I'm struggling to make it generic for any numeric type.

Working, non-generic code:

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);
}

The issue is that the implementation of Iterator requires a constraint to Num, but I don't know how to express this:

 impl <T : Num> Iterator for Fib<T> { ... }

Produces:

 use of undeclared trait name `Num`

And when I try either use std::num::{Num} or use num::traits::{Num}, I am told that the modules do not exist.

Upvotes: 4

Views: 3529

Answers (1)

Michael Eden
Michael Eden

Reputation: 1008

I don't think you want Fib to be generic over numeric types, but types that implement the + operator. Like so:

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 is the trait that allows you to use the + operator and produce Output. In this case N implements the Add<Output = N> trait which means N + N will produce something of type N.

That sounds like it, but when you try to do self.next + self.value you are moving value and next out of self which causes an error.

You can't get away with not moving the values since the definition of add has this method signature:

fn add(self, rhs: RHS) -> Self::Output;

RHS in Add's case is just Self. So in order to restrict N it to types that can just be copied with little overhead I added the Copy trait as a restriction.

OP mentions an interesting point: Is it possible to alias traits? In short no. You could make a new trait:

trait SimpleAdd: Add<Output = Self> + Copy {
}

But then you would have to implement that trait for all the types you wanted. I.e. i32 does not automatically implement SimpleAdd. But you can do it with generics if you wanted:

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

So the above two blocks will get you the same thing as a trait alias, but it seems like a hassle.

Upvotes: 4

Related Questions