How to get Deref coercion when using impl Trait (t

2019-07-10 04:26发布

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条回答
干净又极端
2楼-- · 2019-07-10 04:46

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.

查看更多
登录 后发表回答