@scottmcm has the right answer here. The compiler intrinsically knows how to index into slices. So in a way, implementing Index
on slices seems redundant. In fact, you'll find this behavior for many of the traits in std::ops
. The implementation for impl Add<i32> for i32
is basically
impl Add for i32 {
type Output = i32;
#[inline]
#[rustc_inherit_overflow_checks]
fn add(self, other: i32) -> i32 { self + other }
}
Why does this not recurse? Same reason as for Index
. The compiler intrinsically knows how to add two i32
s.
(Source here, it uses macros because this impl
is the same for all the primitive numeric types.)
So why does rust have these seemingly redundant impl
s? Because they make generic code possible. Here's a (very contrived) example where you want a function that always adds 2 to an index before doing the operation.
use std::ops::Add;
use std::ops::Index;
fn main() {
let vec: Vec<_> = vec![9, 8, 7, 6, 5];
let slice: &[_] = &vec[..];
dbg!(foo(&vec, 1)); // equivalent to vec[3]
dbg!(foo(slice, 2)); // equivalent to slice[4]
}
// Takes any collection, including slices, that can be indexed into by `Ind`
fn foo<Out, Ind, C>(collection: &C, index: Ind) -> &Out
where
C: Index<Ind, Output = Out> + ?Sized,
Ind: From<u8> + Add<Output = Ind>,
{
let i = index + 2.into();
&collection[i]
}
Playground