Search code examples
rusttraits

Implementing a trait function that returns generic


In the following code, the trait called LimitCollection has a function that returns a type that implements Iterator trait.

struct Limit {}

impl Limit {
    fn is_violated(&self) -> bool {
        todo!()
    }
}

trait LimitCollection {
    fn get_violated_limits<'a, I>(&'a self) -> I
    where
        I: Iterator<Item = &'a Limit>;
}

Now I'd like to make something that implements the trait but the compiler complains that there is mismatch between the type that I return and the generic return type: "expected type parameter I, found struct Filter"

struct ConcreteLimitCollection {
    limits: Vec<Limit>,
}

impl LimitCollection for ConcreteLimitCollection {
    fn get_violated_limits<'a, I>(&'a self) -> I
    where
        I: Iterator<Item = &'a Limit>,
    {
        self.limits.iter().filter(Limit::is_violated)
    }
}

Is there any way to make this construct work, preferably without resorting to dynamic allocation?


Solution

  • Usually you would use an associated type , in this case we would have some lifetimes involved (we need a GAT, generic associated type). Sadly what it is needed is not stabilized yet:

    #![feature(generic_associated_types)]
    
    struct Limit {}
    
    impl Limit {
        fn is_violated(&self) -> bool {
            todo!()
        }
    }
    
    trait LimitCollection {
        type Output<'a>: Iterator<Item = &'a Limit> where Self: 'a;
        fn get_violated_limits<'a>(&'a self) -> Self::Output<'a>;
    }
    
    struct ConcreteLimitCollection {
        limits: Vec<Limit>,
    }
    
    impl LimitCollection for ConcreteLimitCollection {
        type Output<'a> = Box<dyn Iterator<Item = &'a Limit> + 'a> where Self: 'a;
        
        fn get_violated_limits<'a>(&'a self) -> Self::Output<'a> {
            Box::new(self.limits.iter().filter(|l| l.is_violated()))
        }
    }
    

    Playground

    Disclaimer, I used Box<dyn Iterator<Item = &'a Limit> + 'a> even if you did not wanted to use dinamic allocation, but in this case you would just have to fulfill the whole Filter<...> type. Just did used a Boxed iterator for the example simplicity.

    @SvenMarnach fitted the types :

    impl LimitCollection for ConcreteLimitCollection {
        type Output<'a> = std::iter::Filter<std::slice::Iter<'a, Limit>, fn(&&Limit) -> bool>;
        
        fn get_violated_limits<'a>(&'a self) -> Self::Output<'a> {
            self.limits.iter().filter(|l| l.is_violated())
        }
    }
    

    Playground