Workaround for lack of type equality constraint?


#1

This is a problem that I think I’d know how to solve in e.g. OCaml, but I’m not sure on how to best phrase things to make the rust compiler happy. Which has me thinking that maybe I’m trying to code OCaml in rust and I should be going about it in a completely different way :wink:

I’m making use of a crate that’ll (generically) process some elements for me:

mod external {
    // This is an external "engine" that, given some elements of type T,
    // will produce Thingies which reference the elements and their position
    // in the &[T] passed in. We only care about the latter in this instance,
    // but it's the former that's giving us trouble.
    pub struct Thingy<T : PartialEq + Clone> {
        pub backref : usize,
        boring : T,
    }
    pub fn process_elements<T : PartialEq + Clone>(elems : &[T]) -> Vec<Thingy<T>> {
        elems.iter().enumerate().map(|(i, el)| {
            Thingy {
                backref : i,
                boring : el.clone(),
            }
        }).collect::<Vec<Thingy<T>>>()
    }
}

Then, I’m doing some filtering, reordering and grouping of the thingies and sticking them in a

struct ThingyHolder<T : PartialEq + Clone> {
    other_stuff : usize,
    thingies : Vec<Thingy<T>>,
}

(this is required because the other_stuff value depends on the eventual number of thingies).

I have two concrete types in mind for T (let’s say A and B) and the formatting when I’m eventually printing them out needs to be different. In order to achieve that, I’m defining a trait and want to implement that only for ThingyHolder<A> and ThingyHolder<B>.

The trait looks like this:

trait DumpableHolder where Self::Item : PartialEq + Clone {
    type Item;
    fn do_write(&self, &[Self::Item], &mut Write);
}

and a mock implementation for A = Vec<u8> would be:

impl DumpableHolder for ThingyHolder<Vec<u8>> {
    type Item=Vec<u8>;
    fn do_write(&self, elems : &[Self::Item], out: &mut Write) {
        writeln!(out, "stuff {}", self.other_stuff);
        for thingy in self.thingies {
            // Referencing an elem is a hard requirement
            out.write(&elems[thingy.backref]);
        }
    }
}

Now, I want to do my processing, creating ThingyHolders as I go along, but dump them out as soon as a ThingyHolder is complete. The relevant function is called process_thingies and I’ve tried to make it generic in two different ways.

Version 1, parameterizes it over T:

fn process_thingies<T>(out : &mut Write, elements : &[T], thingies : Vec<Thingy<T>>) -> io::Result<()>
where T : PartialEq + Clone,
ThingyHolder<T> : DumpableHolder

but of course this fails with the expected associated type, found type parameter error message (see the playground link for the exact error message).

Version 2, tries to parameterize over ThingyHolder<T>:

fn process_thingies<MyThingyHolder>(
    out : &mut Write,
    elements : &[<MyThingyHolder as DumpableHolder>::Item],
    thingies : Vec<Thingy<<MyThingyHolder as DumpableHolder>::Item>>) -> io::Result<()>
where MyThingyHolder : DumpableHolder

but apparently, as the construction of the ThingyHolder in the body of process_thingies references ThingyHolder< <MyThingyHolder as DumpableHolder>::Item >, it appears that the fact that MyThingyHolder necessarily implements DumpableHolder is lost on the compiler (again, see the playground version for the exact error).

At this point, however, I’m somewhat stuck as to how to work around this issue (limitation?). I’ve looked at https://stackoverflow.com/questions/29345708/matching-a-generic-parameter-to-an-associated-type-in-an-impl for inspiration, but the suggestion doesn’t seem to be directly applicable here.

Any pointers? Please note that, despite my efforts, the linked playground versions might be easier to understand than the code excerpts above.


#2

I think here you just need to say ThingyHolder<T>: DumpableHolder<Item = T> to carry the association through.


#3

Thanks! That’s exactly what I needed. I had briefly tried some variation of : DumpableHolder<Item = T> in the original (and somewhat more complicated) source and didn’t get it to work for some reason. Guess it just goes to show that one shouldn’t give up on an approach until they understand why it doesn’t work.