uninit_read/
lib.rs

1//! A marker trait and utilities for safe, high-performance reads into uninitialized buffers.
2//!
3//! ## The Problem
4//!
5//! The standard I/O traits, `std::io::Read` and `futures::io::AsyncRead`, require a `&mut [u8]` buffer. By Rust's safety
6//! rules, this slice must be fully initialized. In performance-critical applications, the cost of zeroing a large buffer
7//! before a read can be significant.
8//!
9//! The common workaround is to create an uninitialized buffer and `unsafe`ly cast it to `&mut [u8]`:
10//!
11//! ```rust
12//! use std::mem::MaybeUninit;
13//!
14//! let mut uninit_buf = MaybeUninit::<[u8; 8192] >::uninit();
15//!
16//! // This is potentially UNDEFINED BEHAVIOR!
17//! let buf_slice = unsafe { uninit_buf.assume_init_mut() };
18//! // reader.read(buf_slice)?;
19//! ```
20//!
21//! This is dangerous because the `Read` contract does not forbid the implementation from reading from the buffer before
22//! writing to it. While most readers don't, the caller is relying on an unverified implementation detail.
23//!
24//! ## A Solution: `UninitRead`
25//!
26//! This crate provides a single, `unsafe` marker trait that formalizes this contract:
27//!
28//! ```rust
29//! pub unsafe trait UninitRead {}
30//! ```
31//!
32//! By implementing `UninitRead` for a reader type, an author makes a **guarantee**:
33//!
34//! > The reader implementation will not read from any part of the provided buffer that has not been written to *by the
35//! implementation itself within the same call*. It must treat the buffer as if it were completely uninitialized on entry.
36//!
37//! This contract makes it sound for callers to use an uninitialized buffer with any reader that implements `UninitRead`.
38#[cfg(feature = "exts")]
39mod exts;
40#[cfg(all(feature = "futures-lite", feature = "exts"))]
41pub use exts::async_read_ext::UninitAsyncReadExt;
42#[cfg(feature = "exts")]
43pub use exts::sync_read_ext::UninitSyncReadExt;
44
45#[cfg(feature = "impls")]
46pub mod impls;
47
48#[cfg(feature = "reflection")]
49mod reflection;
50
51#[cfg(feature = "assume-uninit-read")]
52mod assume_uninit_read;
53#[cfg(feature = "assume-uninit-read")]
54pub use assume_uninit_read::AssumeUninitRead;
55
56#[cfg(feature = "reflection")]
57pub use reflection::is_uninit_read::is_uninit_read;
58
59#[allow(rustdoc::broken_intra_doc_links)]
60/// A marker trait for readers that are safe to use with uninitialized buffers.
61///
62/// The standard I/O traits, [`std::io::Read`] and [`futures::io::AsyncRead`],
63/// pass a `&mut [u8]` buffer to their read methods. Rust's safety rules require
64/// that any `&mut [u8]` be fully initialized. This forces users to zero-out
65/// buffers before passing them to a read function, which can be a performance
66/// penalty in hot code paths or high-performance workloads.
67///
68/// This trait provides a formal contract to address this. By implementing
69/// `UninitRead`, a type guarantees that its I/O methods will not read from the
70/// provided buffer before writing to it, making it safe for callers to pass a
71/// buffer that contains uninitialized memory.
72///
73/// # Safety
74///
75/// This trait is `unsafe` because the compiler cannot verify the guarantee it
76/// provides. The implementor of this trait **must guarantee** that their
77/// implementation of `Read::read()` and/or `AsyncRead::poll_read()` will not
78/// read from any part of the provided buffer that has not been written to
79/// by the implementation itself.
80///
81/// The implementation must treat the buffer as if it were completely
82/// uninitialized at the start of the call. It is permissible to read from
83/// portions of the buffer that have been written to *within the same call*
84/// to a read method (e.g., for an in-place decryption routine).
85///
86/// Violating this contract by reading from a part of the buffer that has not
87/// yet been written to will result in undefined behavior when a consumer of
88/// this trait passes an uninitialized buffer.
89pub unsafe trait UninitRead {}
90
91#[cfg(test)]
92mod tests {
93    use crate::AssumeUninitRead;
94    use futures_lite::io::Empty;
95    use std::mem::MaybeUninit;
96
97    #[tokio::test]
98    async fn read_ext_test_async() -> std::io::Result<()> {
99        use crate::UninitAsyncReadExt;
100        let mut reader: &[u8] = b"hello world";
101        let mut buf = [MaybeUninit::uninit(); 5];
102
103        // Read into uninitialized buffer without zeroing it first
104        let data = reader.read_uninit(&mut buf).await?;
105        assert_eq!(data, b"hello");
106        Ok(())
107    }
108
109    #[test]
110    fn read_ext_test_sync() -> std::io::Result<()> {
111        use crate::UninitSyncReadExt;
112        let mut reader: &[u8] = b"hello world";
113        let mut buf = [MaybeUninit::uninit(); 5];
114
115        // Read into uninitialized buffer without zeroing it first
116        let data = reader.read_uninit(&mut buf)?;
117        assert_eq!(data, b"hello");
118        Ok(())
119    }
120
121    #[test]
122    fn is_uninit_read() {
123        assert!(super::is_uninit_read::<Empty>());
124        assert!(!super::is_uninit_read::<()>());
125        assert!(super::is_uninit_read::<AssumeUninitRead<()>>());
126    }
127}