# borrowed-buf
[](https://crates.io/crates/borrowed-buf)
[](https://docs.rs/borrowed-buf)
[](https://github.com/M-Compressors/borrowed-buf/actions/workflows/ci.yml)
[](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/)
A small, fast, `no_std` port of the nightly-only
[`core::io::BorrowedBuf`](https://doc.rust-lang.org/nightly/core/io/struct.BorrowedBuf.html)
and [`BorrowedCursor`](https://doc.rust-lang.org/nightly/core/io/struct.BorrowedCursor.html),
usable on **stable Rust (MSRV 1.89)**.
The API mirrors `core::io` on nightly (including the `borrowed_buf_init` methods), so
switching to the standard library later is a matter of changing the import.
It lets you read into uninitialized memory without zeroing it first, while
tracking how much of the buffer is filled and whether the rest is initialized:
```text
[ capacity ]
[ filled | unfilled (may be initialized) ]
```
- No dependencies, `#![no_std]`; the default `std` feature adds `std::io` helpers.
- Generic over the element type: `BorrowedBuf<'a, T>` for any `T: Copy`.
- `ensure_init` initializes the buffer at most once, so a reused buffer is zeroed only once.
- Every test runs under Miri (Stacked Borrows and Tree Borrows) in CI.
## Why
Most APIs that fill a buffer (`Read::read`, decoders, codecs) take `&mut [T]`,
which must already be initialized. That leaves two options:
- **Zero it first.** `[0; N]` or `vec![0; n]` costs a full memset for every
fresh buffer, often more than the read itself.
- **Use `MaybeUninit<T>` by hand.** Then you track which prefix is written,
call `assume_init` only on that prefix, and never create a `&[T]` over
uninitialized memory. Get any step wrong and it's silent undefined behavior.
`BorrowedBuf` is a **safe wrapper around `&mut [MaybeUninit<T>]`** that does
this bookkeeping for you:
- `filled()`, `into_filled()` and `into_filled_mut()` expose only initialized
data, as plain `&[T]` / `&mut [T]`, so your code never calls `assume_init`.
- `append()` copies straight into uninitialized memory, with no zeroing.
- `ensure_init()` initializes the unfilled part at most once per buffer, and
hands back a `&mut [T]` for APIs like `Read::read`.
- A `BorrowedCursor` is append-only: a callee can't read, overwrite or
de-initialize what the caller already filled.
- `with_unfilled_buf()` hands a callee a fresh `BorrowedBuf` over the unfilled
part, so it can inspect what it wrote.
- Writing in place is still possible through a few `unsafe` methods
(`as_mut`, `advance`, `set_init`), each with one documented precondition,
so the unsafe surface is small and easy to audit.
## Example
```rust
use borrowed_buf::BorrowedBuf;
use core::mem::MaybeUninit;
let mut storage = [MaybeUninit::<u8>::uninit(); 16];
let mut buf: BorrowedBuf<'_, u8> = BorrowedBuf::from(&mut storage[..]);
// Hand out a cursor: the callee can only append, never touch what's already filled.
let mut cursor = buf.unfilled();
cursor.append(b"hello");
// Write straight into the unfilled region, then record the progress.
// SAFETY: only initialized bytes are written, and the first one is written before advancing.
unsafe {
cursor.as_mut()[0].write(b'!');
cursor.advance(1);
}
assert_eq!(buf.filled(), b"hello!");
assert!(!buf.is_init());
```
Any `Copy` element type works, e.g. decoding samples straight into uninitialized memory:
```rust
use borrowed_buf::{BorrowedBuf, BorrowedCursor};
use core::mem::MaybeUninit;
fn decode(input: &[u8], mut out: BorrowedCursor<'_, i16>) {
let n = out.capacity().min(input.len() / 2);
// SAFETY: only initialized values are written.
let slots = unsafe { out.as_mut() };
for (slot, pair) in slots.iter_mut().zip(input.chunks_exact(2)).take(n) {
slot.write(i16::from_le_bytes([pair[0], pair[1]]));
}
// SAFETY: the first `n` elements of the cursor were just written.
unsafe { out.advance(n) };
}
let mut storage = [MaybeUninit::<i16>::uninit(); 4];
let mut samples = BorrowedBuf::<i16>::from(&mut storage[..]);
decode(&[1, 0, 0xff, 0xff], samples.unfilled());
assert_eq!(samples.filled(), &[1, -1]);
```
Reading from `std::io::Read` (with the `std` feature):
```rust
# #[cfg(feature = "std")] {
use borrowed_buf::{io, BorrowedBuf};
use core::mem::MaybeUninit;
let mut reader: &[u8] = b"some bytes";
let mut storage = [MaybeUninit::<u8>::uninit(); 4];
let mut buf = BorrowedBuf::<u8>::from(&mut storage[..]);
io::read_buf_exact(&mut reader, buf.unfilled()).unwrap();
assert_eq!(buf.filled(), b"some");
# }
```
Note that `BorrowedBuf::from(&mut [MaybeUninit<T>])` can't always infer `T`, since
`MaybeUninit<T>` is itself `Copy` and `From<&mut [T]>` would also apply. Name the element type
(`BorrowedBuf::<u8>::from(..)`) when the compiler asks for it; `core::io` behaves the same.
## Differences from `core::io`
The `BorrowedBuf` and `BorrowedCursor` APIs are the same as on nightly. The only additions live
in the `io` module, because `std::io::Read` can't be extended with new methods on stable:
| `Read::read_buf(cursor)` | `io::read_buf(&mut reader, cursor)` |
| `Read::read_buf_exact(cursor)` | `io::read_buf_exact(&mut reader, cursor)` |
| `impl Write for BorrowedCursor<'_, u8>` | `impl Write for BorrowedCursor<'_, u8>` |
## License
MIT