Makogan
Makogan

Reputation: 9558

Rust define trait interface for any type?

I am building a rendering engine, one thing I want is to handle mangagement of arbitrary mesh data, regardless of representation.

My idea was, define a trait the enforces a function signature and then serialization can be handled by the user while I handle all the gpu stuff. This was the trait I made:

pub enum GpuAttributeData
{
    OwnedData(Vec<Vec<i8>>, Vec<u32>),
}
pub trait GpuSerializable
{
    fn serialize(&self) -> GpuAttributeData;
}

So very simple, give me a couple of arrays.

When i tested things inside the crate it worked, but I moved my example outside the crate, i.e. I have this snippet in an example:


impl <const N : usize> GpuSerializable for [Vertex; N]
{
    fn serialize(&self) -> GpuAttributeData
    {
        let size = size_of::<Vertex>() * self.len();
        let data = unsafe {
            let mut data = Vec::<i8>::with_capacity(size);
            copy_nonoverlapping(
                self.as_ptr() as *const i8, data.as_ptr() as *mut i8, size);
            data.set_len(size);
            data
        };

        // let indices : Vec<usize> = (0..self.len()).into_iter().collect();
        let indices = vec![0, 1, 2];
        let mut buffers :Vec<Vec<i8>> = Vec::new();
        buffers.push(data);

        return GpuAttributeData::OwnedData(buffers, indices);
    }
}

Which gives me this error:

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> examples/01_spinning_triangle/main.rs:41:1
   |
41 | impl <const N : usize> GpuSerializable for [Vertex; N]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------
   | |                                          |
   | |                                          this is not defined in the current crate because arrays are always foreign
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

This utterly breaks my design. The whole point of what I am trying to achieve is, anyone anywhere should be able to implement that trait, inside or outside any crate, to be able to serialize whatever data they have, in whatever esoteric format it is.

Can I somehow bypass this restriction? If not, is there another way I could enforce at runtime "give me an object that has this function signature among its methods"?

Upvotes: 1

Views: 451

Answers (1)

cdhowie
cdhowie

Reputation: 169068

Rust has the orphan rule, which says that any implementation of a trait on a type must exist in the same crate as at least one of the trait or the type. In other words, both types can't be foreign. (It's a bit more complex than this -- for example, you can implement a foreign generic trait on a foreign type if a generic type argument to the generic trait is a type declared in the local crate.)

This is why you frequently see so-called "newtypes" in Rust, which are typically unit structs with a single member, whose sole purpose is to implement a foreign trait on a foreign type. The newtype lives in the same crate as the implementation, so the compiler accepts the implementation.

This could be realized in your example with a newtype around the array type:

#[repr(transparent)]
struct VertexArray<const N: usize>(pub [Vertex; N]);

impl<const N: usize> Deref for VertexArray<N> {
    type Target = [Vertex; N];
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<const N: usize> DerefMut for VertexArray<N> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl<const N: usize> GpuSerializable for VertexArray<N> {
    // ...
}

Note that because of #[repr(transparent)], the layout of both VertexArray<N> and [Vertex; N] are guaranteed to be identical, meaning you can transmute between them, or references to them. This allows you to, for example, reborrow a &[Vertex; N] as a &VertexArray<N> safely, which means you can store all of your data as [Vertex; N] and borrow it as the newtype at zero cost whenever you need to interact with something expecting an implementation of GpuSerializable.

impl<const N: usize> AsRef<VertexArray<N>> for [Vertex; N] {
    fn as_ref(&self) -> &VertexArray<N> {
        unsafe { std::mem::transmute(self) }
    }
}

Upvotes: 5

Related Questions