2

I started off with a function a bit like this (playground):

fn find <T: Copy> (src: &[T], index: usize) -> T {
    // more complex algorithm, involving src goes here
    src[index]
}

pub fn main() {
    let x = b"abc";
    assert_eq!(b'b', find(x, 1));    
}

And I wanted to generalise it so that I can use any appropriate type for src. The best I came up with is this (playground):

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl <T: Copy> RandomAccess<T> for [T] {
    fn get_at(&self, index: usize) -> T {
        self[index]
    }
}

fn find <T: Copy, S: ?Sized + RandomAccess<T>> (src: &S, index: usize) -> T {
    // more complex algorithm, involving src goes here
    src.get_at(index)
}

pub fn main() {
    let x = b"xyz";
    assert_eq!(b'y', find(&x[..], 1));
}

However, I now can't just invoke find(x, 1), I have to create a slice: find(&x[..], 1).

Is there a way to make this generic, but still be able to invoke find as in the original example?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • It seems like the rust compiler is doing some trickery behind the scenes in the first example, and allowing `&[u8; 3]` to be coerced to `&[u8]`, but cannot do this in the second example because it cannot know for sure that this is the intention. Is that basically the issue? – Peter Hall Sep 21 '16 at 18:40
  • I tried abstracting over Index and some other stuff, but I'm unconvinced that you need to introduce such abstractions at all - they tend to complicate things without added value. What are the other `src` types `find()` can apply to? – ljedrz Sep 22 '16 at 08:22
  • 1
    The trickery is that: coercions trigger if the destination type is known. Since `find` is using a type parameter for `src`, it's not. – bluss Sep 23 '16 at 08:59

1 Answers1

2

Your second example doesn't currently work due to a limitation in the rust compiler (https://github.com/rust-lang/rust/issues/29504). However, there are a few ways around this.

The simplest way is to implement RandomAccess<T> for all C: AsRef<[T]>. This way, it'll work with [T; n], &[T], Vec<T>, etc.:

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl<T: Copy, C: AsRef<[T]>> RandomAccess<T> for C {
    fn get_at(&self, index: usize) -> T {
        self.as_ref()[index]
    }
}

fn find<T: Copy, C: RandomAccess<T>>(src: C, index: usize) -> T {
    src.get_at(index)
}

Unfortunately, you won't be able to add any other RandomAccess impls if you do that so you might as well just change find to take some collection satisfying AsRef<[T]>:

fn find<T: Copy, C: AsRef<[T]>> (src: C, index: usize) -> T {
    src.get_at(index)
}

Alternatively, if you need to be able to support collections that can't be borrowed as [T], you can implement RandomAccess<T> for [T; n] for all n in some range using a macro:

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl<T: Copy> RandomAccess<T> for [T] {
    fn get_at(&self, index: usize) -> T {
        self[index]
    }
}

macro_rules! impl_random_access {
    ($($n:expr,)*) => {
        $(
            impl <T: Copy> RandomAccess<T> for [T; $n] {
                fn get_at(&self, index: usize) -> T {
                    self[index]
                }
            }
        )*
    }
}

impl_random_access! {
    01,02,03,04,05,06,07,08,
    09,10,11,12,13,14,15,16,
    17,18,19,20,21,22,23,24,
    25,26,27,28,29,30,31,32,
}



fn find<T: Copy, S: ?Sized + RandomAccess<T>>(src: &S, index: usize) -> T {
    src.get_at(index)
}

When we eventually get type-level constants (currently on the wish-list), you should be able to just implement RandomAccess<T> for all [T; n]. But for now, you'll need to use a macro.

Steven
  • 5,654
  • 1
  • 16
  • 19