danielhenrymantilla / rust-uninit

Read trait fixed to soundly work with uninitalized memory
MIT License
25 stars 2 forks source link

Consider extending functionality for `T: ?Sized` #9

Closed kupiakos closed 10 months ago

kupiakos commented 1 year ago

I want to write a "read from flash" function that takes an Out<'_, T> where T: FromBytes + ?Sized. FromBytes lets us describe that a type is constructible from any bit pattern. They all defer down to a common "read into raw slice" function.

In other words:

// I already have:
fn read_into(page: Page, offset: usize, start: *mut u8, size: usize) {}

// I want to write this:
pub fn read<T: FromBytes + ?Sized>(
    page: Page,
    offset: usize,
    read_into: Out<'_, T>,
) -> Result<&'_ mut T> {
    let out_bytes: Out<[u8]> = out_into_bytes(read_into.reborrow());
    read_inner(page, offset, out_bytes.as_mut_ptr(), out_bytes.len())?;
    // SAFETY: `read_into` has been initialized as required by the Read syscall
    Ok(unsafe { read_into.assume_init() })
}

// Which ideally means being able to write this:
fn out_into_bytes<T: FromBytes + ?Sized>(mut out: Out<'_, T>) -> Out<'_, [u8]> {}

There are two issues with this approach. The first is that I can't write that Out<'_, T> to Out<'_, [u8]> conversion function generically since pretty much all of the useful functions for Out are gated on T: ?Sized or T = [U]. I can't get the pointer and I can't get the size of the pointee. The second is that I can't assume_init after I know the data has been read.

I'd like if I all of these functions worked for T: ?Sized:

My current workaround, which is fine enough, is to have a read which always takes an Out<[u8]> and a Storage::read_a which is takes an Out<T> with T: Sized. We haven't had the need to need to read slices of [T] yet.

This would all be so much easier if MaybeUninit<T> supported T: ?Sized!

danielhenrymantilla commented 1 year ago

This would all be so much easier if MaybeUninit supported T: ?Sized!

Yeah 😔


And even more general API, a bit overkill for your need So, to clarify, you'd like to have something like: ```rs #![feature(arbitrary_self_types)] use { ::core::{ mem::MaybeUninit, }, ::uninit::{ prelude::*, }, ::zerocopy::{ FromBytes, }, }; unsafe // Safety: pointer must be non-null, and the total byte size must not overflow `isize`. fn raw_slice_byte_len(p: *mut [T]) -> usize { ::core::ops::Mul::mul( // cannot overflow (&*(p as *const [()])).len(), // trick to get the `.len` field of a non-null raw slice pointer; ::core::mem::size_of::(), ) } pub trait AsOutBytes : FromBytes { // Safety: the pointer must be a `&mut MaybeUninit` that has just been raw-ified. // That is, it must point to a valid `MaybeUninit` allocation, while being // well-aligned, non-null, and `&mut`-non-aliased. unsafe fn dyn_as_out_bytes<'r>(self: *mut Self) -> Out<'r, [u8]> ; } impl AsOutBytes for T { unsafe fn dyn_as_out_bytes<'r>(self: *mut Self) -> Out<'r, [u8]> { let len = ::core::mem::size_of::(); let p: &mut [_] = ::core::slice::from_raw_parts_mut( self.cast::>(), len, ); p.as_out() } } impl AsOutBytes for [T] { unsafe fn dyn_as_out_bytes<'r>(self: *mut Self) -> Out<'r, [u8]> { let len = raw_slice_byte_len(self); let p: &mut [_] = ::core::slice::from_raw_parts_mut( self.cast::>(), len, ); p.as_out() } } fn as_out_bytes(p: Out<'_, T>) -> Out<'_, [u8]> { unsafe { ::core::mem::transmute::<_, *mut T>(p) .dyn_as_out_bytes() } } ``` - [Demo](https://www.rustexplorer.com/b/aghd0n) ### Remarks - `Out` is missing `{from,into}_raw()` low-level operations (I've had to polyfill them using `unsafe` in that demo); - This requires `arbitrary_self_types` to be able to use `*mut Self` receivers, so as to support `dyn Trait`s. If "arbitrary slices" suffice to your use case, then we can tweak the trait definition to fully start with a `this: *mut Self`, or `this: Out<'_, Self>`, and thus remain usable on stable. ___ ## EDIT Actually, re-reading your post,

you basically would like to be able to write some T : ?Sized genericity that would allow you to cover both T : Sized and [u8] equally, right? That seems a smaller API addition than the one I've just shown in that drop-down section, and indeed should be possible. The key idea would be to have a IsSizedOrByteSlice trait, and then implement it for <T> and for [u8], right?

kupiakos commented 1 year ago

I was more thinking along the lines of unifying methods between Out<T: Sized> and Out<[T]> through a trait. I have a draft in https://github.com/danielhenrymantilla/rust-uninit/pull/10. What do you think?