Polymorphic return type

I have an implementation using Rust's Index trait like so

trait SomeTrait<I: Index<Idx> + Index<Vec<Idx>>, Idx> {
    fn foo(&self, _: I) -> Vec<Idx>;
}

impl<I: Index<Idx> + Index<Vec<Idx>>, Idx> SomeTrait<I, Idx> for SomeStruct {
    fn foo(&self, i: I) -> Vec<Idx> {
        // some stuff happens here
    }
}

This works just fine. But the Vec<Idx> bothers me. See, the actual type I would like to return is simply an Idx that the parameter I implements as Index<_>. Vec<Idx> should in itself be an Idx. In other words, I: Index<Idx> AND I: Index<Vec<Idx>>. So I would like to return a type that is either of those parameters in Index<_>.

I'm not sure how to express this in the return type so that I can return either an Idx singularly, or a Vec multiply, because both can be used as indexes for parameter I. i.index(idx) and i.index(vec_idx) are both valid.

If I say fn foo(&self, _: I) -> Idx and I try to return Vec then I get an error expected Idx, found struct Vec<Idx>. If I return Vec<Idx> then I can't return just an Idx singularly. One or the other but both should be valid. The only other way I can think of is to introduce a third generic parameter for the return type, but I'm loath do that.

You probably want an associated type, since that's how generics express output types:

trait SomeTrait {
    type I: Index<Idx>;

    fn index(&self) -> Self::I;
}
1 Like

Keep in mind that the return type should be an Idx to I, not I itself. I is the input to foo(). So the associated type should be generic over anything and in the case of using an AT as an Idx, the parameter I should impl IndexSelf::Output.

Here:

fn bar<I, T>(i: I, t: T)
where
    I: Clone + Index<SomeType> + Index<Vec<SomeType>>,
    T: SomeTrait,
{
    let idx = t.foo(i.clone());
    i[idx];
//  ^^^^^^
}

After monomorphization, I and T (and SomeType) are fixed. If the output of <T as SomeTrait<I, SomeType>>::foo could vary by type, it would be ambiguous which implementation of Index to call in the final statement.

You could return an enum, but you can't implement Index for foreign types (though you could supply a wrapper for indexing as well).

If you go with an associated type, there's probably no way to have two blanket implementations where one returns a Vec and the other doesn't; I don't think this will help.

You could let the caller choose, but you'll need to always be able to produce anything the caller chooses (e.g. via some other trait bounds on Out), which seems unlikely. The caller will have to specify which they want.

You could have a separate foo_singular(...) -> Option<Idx> method or the like maybe. (Exactly when you're wanting to return a non-Vec is not clear to me.)

Yeah the more I think about it, the more it seems like this is unreasonable for me to try to do. However, it seems to compile when I try with GATs.

impl<T, I> SomeTrait<I> for SomeStruct<T>
    where
        I: Iterator<Item = T>,
{
    type Output<Idx: Copy + Incrementable> = OurVec<Idx>;

    fn foo<Idx: Copy + Incrementable + Default>(&self, i: I) -> Self::Output<Idx> where I: Index<Idx> {
        let mut idxes: Vec<Idx> = Vec::new();
        for (idx, item) in i.enumerateble() {
            if &self.0 == &item {
                idxes.push(idx);
            }
        }

        OurVec(idxes)
    }
}

Incrementable and enumeratable are my own implementation.

One of the problems now is that this can't be made into a trait object?

let e: &dyn SomeTrait<Vec<u32>, Output<usize> = OurVec<usize>> = &SomeStruct(1u32); //error[E0038]: the trait SomeTrait cannot be made into an object

And I probably can't make other impls without conflicting coherence.

It has a generic function, so no, it can't be a trait object.