1

Basically I'm trying to make a trait that indicates the ability to be converted into a 2D ndarray aka ndarray::Array2:

trait Into2DArray{
    fn to_array(&self) -> Array2<f64>;
}

I would like to do this by expanding the existing AsArray trait, but Rust forbids me from implementing a third party trait for a third party struct (polars::DataFrame) for some esoteric reason, so instead I have to make my own trait for this.

Anyway, this works well for polars::DataFrame:

impl Into2DArray for DataFrame {
    fn to_array(&self) -> Array2<f64> {
        return self.to_array();
    }
}

However, I also want to implement this for anything that is already convertable into a 2D array, so I implement this trait for the AsArray trait mentioned above:

impl Into2DArray for AsArray<'_, f64, Ix2> {
    fn to_array(&self) -> Array2<f64> {
        return self.into();
    }
}

However the compiler gives me grief for this:

   |
26 | impl Into2DArray for AsArray<'_, f64, Ix2> {
   |                      ^^^^^^^^^^^^^^^^^^^^^ `AsArray` cannot be made into an object
   |
   = note: the trait cannot be made into an object because it requires `Self: Sized`
   = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>

I understand that has something to do with object safety but I thought I had fulfilled all the criteria mentioned on that page, namely the trait doesn't return Self, and all the generic parameters of AsArray are specified.

What is going wrong, and how can I fix it?

Migwell
  • 18,631
  • 21
  • 91
  • 160
  • `impl> Into2DArray for T {…}` – Jmb Jul 22 '21 at 12:53
  • `AsArray` is not object safe because of the second rule in the link: *"`Sized` must not be a supertrait. In other words, it must not require `Self: Sized`"*. `AsArray` has the supertrait `Into<_>` which has the supertrait `Sized`. – kmdreko Jul 22 '21 at 12:54
  • `impl<'a, T: AsArray<'a, f64, Ix2>> Into2DArray for T` worked, which is a slight adjustment of @Jmb's answer. If anyone can explain how this is different then I'll happily accept it as an answer. – Migwell Jul 22 '21 at 13:14
  • This question is a duplicate of [1](https://stackoverflow.com/questions/45116984/the-trait-cannot-be-made-into-an-object) and [2](https://stackoverflow.com/questions/31082179/is-there-a-way-to-implement-a-trait-on-top-of-another-trait). But although they both contain the same answer as my comment above, I'm afraid neither of them really explains _why_ this is the answer. – Jmb Jul 22 '21 at 14:22

1 Answers1

2

What you were trying to do is implementing the Into2DArray trait for the AsArray dynamic trait object. There should have been a warning of using AsArray without dyn anyway.

But this is not what you actually want. You want to implement it for any type that implements AsArray. Just like you did in your comment.

It is important to know the difference between these two things:

trait NeedThis {
    fn can_be_called_by_the_impl(&self) {}
}

trait ToDoThis {
    fn example(&self);
}

impl ToDoThis for dyn NeedThis {
    fn example(&self) {
        self.can_be_called_by_the_impl()
    }
}

impl NeedThis for u8 {}

fn main() {
    let num: u8 = 0;
    // num.example(); // doesn't work because ToDoThis is not implemented for u8

    let num_as_trait_obj: &dyn NeedThis = &0_u8 as &dyn NeedThis;
    num_as_trait_obj.example(); // works because this time it is a trait object
}
trait NeedThis {
    fn can_be_called_by_the_impl(&self) {}
}

trait ToDoThis {
    fn example(&self);
}

// removing ?Sized would make it the same as T: NeedThis + Sized
impl<T: NeedThis + ?Sized> ToDoThis for T {
    fn example(&self) {
        self.can_be_called_by_the_impl()
    }
}

impl NeedThis for u8 {}

fn main() {
    let num: u8 = 0_u8;
    num.example(); // works because we implemented it for all types that implement NeedThis

    let num_as_trait_obj: &dyn NeedThis = &0_u8 as &dyn NeedThis;
    num_as_trait_obj.example(); // works because dyn NeedThis also implements NeedThis.
    // This is only true because we added ?Sized to the bounds of the impl block.
    // Otherwise it doesn't work because dyn NeedThis is not actually Sized.
    // And a Sized bound is implied by default.
}
Hadus
  • 1,551
  • 11
  • 22
  • Loads of code but hopefully it clarifies things instead of making things more convoluted. – Hadus Jul 22 '21 at 23:25
  • I find it very strange that `impl ToDoThis for NeedThis` and `impl ToDoThis for T` have such different meanings given that they seem to be saying the same thing. Is there a rationale behind this? Does each syntax have a different name? – Migwell Jul 23 '21 at 01:57
  • @Migwell `impl ToDoThis for NeedThis` is deprecated nowadays, it would be written `impl ToDoThis for dyn NeedThis` for clarity. `dyn NeedThis` is a [trait object](https://doc.rust-lang.org/book/ch17-02-trait-objects.html) – Tavian Barnes Jul 23 '21 at 03:12
  • 1
    @Migwell they're not saying the same thing: `impl ToDoThis` creates a _different_ function for each type that implements `NeedThis`, whereas `impl ToDoThis for dyn NeedThis` creates a _single_ function that can be used with any `&dyn NeedThis`. See this [playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=198a87e81bd6788ad0b44c47c1c02031) showing that the functions have different addresses in the first case and the same address in the second. – Jmb Jul 23 '21 at 06:50