问题
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()?
Playground
回答1:
Deref is implemented for Vec, 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:
let data: [usize; 2] = [3, 4];
assert_eq!((&data[..]).first_item(), 3); // Ok
Please note that issues like this are probably going to disappear once const generic is merged.
来源:https://stackoverflow.com/questions/54451342/how-to-get-deref-coercion-when-using-impl-trait-take-2