Search code examples
arraysrustindexingabstraction

Abstracting indexes in rust


I want a trait that is an indexable. However, its implementation may be a 'standard' array (one that actually holds the things it returns; e.g. Vec of integers) or a non-standard array that constructs the value in the index function (and so must return a value, not reference). What is the easiest way to do it? I can't use the Index trait as it won't allow the latter. Currently it looks like I will have to wrap "standard Index"-es (e.g. Vec) with a custom Index trait that returns some Self::Output (and not &Self::Output). Sounds a bit too much for such a simple abstraction that I expect to be a pretty common need.

(About lifetimes; Be it a value or a reference, I intend to use the indexed value only during the indexer is lifetime)


Solution

  • Working off of the answer provided by prog-fh, here is my attempt at writing Indexable while avoiding the clone. This lets us directly return the references when Index is implemented, but also write implementations that return values that aren't references.

    trait Indexable<'a, Idx>
    where
        Idx: ?Sized,
    {
        type Output<'b>: ?Sized
        where
            Self: 'b,
            'a: 'b;
        fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
        where
            'a: 'b;
    }
    
    impl<'a, T: ?Sized, Idx, V> Indexable<'a, Idx> for T
    where
        T: std::ops::Index<Idx, Output = V>,
        V: 'a,
    {
        type Output<'b> = &'b V where Self: 'b, 'a: 'b;
        fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
        where
            'a: 'b,
        {
            self.index(idx)
        }
    }
    
    struct Custom {}
    
    impl Indexable<'static, usize> for Custom {
        type Output<'a> = usize;
    
        fn at<'a>(&'a self, idx: usize) -> Self::Output<'a>
        where
            'static: 'a,
        {
            idx * 2
        }
    }
    
    fn main() {
        println!("testing on vector");
        let v = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
        for i in 0..v.len() {
            println!("{} -> {}", i, *v.at(i));
        }
    
        println!("testing on custom");
        let c = Custom {};
        for i in 0..3 {
            println!("{} -> {}", i, c.at(i));
        }
    }