Skip to content

Commit

Permalink
Add FromBytes::read_from_io and IntoBytes::write_to_io (#2016) (#…
Browse files Browse the repository at this point in the history
…2046)

Makes progress on #158.


gherrit-pr-id: I9253d6be7407d8d8679ee355e4e71cd9b15b9ff7

Co-authored-by: Jack Wrenn <[email protected]>
  • Loading branch information
joshlf and jswrenn authored Nov 11, 2024
1 parent d3157cc commit 6c2a249
Showing 1 changed file with 109 additions and 4 deletions.
113 changes: 109 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ use core::{
slice,
};

#[cfg(feature = "std")]
use std::io;

use crate::pointer::invariant::{self, BecauseExclusive};

#[cfg(any(feature = "alloc", test))]
Expand Down Expand Up @@ -3068,10 +3071,7 @@ pub unsafe trait FromZeros: TryFromBytes {
// "exposed" provenance, and thus Rust may have to assume that this
// may consume provenance from any pointer whose provenance has been
// exposed.
#[allow(fuzzy_provenance_casts)]
unsafe {
NonNull::new_unchecked(dangling)
}
unsafe { NonNull::new_unchecked(dangling) }
};

let ptr = Self::raw_from_ptr_len(ptr, count);
Expand Down Expand Up @@ -4495,6 +4495,48 @@ pub unsafe trait FromBytes: FromZeros {
Err(CastError::Validity(i)) => match i {},
}
}

/// Reads a copy of `self` from an `io::Read`.
///
/// This is useful for interfacing with operating system byte sinks (files,
/// sockets, etc.).
///
/// # Examples
///
/// ```no_run
/// use zerocopy::{byteorder::big_endian::*, FromBytes};
/// use std::fs::File;
/// # use zerocopy_derive::*;
///
/// #[derive(FromBytes)]
/// #[repr(C)]
/// struct BitmapFileHeader {
/// signature: [u8; 2],
/// size: U32,
/// reserved: U64,
/// offset: U64,
/// }
///
/// let mut file = File::open("image.bin").unwrap();
/// let header = BitmapFileHeader::read_from_io(&mut file).unwrap();
/// ```
#[cfg(feature = "std")]
#[inline(always)]
fn read_from_io<R>(mut src: R) -> io::Result<Self>
where
Self: Sized,
R: io::Read,
{
let mut buf = MaybeUninit::<Self>::zeroed();
let ptr = Ptr::from_mut(&mut buf);
// SAFETY: `buf` consists entirely of initialized, zeroed bytes.
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
let ptr = ptr.as_bytes::<BecauseExclusive>();
src.read_exact(ptr.as_mut())?;
// SAFETY: `buf` entirely consists of initialized bytes, and `Self` is
// `FromBytes`.
Ok(unsafe { buf.assume_init() })
}
}

/// Interprets the given affix of the given bytes as a `&Self`.
Expand Down Expand Up @@ -5081,6 +5123,55 @@ pub unsafe trait IntoBytes {
}
Ok(())
}

/// Writes a copy of `self` to an `io::Write`.
///
/// This is a shorthand for `dst.write_all(self.as_bytes())`, and is useful
/// for interfacing with operating system byte sinks (files, sockets, etc.).
///
/// # Examples
///
/// ```no_run
/// use zerocopy::{byteorder::big_endian::U16, FromBytes, IntoBytes};
/// use std::fs::File;
/// # use zerocopy_derive::*;
///
/// #[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
/// #[repr(C, packed)]
/// struct GrayscaleImage {
/// height: U16,
/// width: U16,
/// pixels: [U16],
/// }
///
/// let image = GrayscaleImage::ref_from_bytes(&[0, 0, 0, 0][..]).unwrap();
/// let mut file = File::create("image.bin").unwrap();
/// image.write_to_io(&mut file).unwrap();
/// ```
///
/// If the write fails, `write_to_io` returns `Err` and a partial write may
/// have occured; e.g.:
///
/// ```
/// # use zerocopy::IntoBytes;
///
/// let src = u128::MAX;
/// let mut dst = [0u8; 2];
///
/// let write_result = src.write_to_io(&mut dst[..]);
///
/// assert!(write_result.is_err());
/// assert_eq!(dst, [255, 255]);
/// ```
#[cfg(feature = "std")]
#[inline(always)]
fn write_to_io<W>(&self, mut dst: W) -> io::Result<()>
where
Self: Immutable,
W: io::Write,
{
dst.write_all(self.as_bytes())
}
}

/// Analyzes whether a type is [`Unaligned`].
Expand Down Expand Up @@ -5795,6 +5886,20 @@ mod tests {
assert_eq!(bytes, want);
}

#[test]
#[cfg(feature = "std")]
fn test_read_write_io() {
let mut long_buffer = [0, 0, 0, 0];
assert!(matches!(u16::MAX.write_to_io(&mut long_buffer[..]), Ok(())));
assert_eq!(long_buffer, [255, 255, 0, 0]);
assert!(matches!(u16::read_from_io(&long_buffer[..]), Ok(u16::MAX)));

let mut short_buffer = [0, 0];
assert!(u32::MAX.write_to_io(&mut short_buffer[..]).is_err());
assert_eq!(short_buffer, [255, 255]);
assert!(u32::read_from_io(&short_buffer[..]).is_err());
}

#[test]
fn test_try_from_bytes_try_read_from() {
assert_eq!(<bool as TryFromBytes>::try_read_from_bytes(&[0]), Ok(false));
Expand Down

0 comments on commit 6c2a249

Please sign in to comment.