Search code examples
rustrust-polarsrust-ndarray

`AsArray` cannot be made into an object when implementing a trait for a trait


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?


Solution

  • 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.
    }