How to define a trait with a function that returns an array with type-specific size?

Hello. I would like to create a trait like this:

pub trait MyTrait: Sized {
    type BaseType: Sized;
    fn to_bytes(&self) -> Result<[u8; SIZE_OF_BASE_TYPE], Error> {
        /* ... */
    }
}

Unfortunately, this does not work:

pub trait MyTrait: Sized {
    type BaseType: Sized;
    fn to_bytes(&self) -> Result<[u8; size_of::<Self::BaseType>()], Error> {
        /* ... */
    }
}

Also does not work:

pub trait MyTrait: Sized {
    type BaseType: Sized;
    const SIZE_OF_TYPE: usize = size_of::<Self::BaseType>();
    fn to_bytes(&self) -> Result<[u8; SIZE_OF_TYPE], Error> {
        /* ... */
    }
}

The following does work:

pub trait MyTrait<const SIZE_OF_TYPE: usize>: Sized {
    type BaseType: Sized;
    fn to_bytes(&self) -> Result<[u8; SIZE_OF_TYPE], Error> {
        /* ... */
    }
}

...but there is no way to ensure that SIZE_OF_TYPE equals size_of::<Self::BaseType>() :face_with_raised_eyebrow:

Is there any way to improve this?

You can check:

    fn to_bytes(&self) -> Result<[u8; SIZE_OF_TYPE], Error> {
        const {
            if SIZE_OF_TYPE != size_of::<Self::BaseType>() {
                panic!("incorrect size");
            }
        }
        todo!("rest of the function")
    }

But other than that, you are running into the fundamental limitations of current const generics: you can never have a calculation “in the middle” such that the value of a const generic depends on another generic indirectly; you can only use non-generic constant expressions, and copy other const generic parameters without doing any computation. Improving on that is tracked by Tracking Issue for complex generic constants: `feature(generic_const_exprs)` · Issue #76560 · rust-lang/rust · GitHub

But then, the size of the type does not necessarily equal its byte size due to alignment.

Also, #[repr(packed)] might not be an option on some platforms.

For my use case, I thus crated le_stream.

I had a similar problem and came to the conclusion that it's easier to just return a Vec<u8>.

If you want to avoid the heap allocation, and if the data to be return is usually small, consider using ArrayVec (or maybe TinyVec) from the tinyvec create.

const MAX_SIZE: usize = 8usize;

pub trait MyTrait: Sized {
    type BaseType: Sized;
    fn to_bytes(&self) -> Result<ArrayVec<[u8; MAX_SIZE]>, Error> {
        let mut buffer = ArrayVec::new();
        buffer.resize(size_of::<Self::ReprType>(), 0u8);

        /* ... */

        Ok(buffer)
    }
}