Skip to content

Add FromBytes::read_from_io and IntoBytes::write_to_io #2016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 109 additions & 4 deletions src/lib.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Tests

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, BecauseExclusive};

#[cfg(any(feature = "alloc", test))]
Expand Down Expand Up @@ -3083,10 +3086,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 @@ -4525,6 +4525,48 @@ pub unsafe trait FromBytes: FromZeros {
}
}

/// 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() })
}

#[deprecated(since = "0.8.0", note = "renamed to `FromBytes::ref_from_bytes`")]
#[doc(hidden)]
#[must_use = "has no side effects"]
Expand Down Expand Up @@ -5188,6 +5230,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())
}

#[deprecated(since = "0.8.0", note = "`IntoBytes::as_bytes_mut` was renamed to `as_mut_bytes`")]
#[doc(hidden)]
#[inline]
Expand Down Expand Up @@ -5950,6 +6041,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