Implement Index Trait for Reference and Owned Types

I'm trying to implement the Index trait for a struct that I've defined in my crate. I want my struct to be able to be either the owner of a vec, or have a reference (or mutable reference) to a vec as one of it's fields so I've defined that field as a generic type like so:

use std::ops::{Index, IndexMut};

#[derive(Debug)]
struct BackPack<V> {
    data: V,
}

impl<V, T> Index<usize> for BackPack<V>
where
    V: Index<usize, Output = T>+IndexMut<usize, Output = T>,
{
    type Output = T;

    fn index(&self, rhs: usize) -> &Self::Output {
        &self.data[rhs]
    }
}

This implementation works if I give ownership of the vec to the struct like this:

fn main() {
    let mut a = vec![0; 100];
    {
        let b3 = BackPack{
            data: a,
        };
        println!("{:?}", b3[5]);
    }
}

But gives me an error if I try to do this:

fn main() {
    let mut a = vec![0; 100];
    {
        let b1 = BackPack{
            data: &a,
        };
        println!("{:?}", b1[5]);
    }
}
cannot index into a value of type `BackPack<&std::vec::Vec<{integer}>>`rustc(E0608)

I can update my Index impl to be this (change V to &V in the impl line):

impl<V, T> Index<usize> for BackPack<&V>
where
    V: Index<usize, Output = T>+IndexMut<usize, Output = T>,
{
    type Output = T;

    fn index(&self, rhs: usize) -> &Self::Output {
        &self.data[rhs]
    }
}

Which basically swaps the errors - now the version of main where I give ownership of the vec to my Backpack gives the cannot index... error, but the reference version works.

So... I tried just creating two implementations of the Index trait (one for BackPack<V> and another for BackPack<&V>) but this gives a new error:

conflicting implementations of trait `std::ops::Index<usize>` for type `BackPack<&_>`

note: downstream crates may implement trait `std::ops::Index<usize>` for type `&_`

Am I attempting to do something that is fundamentally impossible? Is there a way that I could change this implementation so it could accomplish what I'm hoping to do? Thank you!

Right, it's a bit unfortunate that references to Vec don't implement Index, yet you still have to account for the fact that they could do so in the future. Well, and you have to account for the fact that any other type, even downstream, could add an Index<usize> implementation both for Foo and for &Foo.

You don't have to base your Index impl on the underlying type's Index implementation necessarily though. If your goal is only to support a handful of types anyways, you could just list a few concrete implementations impl<T> Index<usize> for BackPack<Vec<T>>, impl<T> Index<usize> for BackPack<&Vec<T>>, impl<T> Index<usize> for BackPack<&[T]>, etc.

The code example you show seems pretty minimized. It's not clear what BackPack achieves at all; if the data structure has any additional features, you'll probably have to think about how to access elements of the contained vec anyways in a general context, right? Then accessing elements for the purpose of indexing could use the same approach?


If you only want to support things like vectors and slices (owned or borrowed), then you could also consider using an Index implementation based on V: AsRef<[T]>; well in that case, the item type would need to be fixed, I suppose, so something like

#[derive(Debug)]
struct BackPack<V, T> {
    data: V,
    marker: PhantomData<fn() -> T>,
}

impl<V, T> Index<usize> for BackPack<V, T>
where
    V: AsRef<[T]>,
{
    type Output = T;

    fn index(&self, rhs: usize) -> &Self::Output {
        &self.data.as_ref()[rhs]
    }
}

(playground)

1 Like

Thank you @steffahn! My use case should only need to support a few concrete types so I think your first suggestion should suffice. I need to learn a bit more about the AsRef approach you suggested as well but I'm confident one of the two should work. Thank you for your detailed response!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.