uninit-read 0.1.1

A marker trait and utilities for safe, high-performance reads into uninitialized buffers.
Documentation
//! A marker trait and utilities for safe, high-performance reads into uninitialized buffers.
//!
//! ## The Problem
//!
//! The standard I/O traits, `std::io::Read` and `futures::io::AsyncRead`, require a `&mut [u8]` buffer. By Rust's safety
//! rules, this slice must be fully initialized. In performance-critical applications, the cost of zeroing a large buffer
//! before a read can be significant.
//!
//! The common workaround is to create an uninitialized buffer and `unsafe`ly cast it to `&mut [u8]`:
//!
//! ```rust
//! use std::mem::MaybeUninit;
//!
//! let mut uninit_buf = MaybeUninit::<[u8; 8192] >::uninit();
//!
//! // This is potentially UNDEFINED BEHAVIOR!
//! let buf_slice = unsafe { uninit_buf.assume_init_mut() };
//! // reader.read(buf_slice)?;
//! ```
//!
//! This is dangerous because the `Read` contract does not forbid the implementation from reading from the buffer before
//! writing to it. While most readers don't, the caller is relying on an unverified implementation detail.
//!
//! ## A Solution: `UninitRead`
//!
//! This crate provides a single, `unsafe` marker trait that formalizes this contract:
//!
//! ```rust
//! pub unsafe trait UninitRead {}
//! ```
//!
//! By implementing `UninitRead` for a reader type, an author makes a **guarantee**:
//!
//! > The reader implementation will not read from any part of the provided buffer that has not been written to *by the
//! implementation itself within the same call*. It must treat the buffer as if it were completely uninitialized on entry.
//!
//! This contract makes it sound for callers to use an uninitialized buffer with any reader that implements `UninitRead`.
#[cfg(feature = "exts")]
mod exts;
#[cfg(all(feature = "futures-lite", feature = "exts"))]
pub use exts::async_read_ext::UninitAsyncReadExt;
#[cfg(feature = "exts")]
pub use exts::sync_read_ext::UninitSyncReadExt;

#[cfg(feature = "impls")]
pub mod impls;

#[cfg(feature = "reflection")]
mod reflection;

#[cfg(feature = "assume-uninit-read")]
mod assume_uninit_read;
#[cfg(feature = "assume-uninit-read")]
pub use assume_uninit_read::AssumeUninitRead;

#[cfg(feature = "reflection")]
pub use reflection::is_uninit_read::is_uninit_read;

#[allow(rustdoc::broken_intra_doc_links)]
/// A marker trait for readers that are safe to use with uninitialized buffers.
///
/// The standard I/O traits, [`std::io::Read`] and [`futures::io::AsyncRead`],
/// pass a `&mut [u8]` buffer to their read methods. Rust's safety rules require
/// that any `&mut [u8]` be fully initialized. This forces users to zero-out
/// buffers before passing them to a read function, which can be a performance
/// penalty in hot code paths or high-performance workloads.
///
/// This trait provides a formal contract to address this. By implementing
/// `UninitRead`, a type guarantees that its I/O methods will not read from the
/// provided buffer before writing to it, making it safe for callers to pass a
/// buffer that contains uninitialized memory.
///
/// # Safety
///
/// This trait is `unsafe` because the compiler cannot verify the guarantee it
/// provides. The implementor of this trait **must guarantee** that their
/// implementation of `Read::read()` and/or `AsyncRead::poll_read()` will not
/// read from any part of the provided buffer that has not been written to
/// by the implementation itself.
///
/// The implementation must treat the buffer as if it were completely
/// uninitialized at the start of the call. It is permissible to read from
/// portions of the buffer that have been written to *within the same call*
/// to a read method (e.g., for an in-place decryption routine).
///
/// Violating this contract by reading from a part of the buffer that has not
/// yet been written to will result in undefined behavior when a consumer of
/// this trait passes an uninitialized buffer.
pub unsafe trait UninitRead {}

#[cfg(test)]
mod tests {
    use crate::AssumeUninitRead;
    use futures_lite::io::Empty;
    use std::mem::MaybeUninit;

    #[tokio::test]
    async fn read_ext_test_async() -> std::io::Result<()> {
        use crate::UninitAsyncReadExt;
        let mut reader: &[u8] = b"hello world";
        let mut buf = [MaybeUninit::uninit(); 5];

        // Read into uninitialized buffer without zeroing it first
        let data = reader.read_uninit(&mut buf).await?;
        assert_eq!(data, b"hello");
        Ok(())
    }

    #[test]
    fn read_ext_test_sync() -> std::io::Result<()> {
        use crate::UninitSyncReadExt;
        let mut reader: &[u8] = b"hello world";
        let mut buf = [MaybeUninit::uninit(); 5];

        // Read into uninitialized buffer without zeroing it first
        let data = reader.read_uninit(&mut buf)?;
        assert_eq!(data, b"hello");
        Ok(())
    }

    #[test]
    fn is_uninit_read() {
        assert!(super::is_uninit_read::<Empty>());
        assert!(!super::is_uninit_read::<()>());
        assert!(super::is_uninit_read::<AssumeUninitRead<()>>());
    }
}