kyku
kyku

Reputation: 6052

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?

Upvotes: 2

Views: 320

Answers (2)

Netwave
Netwave

Reputation: 42786

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

Upvotes: 4

Masklinn
Masklinn

Reputation: 42562

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

A generic type means the caller decides what the type is, because I (the type) is an input parameter so the caller should be able to pass in whatever they want as long as it fulfills the constraints.

But that's not the case here, you give the caller no choice.

The way to give the caller no choice, but to give the trait implementer a choice is an associated type.

Sadly as Netwave notes, here this requires a generic associated type (as Iterator is generic over the 'a lifetime), and GATs are not stable.

Two ways around that are to parameterize the trait itself, and to implement the trait for a reference rather than the type. https://stackoverflow.com/a/33756123/8182118 has examples and discussions of the possibilities.

Upvotes: 2

Related Questions