diff --git a/crates/musli-zerocopy/src/buf/load.rs b/crates/musli-zerocopy/src/buf/load.rs index 3ac6c826e..1f04df52c 100644 --- a/crates/musli-zerocopy/src/buf/load.rs +++ b/crates/musli-zerocopy/src/buf/load.rs @@ -58,7 +58,7 @@ where } } -impl Load for Ref { +impl Load for Ref { type Target = str; #[inline] @@ -88,7 +88,7 @@ where } } -impl LoadMut for Ref { +impl LoadMut for Ref { #[inline] fn load_mut<'buf>(&self, buf: &'buf mut Buf) -> Result<&'buf mut Self::Target, Error> { buf.load_unsized_mut(*self) diff --git a/crates/musli-zerocopy/src/buf/owned_buf.rs b/crates/musli-zerocopy/src/buf/owned_buf.rs index a02117a47..f7b21539e 100644 --- a/crates/musli-zerocopy/src/buf/owned_buf.rs +++ b/crates/musli-zerocopy/src/buf/owned_buf.rs @@ -200,8 +200,7 @@ impl OwnedBuf { /// # Examples /// /// ``` - /// use musli_zerocopy::OwnedBuf; - /// use musli_zerocopy::endian::LittleEndian; + /// use musli_zerocopy::{LittleEndian, OwnedBuf}; /// /// let mut buf = OwnedBuf::with_capacity(1024) /// .with_byte_order::(); diff --git a/crates/musli-zerocopy/src/endian.rs b/crates/musli-zerocopy/src/endian.rs index 379953066..a7bb2f6c6 100644 --- a/crates/musli-zerocopy/src/endian.rs +++ b/crates/musli-zerocopy/src/endian.rs @@ -1,6 +1,6 @@ //! Marker types which define a [`ByteOrder`] to use. -/// Default [`Endianness`]. +/// Default [`ByteOrder`]. pub type DefaultEndian = NativeEndian; /// Alias for the native endian [`ByteOrder`]. diff --git a/crates/musli-zerocopy/src/lib.rs b/crates/musli-zerocopy/src/lib.rs index 5150cd1d1..f14a9a672 100644 --- a/crates/musli-zerocopy/src/lib.rs +++ b/crates/musli-zerocopy/src/lib.rs @@ -61,6 +61,14 @@ //! accessing. So for random access we only need to validate the parts that //! are being accessed. //! +//! Overview: +//! * [Why should I consider `musli-zerocopy` over X?](#why-should-i-consider-musli-zerocopy-over-x) +//! * [Guide](#guide) +//! * [Reading data](#reading-data) +//! * [Writing data at offset zero](#writing-data-at-offset-zero) +//! * [Portability](#portability) +//! * [Limits](#limits) +//! //!
//! //! ## Why should I consider `musli-zerocopy` over X? @@ -306,6 +314,95 @@ //! //!
//! +//! ## Portability +//! +//! By default archives will use the native [`ByteOrder`]. In order to construct +//! and load a portable archive, the byte order in use has to be explicitly +//! specified. +//! +//! This is done by specifying the byte order in use during buffer construction +//! and expliclty setting the `E` parameter in types which received it such as +//! [`Ref`]. +//! +//! We can start of by defining a fully `Portable` archive structure, which +//! received both size and [`ByteOrder`]. Note that it could also just +//! explicitly specify a desired byte order but doing it like this makes it +//! maximally flexible as an example: +//! +//! ``` +//! use musli_zerocopy::{Size, ByteOrder, Ref, Ordered, ZeroCopy}; +//! +//! #[derive(ZeroCopy)] +//! #[repr(C)] +//! struct Archive where O: Size, E: ByteOrder { +//! string: Ref, +//! number: Ordered, +//! } +//! ``` +//! +//! Building a buffer out of the structure is fairly straight forward, +//! [`OwnedBuf`] has the [`with_byte_order::()`] method which allows us to +//! specify a "sticky" [`ByteOrder`] to use in types which interact with it +//! during construction: +//! +//! ``` +//! use musli_zerocopy::{BigEndian, LittleEndian, Ordered, OwnedBuf}; +//! # use musli_zerocopy::{Size, ByteOrder, Ref, ZeroCopy}; +//! # #[derive(ZeroCopy)] +//! # #[repr(C)] +//! # struct Archive where O: Size, E: ByteOrder { +//! # string: Ref, +//! # number: Ordered +//! # } +//! +//! let mut buf = OwnedBuf::new().with_byte_order::(); +//! +//! let first = buf.store(&Ordered::le(42u16)); +//! let portable = Archive { +//! string: buf.store_unsized("Hello World!"), +//! number: Ordered::new(10), +//! }; +//! let portable = buf.store(&portable); +//! +//! assert_eq!(&buf[..], &[ +//! 42, 0, // 42u16 +//! 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, // "Hello World!" +//! 0, 0, // padding +//! 2, 0, 0, 0, 12, 0, 0, 0, // Ref +//! 10, 0, 0, 0 // 10u32 +//! ]); +//! +//! let portable = buf.load(portable)?; +//! # assert_eq!(buf.load(first)?.to_ne(), 42); +//! # assert_eq!(buf.load(portable.string)?, "Hello World!"); +//! # assert_eq!(portable.number.to_ne(), 10); +//! +//! let mut buf = OwnedBuf::new().with_byte_order::(); +//! +//! let first = buf.store(&Ordered::be(42u16)); +//! let portable = Archive { +//! string: buf.store_unsized("Hello World!"), +//! number: Ordered::new(10), +//! }; +//! let portable = buf.store(&portable); +//! +//! assert_eq!(&buf[..], &[ +//! 0, 42, // 42u16 +//! 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, // "Hello World!" +//! 0, 0, // padding +//! 0, 0, 0, 2, 0, 0, 0, 12, // Ref +//! 0, 0, 0, 10 // 10u32 +//! ]); +//! +//! let portable = buf.load(portable)?; +//! # assert_eq!(buf.load(first)?.to_ne(), 42); +//! # assert_eq!(buf.load(portable.string)?, "Hello World!"); +//! # assert_eq!(portable.number.to_ne(), 10); +//! # Ok::<_, musli_zerocopy::Error>(()) +//! ``` +//! +//!
+//! //! ## Limits //! //! Offset, the size of unsized values, and slice lengths are all limited to @@ -398,20 +495,23 @@ //! //!
//! -//! [`swiss`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/swiss/index.html -//! [`phf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/phf/index.html -//! [`phf` crate]: https://docs.rs/phf +//! [`aligned_buf(bytes, align)`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html +//! [`ByteOrder`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/trait.ByteOrder.html //! [`hashbrown` crate]: https://docs.rs/phf +//! [`OwnedBuf::with_size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_size +//! [`OwnedBuf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html +//! [`with_byte_order::()`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_byte_order +//! [`phf` crate]: https://docs.rs/phf +//! [`phf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/phf/index.html +//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html +//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html +//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html //! [`requested()`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/struct.OwnedBuf.html#method.requested +//! [`Size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html +//! [`swiss`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/swiss/index.html //! [`ZeroCopy`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/trait.ZeroCopy.html //! [derive]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/derive.ZeroCopy.html -//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html //! [ref-u32]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html -//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html -//! [`OwnedBuf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html -//! [`OwnedBuf::with_size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_size -//! [`Size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html -//! [`aligned_buf(bytes, align)`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html #![no_std] #![allow(clippy::module_inception)] @@ -451,12 +551,13 @@ pub mod phf; pub mod swiss; #[doc(inline)] -pub use self::pointer::Ref; +pub use self::pointer::{Ref, Size}; pub mod pointer; mod ordered; pub use self::ordered::Ordered; +pub use self::endian::{BigEndian, ByteOrder, LittleEndian}; pub mod endian; /// Derive macro to implement [`ZeroCopy`]. diff --git a/crates/musli-zerocopy/src/ordered.rs b/crates/musli-zerocopy/src/ordered.rs index b7d17a879..7184eb07a 100644 --- a/crates/musli-zerocopy/src/ordered.rs +++ b/crates/musli-zerocopy/src/ordered.rs @@ -1,8 +1,7 @@ use core::marker::PhantomData; use core::{any, fmt}; -use crate::buf::{Padder, Validator}; -use crate::endian::ByteOrder; +use crate::endian::{BigEndian, ByteOrder, LittleEndian}; use crate::ZeroCopy; /// A value capable of enforcing a custom [`ByteOrder`]. @@ -10,28 +9,51 @@ use crate::ZeroCopy; /// This can be used to store values in a zero-copy container in a portable /// manner, which is especially important to transfer types such as `char` which /// have a limited supported bit-pattern. -/// -/// # Examples -/// -/// ``` -/// use musli_zerocopy::{Ordered, ZeroCopy}; -/// use musli_zerocopy::endian::{BigEndian, LittleEndian}; -/// -/// let mut a: Ordered<_, BigEndian> = Ordered::new('a' as u32); -/// let mut b: Ordered<_, LittleEndian> = Ordered::new('a' as u32); -/// -/// assert_eq!(a.into_value(), 'a' as u32); -/// assert_eq!(a.to_bytes(), &[0, 0, 0, 97]); -/// -/// assert_eq!(b.into_value(), 'a' as u32); -/// assert_eq!(b.to_bytes(), &[97, 0, 0, 0]); -/// ``` +#[derive(ZeroCopy)] +#[zero_copy(crate, swap_bytes, bounds = {T: ZeroCopy})] #[repr(transparent)] pub struct Ordered { value: T, + #[zero_copy(ignore)] _marker: PhantomData, } +impl Ordered { + /// Construct new value wrapper with [`LittleEndian`] [`ByteOrder`]. + /// + /// # Examples + /// + /// ``` + /// use musli_zerocopy::Ordered; + /// + /// let value = Ordered::le(42u32); + /// assert_eq!(value.to_ne(), 42); + /// assert_eq!(value.to_raw(), 42u32.to_le()); + /// ``` + #[inline] + pub fn le(value: T) -> Self { + Self::new(value) + } +} + +impl Ordered { + /// Construct new value wrapper with [`BigEndian`] [`ByteOrder`]. + /// + /// # Examples + /// + /// ``` + /// use musli_zerocopy::Ordered; + /// + /// let value = Ordered::be(42u32); + /// assert_eq!(value.to_ne(), 42); + /// assert_eq!(value.to_raw(), 42u32.to_be()); + /// ``` + #[inline] + pub fn be(value: T) -> Self { + Self::new(value) + } +} + impl Ordered { /// Construct new value wrapper with the specified [`ByteOrder`]. /// @@ -41,11 +63,25 @@ impl Ordered { /// byte-ordered. /// /// ```should_panic - /// use musli_zerocopy::Ordered; - /// use musli_zerocopy::endian::LittleEndian; + /// use musli_zerocopy::{LittleEndian, Ordered}; /// /// let _: Ordered<_, LittleEndian> = Ordered::new('a'); /// ``` + /// + /// # Examples + /// + /// ``` + /// use musli_zerocopy::{BigEndian, LittleEndian, Ordered, ZeroCopy}; + /// + /// let mut a: Ordered<_, BigEndian> = Ordered::new('a' as u32); + /// let mut b: Ordered<_, LittleEndian> = Ordered::new('a' as u32); + /// + /// assert_eq!(a.to_ne(), 'a' as u32); + /// assert_eq!(a.to_bytes(), &[0, 0, 0, 97]); + /// + /// assert_eq!(b.to_ne(), 'a' as u32); + /// assert_eq!(b.to_bytes(), &[97, 0, 0, 0]); + /// ``` #[inline] pub fn new(value: T) -> Self { assert!( @@ -60,37 +96,36 @@ impl Ordered { } } - /// Get interior value with the desired native alignment. + /// Get interior value in native [`ByteOrder`]. + /// + /// # Examples + /// + /// ``` + /// use musli_zerocopy::Ordered; + /// + /// let value = Ordered::le(42u32); + /// assert_eq!(value.to_ne(), 42); + /// assert_eq!(value.to_raw(), 42u32.to_le()); + /// ``` #[inline] - pub fn into_value(self) -> T { + pub fn to_ne(self) -> T { T::swap_bytes::(self.value) } -} - -unsafe impl ZeroCopy for Ordered -where - T: ZeroCopy, - E: ByteOrder, -{ - const ANY_BITS: bool = T::ANY_BITS; - const PADDED: bool = T::PADDED; - const CAN_SWAP_BYTES: bool = true; - - #[inline] - unsafe fn pad(padder: &mut Padder<'_, Self>) { - T::pad(padder.transparent()) - } - - #[inline] - unsafe fn validate(validator: &mut Validator<'_, Self>) -> Result<(), crate::Error> { - T::validate(validator.transparent()) - } + /// Get the raw inner value. + /// + /// # Examples + /// + /// ``` + /// use musli_zerocopy::Ordered; + /// + /// let value = Ordered::le(42u32); + /// assert_eq!(value.to_ne(), 42); + /// assert_eq!(value.to_raw(), 42u32.to_le()); + /// ``` #[inline] - fn swap_bytes<__E: ByteOrder>(self) -> Self { - // NB: This is a no-op, since the byte-ordering is recorded at the type - // level and doesn't change no matter what ordering is requested. - self + pub fn to_raw(self) -> T { + self.value } } diff --git a/crates/musli-zerocopy/src/phf/map.rs b/crates/musli-zerocopy/src/phf/map.rs index d7cdf4a9a..22c9c9db0 100644 --- a/crates/musli-zerocopy/src/phf/map.rs +++ b/crates/musli-zerocopy/src/phf/map.rs @@ -176,7 +176,7 @@ where #[inline] fn bind(self, buf: &Buf) -> Result, Error> { Ok(Map { - key: self.key.into_value(), + key: self.key.to_ne(), entries: buf.load(self.entries)?, displacements: buf.load(self.displacements)?, buf, @@ -337,7 +337,7 @@ where return Ok(None); } - let hashes = crate::phf::hashing::hash(buf, key, &self.key.into_value())?; + let hashes = crate::phf::hashing::hash(buf, key, &self.key.to_ne())?; let displacements = |index| match self.displacements.get(index) { Some(entry) => Ok(Some(buf.load(entry)?)), diff --git a/crates/musli-zerocopy/src/phf/set.rs b/crates/musli-zerocopy/src/phf/set.rs index 8a878ed11..536626109 100644 --- a/crates/musli-zerocopy/src/phf/set.rs +++ b/crates/musli-zerocopy/src/phf/set.rs @@ -105,7 +105,7 @@ where #[inline] fn bind(self, buf: &Buf) -> Result, Error> { Ok(Set { - key: self.key.into_value(), + key: self.key.to_ne(), entries: buf.load(self.entries)?, displacements: buf.load(self.displacements)?, buf, @@ -205,7 +205,7 @@ where return Ok(false); } - let hashes = crate::phf::hashing::hash(buf, key, &self.key.into_value())?; + let hashes = crate::phf::hashing::hash(buf, key, &self.key.to_ne())?; let displacements = |index| match self.displacements.get(index) { Some(entry) => Ok(Some(buf.load(entry)?)), diff --git a/crates/musli-zerocopy/src/pointer/ref.rs b/crates/musli-zerocopy/src/pointer/ref.rs index cd5978c2d..57a48741a 100644 --- a/crates/musli-zerocopy/src/pointer/ref.rs +++ b/crates/musli-zerocopy/src/pointer/ref.rs @@ -51,6 +51,7 @@ where impl Ref where P: Pointee, + P::Packed: ZeroCopy, { /// Construct a reference with custom metadata. /// @@ -73,10 +74,16 @@ where { assert!( O::CAN_SWAP_BYTES, - "Type `{}` cannot be byte-ordered since it would not inhabit valid types", + "Offset `{}` cannot be byte-ordered since it would not inhabit valid types", any::type_name::() ); + assert!( + P::Packed::CAN_SWAP_BYTES, + "Packed metadata `{}` cannot be byte-ordered since it would not inhabit valid types", + any::type_name::() + ); + let Some(offset) = O::try_from(offset).ok() else { panic!("Offset {offset:?} not in legal range 0-{}", O::MAX); }; @@ -87,7 +94,7 @@ where Self { offset: O::swap_bytes::(offset), - metadata, + metadata: P::Packed::swap_bytes::(metadata), _marker: PhantomData, } } diff --git a/crates/musli-zerocopy/src/pointer/size.rs b/crates/musli-zerocopy/src/pointer/size.rs index 74be7b84e..cb6295144 100644 --- a/crates/musli-zerocopy/src/pointer/size.rs +++ b/crates/musli-zerocopy/src/pointer/size.rs @@ -67,8 +67,7 @@ macro_rules! impl_size { /// # Examples /// /// ``` - /// use musli_zerocopy::pointer::Size; - /// use musli_zerocopy::endian::{BigEndian, LittleEndian}; + /// use musli_zerocopy::{BigEndian, LittleEndian, Size}; /// #[doc = concat!("let max = ", stringify!($ty), "::MAX.as_usize::();")] #[doc = concat!("let min = ", stringify!($ty), "::MIN.as_usize::();")] diff --git a/crates/musli-zerocopy/src/swiss/map.rs b/crates/musli-zerocopy/src/swiss/map.rs index 14842f4d0..227ecd7cb 100644 --- a/crates/musli-zerocopy/src/swiss/map.rs +++ b/crates/musli-zerocopy/src/swiss/map.rs @@ -198,7 +198,7 @@ where #[inline] fn bind(self, buf: &Buf) -> Result, Error> { Ok(Map { - key: self.key.into_value(), + key: self.key.to_ne(), table: self.table.bind(buf)?, buf, }) @@ -314,7 +314,7 @@ where /// ``` #[inline] pub fn len(&self) -> usize { - self.table.len.into_value() + self.table.len.to_ne() } /// Test if the map is empty. @@ -334,7 +334,7 @@ where /// ``` #[inline] pub fn is_empty(&self) -> bool { - self.table.len.into_value() == 0 + self.table.len.to_ne() == 0 } /// Test if the map contains the given `key`. @@ -375,7 +375,7 @@ where where H: Hash, { - let mut hasher = SipHasher13::new_with_keys(0, self.key.into_value()); + let mut hasher = SipHasher13::new_with_keys(0, self.key.to_ne()); value.hash(&mut hasher); hasher.finish() } @@ -500,8 +500,8 @@ where Ok(RawTable { ctrl: buf.load(self.ctrl)?, entries: buf.load(self.entries)?, - bucket_mask: self.bucket_mask.into_value(), - len: self.len.into_value(), + bucket_mask: self.bucket_mask.to_ne(), + len: self.len.to_ne(), }) } } @@ -547,7 +547,7 @@ where eq: &mut dyn FnMut(usize) -> Result, ) -> Result, Error> { let h2_hash = h2(hash); - let mut probe_seq = probe_seq(self.bucket_mask.into_value(), hash); + let mut probe_seq = probe_seq(self.bucket_mask.to_ne(), hash); let ctrl = buf.load(self.ctrl)?; @@ -566,7 +566,7 @@ where for bit in group.match_byte(h2_hash) { // This is the same as `(probe_seq.pos + bit) % self.buckets()` because the number // of buckets is a power of two, and `self.bucket_mask = self.buckets() - 1`. - let index = (probe_seq.pos + bit) & self.bucket_mask.into_value(); + let index = (probe_seq.pos + bit) & self.bucket_mask.to_ne(); if likely(eq(index)?) { return Ok(Some(index)); @@ -577,7 +577,7 @@ where return Ok(None); } - probe_seq.move_next(self.bucket_mask.into_value())?; + probe_seq.move_next(self.bucket_mask.to_ne())?; } } } diff --git a/crates/musli-zerocopy/src/traits.rs b/crates/musli-zerocopy/src/traits.rs index cb5b6f883..c9ed6d4a6 100644 --- a/crates/musli-zerocopy/src/traits.rs +++ b/crates/musli-zerocopy/src/traits.rs @@ -102,9 +102,9 @@ where /// The caller is responsible for ensuring that the pointer is valid. The /// base pointer `ptr` has to point to a region of memory that is /// initialized per `P::Metadata` requirements. Practically that means it's - /// passed a call to [`validate()`]. + /// passed a call to [`validate_unsized()`]. /// - /// [`validate()`]: Self::validate + /// [`validate_unsized()`]: Self::validate_unsized unsafe fn ptr_with_metadata(ptr: *const u8, metadata: P::Metadata) -> *const Self; /// Construct a wide mutable pointer from a pointer and its associated @@ -115,9 +115,9 @@ where /// The caller is responsible for ensuring that the pointer is valid. The /// base pointer `ptr` has to point to a region of memory that is /// initialized per `P::Metadata` requirements. Practically that means it's - /// passed a call to [`validate()`]. + /// passed a call to [`validate_unsized()`]. /// - /// [`validate()`]: Self::validate + /// [`validate_unsized()`]: Self::validate_unsized unsafe fn ptr_with_metadata_mut(ptr: *mut u8, metadata: P::Metadata) -> *mut Self; } @@ -549,8 +549,7 @@ pub unsafe trait ZeroCopy: Sized { /// composite field that is apart of that type. /// /// ``` - /// use musli_zerocopy::{Ref, ZeroCopy}; - /// use musli_zerocopy::endian::{BigEndian, LittleEndian}; + /// use musli_zerocopy::{BigEndian, LittleEndian, Ref, ZeroCopy}; /// /// #[derive(ZeroCopy)] /// #[repr(C)]