Indexing by types other than usize


#1

Hello,

I would like to use u32 as an index into Vec<MyStruct>, &[MyStruct], etc. I want this because I store indices into the array as u32 for space economy, and I know in advance that the array size will always be small enough. I got tired of having to cast index as usize at every usage site (e.g. container[index as usize]), so I tried the following:

type MyIndex = u32;

impl Index<MyIndex> for [MyStruct] {
    type Output = MyStruct;
    fn index(&self, index: MyIndex) -> &Self::Output {
        self.index(index as usize)
    }
}

… but it results in an error: “the impl does not reference any types defined in this crate; only traits defined in the current crate can be implemented for arbitrary types [E0117]”.

Next, I tried:

#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct MyIndex(u32);

impl Index<MyIndex> for [MyStruct] {
    type Output = MyStruct;
    fn index(&self, index: MyIndex) -> &Self::Output {
        self.index(index.0 as usize)
    }
}

This works, but it’s specific to[MyStruct]: I would need to provide a separate impl for every other container, e.g. Vec<MyStruct>. So I tried the following:

impl<T: Index<usize, Output=MyStruct>> Index<MyIndex> for T {
    type Output = MyStruct;
    fn index(&self, index: MyIndex) -> &Self::Output {
        self.index(index.0 as usize)
    }
}

… but there is another error: “type parameter T must be used as the type parameter for some local type (e.g. MyStruct<T>); only traits defined in the current crate can be implemented for a type parameter”

By the way, looking at the difference between the two cases it seems to me that if Rust had Higher-Kinded Types, the compiler would allow something like this:

impl<Container: MyStruct -> *  
     where Container: Index<usize, Output=MyStruct>> 
Index<MyIndex> for Container {
    type Output = MyStruct;
    fn index(&self, index: MyIndex) -> &Self::Output {
        self.index(index.0 as usize)
    }
}

Is implementing Index for every container really the only way?


#2

This doesn’t answer your question, but I thought it was worth pointing out that Rust has a good reason for only supporting usize indices by default, this gist goes into detail about why it is preferable in most cases.


#3

Well, Vec in particular is a bit of an edge case because it explicitly implements Index; that isn’t an issue for similar types like Box<[MyStruct]>, which should just automatically dereference to &[MyStruct].

That said, the solution you’ve discovered is basically the only way; the language is intentionally restrictive to make sure trait implementations don’t overlap (so there aren’t two conflicting implementations of the same trait in a program).