Lokathor/bytemuck

Unwieldy duplication of the public API

notgull opened this issue · 1 comments

As mentioned here, bytemuck's public API is very quickly becoming very unwieldy. Here's a screenshot of the current docs.rs main page. This doesn't include the functions in the allocation and checked modules.

Screenshot_2022-09-01_21-01-13

My proposed solution is as follows:

Since bytemuck is essentially just a wrapper around "safe transmute", I propose that the current set of functions is replaced with a try_cast function and a cast function, that operates like this:

pub fn try_cast<Out, In: TransmutableTo<Out>>(input: In) -> Result<Out, In::Error> {
    if let Err(e) = input.can_transmute() {
        Err(e)
    } else {
        Ok(unsafe { transmute!(input) })
    }
}

pub fn cast<Out, In: TransmutableTo<Out>>(input: In) -> Out {
    try_cast(input).expect("Failed to transmute value")
}

pub unsafe trait TransmutableTo<T: Copy> : Copy {
    type Error;

    fn can_transmute(&self) -> Result<(), Self::Error>;
}

Where try_cast is basically just a wrapper around the transmute! macro.

The individual functions (i.e. cast_ref or cast_slice) could be replaced with #[repr(transparent)] helper structs. For instance, to emulate the current behavior of cast, there would be a Raw structure, implemented like so:

pub struct Raw<T>(pub T);

unsafe impl<In: NoUninit, Out: AnyBitPattern> TransmutableTo<Out> for Raw<In> {
    type Error = PodCastError;

    fn can_transmute(&self) -> Result<(), Self::Error> {
        // do the checks on size/alignment/et al
    }
}

// in user code
let a: u16 = bytemuck::cast(bytemuck::Raw(-3i16));

Then, you could implement cast_ref like so:

pub struct Ref<'a, T>(pub &'a T);

unsafe impl<'a, In: NoUninit, Out: AnyBitPattern> TransmutableTo<&'a Out> for Raw<'a In> {
    type Error = PodCastError;

    fn can_transmute(&self) -> Result<(), Self::Error> {
        // do the checks on size/alignment/et al
    }
}

let a: &u16 = bytemuck::cast(bytemuck::Raw(&-3i16));

And so on and so forth. I also imagine you could then implement Box and all through combinations of some kind.

I haven't thought this through all the way, though, so someone more clever than I am could probably come up with something better.

I'm not keen on the idea that someone has to wrap their data in some extra wrapper type we provide them for the functions to work.