Here is a trait (simplified for the question) which I'd like to implement for every type that behaves like a slice:
trait SliceLike {
type Item;
/// Computes and returns (owned) the first item in a collection.
fn first_item(&self) -> Self::Item;
}
Note that the Item
type is an associated type; I want each type that is SliceLike
to have a unique element type.
Here is an attempt at a blanket implementation:
use std::ops::Deref;
impl<T: Clone, U: Deref<Target = [T]>> SliceLike for U {
type Item = T;
fn first_item(&self) -> Self::Item {
self[0].clone()
}
}
For example, this compiles and runs:
let data: Vec<usize> = vec![3, 4];
assert_eq!(data.first_item(), 3);
let data: &[usize] = &[3, 4];
assert_eq!(data.first_item(), 3);
let data: Box<[usize]> = Box::new([3, 4]);
assert_eq!(data.first_item(), 3);
let data: Rc<[usize]> = Rc::new([3, 4]);
assert_eq!((&data).first_item(), 3);
This also compiles and runs:
fn stub(x: &[usize]) -> usize {
x.first_item()
}
let data: [usize; 2] = [3, 4];
assert_eq!(stub(&data), 3);
assert_eq!(stub(&[3, 4]), 3);
But if I inline stub()
it fails to compile:
let data: [usize; 2] = [3, 4];
assert_eq!(data.first_item(), 3); // Fails.
assert_eq!([3, 4].first_item(), 3); // Fails.
The blanket implementation uses the Deref
trait that the compiler itself uses to turn other types into slices. It will catch all third-party types that also behave like a slice.
The error message is:
error[E0599]: no method named `first_item` found for type `[usize; 2]` in the current scope
--> src/lib.rs:20:21
|
20 | assert_eq!(data.first_item(), 3); // Fails.
| ^^^^^^^^^^
|
= note: the method `first_item` exists but the following trait bounds were not satisfied:
`[usize; 2] : SliceLike`
`[usize] : SliceLike`
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `first_item`, perhaps you need to implement it:
candidate #1: `SliceLike`
In take 1 of this question, I was advised to use AsRef
instead of Deref
. That solution won't work here, because some type might implement AsRef
for more than one element type.
I think I understand what is going on. For each type T
there is a unique type <T as Deref>::Target
. When T
is &[usize; 2]
the target is [usize; 2]
, not [usize]
. The compiler is able to coerce &[T; 2]
to &[T]
if I explicitly ask it to, e.g. by using let
or stub()
, but if I don't then it's not able to work out that the coercion is required.
But it's frustrating: it's perfectly obvious to a human what the failing calls are intended to do, and the compiler understands what's required for Vec<usize>
, Box<[usize]>
, Rc<[usize]>
, &[usize]
and so on, so it doesn't seem unreasonable to try to make it work for [usize; 2]
as well.
Is there a convenient way to write first()
so that the last two calls work too? If not, is there a syntax to ask the compiler to coerce a &[usize; 2]
to a &[usize]
inline, i.e. without using let
or stub()
?
Deref
is implemented forVec
,Box
,Rc
,&T where T: ?Sized
and there isn't an implementation for arrays ([T; N]
), that is why[3, 4].first_item()
doesn't work.It isn't possible to implement
Deref
for[T; N]
due to coherence rules, therefore, the array must be coerced to a slice one way or another. The best method I am aware of is as follows:Please note that issues like this are probably going to disappear once const generic is merged.