1

I am trying to implement a trait for any sequence of elements, so that it will work for vectors, arrays and slices. So far I've tried several approaches, but I can't compile any of them :(

I have this trait, and a function that uses it, and a basic data type implementing the trait:

trait Hitable {
    fn hit(&self, val: f64) -> bool;
}

fn check_hit<T: Hitable>(world: &T) -> bool {
    world.hit(1.0)
}

struct Obj(f64);

impl Hitable for Obj {
    fn hit(&self, val: f64) -> bool {
        self.0 > val
    }
}

I'd like to be able to implement that trait for sequence of Obj's. It works fine if I just restrict it to vectors:

impl<T> Hitable for Vec<T>
where
    T: Hitable,
{
    fn hit(&self, val: f64) -> bool {
        self.iter().any(|h| h.hit(val))
    }
}

fn main() {
    let v = vec![Obj(2.0), Obj(3.0)];
    println!("{}", check_hit(&v));
}

But I want to make it more generic so that it works for arrays and slices; how can I do that?

I tried the following four attempts:

Attempt #1: for iterator on Hitables.

// It's not clear how to call it:
//    vec.iter().hit(...) does not compile
//    vec.into_iter().hit(...) does not compile
//
impl<T, U> Hitable for T
where
    T: Iterator<Item = U>,
    U: Hitable,
{
    fn hit(&self, val: f64) -> bool {
        self.any(|h| h.hit(val))
    }
}

Attempt #2: for something which can be turned into iterator.

// Does not compile as well:
//
//         self.into_iter().any(|h| h.hit(val))
//         ^^^^ cannot move out of borrowed content
//
impl<T, U> Hitable for T
where
    T: IntoIterator<Item = U>,
    U: Hitable,
{
    fn hit(&self, val: f64) -> bool {
        self.into_iter().any(|h| h.hit(val))
    }
}

Attempt #3: for slices.

// This usage doesn't compile:
//     let v = vec![Obj(2.0), Obj(3.0)];
//     println!("{}", check_hit(&v));
//
// It says that Hitable is not implemented for vectors.
// When I convert vector to slice, i.e. &v[..], complains about
// unknown size in compilation time.
impl<T> Hitable for [T]
where
    T: Hitable,
{
    fn hit(&self, val: f64) -> bool {
        self.iter().any(|h| h.hit(val))
    }
}

Attempt #4: for Iterator + Clone

//     let v = vec![Obj(2.0), Obj(3.0)];
//     println!("{}", check_hit(&v.iter()));
//
// does not compile:
//     println!("{}", check_hit(&v.iter()));
//                    ^^^^^^^^^ `&Obj` is not an iterator
//
impl<T, U> Hitable for T
where
    T: Iterator<Item = U> + Clone,
    U: Hitable,
{
    fn hit(&self, val: f64) -> bool {
        self.clone().any(|h| h.hit(val))
    }
}

Playground link

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
dying_sphynx
  • 1,136
  • 8
  • 17
  • `&self` — are you aware of the concept of mutability in Rust, as well as the fact that `Iterator::next` requires a mutable receiver? – Shepmaster Mar 13 '19 at 18:18
  • `Hitable for T` ... `&self` — are you aware that references to types might implement different traits than the type itself? – Shepmaster Mar 13 '19 at 18:19
  • @Shepmaster Yes, I think I understand those concepts. My trait doesn't need to mutate underlying value, so perhaps implementing it on iterators is not a good idea. Regarding your second point: I'm not quite sure what happens to the trait's function argument &self, when I implement the trait for some reference type &T. Will it be &&T? Should I dereference it? I'm using &self in the trait function to show that I only need read access (no taking ownership, no mutation)... – dying_sphynx Mar 13 '19 at 18:25
  • 1
    [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6c46b4a5c7583ed274f3a228444f7d3a) , The working version here for into_iter(). and iter(). It is related with the mutability as @Shepmaster pointed. – Ömer Erden Mar 13 '19 at 18:27
  • @ÖmerErden Yes, thank you, it works now indeed. However, now when I realise that implementing it for iterator requires mutation I think that it's better to implement it on slices only, since the trait function shouldn't require mutation (it just reads the data). – dying_sphynx Mar 13 '19 at 18:34

1 Answers1

1

1. Iterator-based

This cannot work because iterators need to be mutable in order to advance them, but your trait requires &self.

2. IntoIterator-based

I'd change the trait to take self by value and then only implement it for references to Obj. This also allows implementing it for any type that implements IntoIterator:

trait Hitable {
    fn hit(self, val: f64) -> bool;
}

fn check_hit<T: Hitable>(world: T) -> bool {
    world.hit(1.0)
}

struct Obj(f64);

impl Hitable for &'_ Obj {
    fn hit(self, val: f64) -> bool {
        self.0 > val
    }
}

impl<I> Hitable for I
where
    I: IntoIterator,
    I::Item: Hitable,
{
    fn hit(self, val: f64) -> bool {
        self.into_iter().any(|h| h.hit(val))
    }
}

fn main() {
    let o = Obj(2.0);
    let v = vec![Obj(2.0), Obj(3.0)];

    println!("{}", check_hit(&o));
    println!("{}", check_hit(&v));
}

See also:

3. Slice-based

I find that reading the entire error message, not just the one line summary, can help:

error[E0277]: the size for values of type `[Obj]` cannot be known at compilation time
  --> src/main.rs:28:20
   |
28 |     println!("{}", check_hit(&v[..]));
   |                    ^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `[Obj]`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: required by `check_hit`
  --> src/main.rs:5:1
   |
5  | fn check_hit<T: Hitable>(world: &T) -> bool {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Specifically, this bit: note: required by check_hitcheck_hit requires that T be Sized. Removing that restriction allows this version to work:

fn check_hit<T: Hitable + ?Sized>(world: &T) -> bool {
//                      ^~~~~~~~
    world.hit(1.0)
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Great, thank you! Yes, I've seen that error message and even checked the suggested link. However, I didn't understand where exactly I need to add `?Sized`, for some reason I though that I have to add it to the bounds in the trait implementation (I tried but it didn't help). Is this approach (i.e. implementing a trait on slices) considered the best for such a task? – dying_sphynx Mar 13 '19 at 18:33
  • 1
    @dying_sphynx I don't know about "best". I think both working solutions are reasonable. – Shepmaster Mar 13 '19 at 19:17