Search code examples
rustannotationstraitsgeneric-programming

How do I tell Rust that something implements a trait?


I'm trying to write a generic function that returns an iterator.

fn one_to_ten<T: Add>() -> std::iter::Iterator<Item = T> {
    (1..5).chain(5..=10)
}

It's generic because it needs to work for u8 through u128, and preferably also i8 through i128, for the following code:

let mut sum: u64 = 0;
for x in one_to_ten() {
    sum += x;
}

Unfortunately, Rust complains:

= help: the trait std::marker::Sized is not implemented for (dyn std::iter::Iterator<Item = T> + 'static)
error[E0277]: the size for values of type dyn std::iter::Iterator<Item = _> cannot be known at compilation time

and

error[E0308]: mismatched types
expected trait std::iter::Iterator, found struct std::iter::Chain

I'm out of my depth here. Rust seems to think I'm trying to implement a dynamically dispatched function, when I'm trying to implement a generic one instead. (And, for some reason, my chained iterator isn't accepted as an iterator – my guess is that I'm specifying a type instead of a trait, but std::iter::Iterator is a trait, not a type.)

What should my code look like?


Solution

  • Traits are unsized so you cannot return a value of trait type. Instead you must return a value of a type that implements that trait:

    fn one_to_ten<T: Add>() -> impl std::iter::Iterator<Item = T> {
        (1..5).chain(5..=10)
    }
    

    Unfortunately that brings another issue, that the (1..5) code builds a range of integers, not of T. You can fix that with a custom trait, as the std::iter::Step is still unstable.

    Something like this (playground):

    pub trait MyRange: Sized {
        type Iter: std::iter::Iterator<Item = Self>;
        fn new(a: i32, b: i32) -> Self::Iter;
        fn new_incl(a: i32, b: i32) -> Self::Iter {
            Self::new(a, b + 1)
        }
    }
    
    impl MyRange for i8 {
        type Iter = std::ops::Range<Self>;
        fn new(a: i32, b: i32) -> Self::Iter {
            (a as i8 .. b as i8) //check overflow?
        }
    }
    
    pub fn one_to_ten<T: Add + MyRange>() -> impl std::iter::Iterator<Item = T> {
        T::new(1, 5).chain(T::new_incl(5, 10))
    }
    

    And then implement the MyRange trait for every integer type you need.