rama_core/io/owned.rs
1//! Owned-buffer async IO traits: rama's completion-style IO surface.
2//!
3//! These are inspired by the owned-buffer model used by io_uring runtimes
4//! (`compio` / `monoio` / `tokio-uring`): instead of lending a borrowed buffer
5//! to a readiness-based `poll_read`, the caller hands an **owned** buffer to the
6//! operation and gets it back together with the result. That ownership is the
7//! precondition for a completion backend: the buffer must stay valid and stable
8//! until the kernel finishes the op, a borrowed-per-poll buffer is gone after
9//! `Pending`, whereas the returned future keeps the owned one alive.
10//!
11//! # Buffers: [`IoBuf`] / [`IoBufMut`]
12//! An owned buffer is an [`IoBuf`] (its initialized bytes via `as_init() -> &[u8]`);
13//! a read *target* is an [`IoBufMut`] (writable backing via
14//! `as_uninit() -> &mut [MaybeUninit<u8>]`, with [`SetLen`] split out to declare the
15//! initialized length).
16//!
17//! **Reads overwrite from offset 0** (the compio/tokio-uring semantic), they fill
18//! the buffer from the start, so reusing one without clearing it is safe. To
19//! *accumulate* instead (read into the spare, keeping prior bytes), pass
20//! [`buf.uninit()`](IoBufMut::uninit), a spare-view [`Uninit`] that appends.
21//!
22//! A tokio transport *is* an owned transport for free: no wrapper is needed in
23//! the `tokio -> owned` direction. Owned-native backends (an io_uring leaf, a
24//! sans-io TLS stream) implement the owned traits **directly** and must NOT also
25//! implement tokio's `AsyncRead`/`AsyncWrite` (coherence: that would overlap the
26//! blanket). The reverse `owned -> tokio` direction, for readiness-only consumers
27//! that can't take owned buffers will need an extra memory copy.
28
29use core::future::poll_fn;
30use core::mem::MaybeUninit;
31use core::ops::{Bound, RangeBounds};
32use core::pin::Pin;
33use core::task::{Context, Poll};
34use std::io;
35
36use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
37
38use crate::bytes::{Bytes, BytesMut};
39
40/// Result of an owned-buffer IO operation: the [`io::Result`] plus the buffer
41/// handed back to the caller (ownership round-trips through the operation).
42pub type BufResult<T, B> = (io::Result<T>, B);
43
44/// An owned, stable byte buffer that can back an IO read or write.
45///
46/// # Safety
47/// The backing storage must stay valid and at a **fixed address for as long as a
48/// backend owns the buffer during an in-flight op**: from when the backend reads
49/// [`buf_ptr`](IoBuf::buf_ptr) / [`buf_mut_ptr`](IoBufMut::buf_mut_ptr) until the
50/// op completes (the kernel may read/write it after the call returns). A heap
51/// buffer (`Vec`/`BytesMut`/`Bytes`) satisfies this trivially, moving the handle
52/// moves a header, not the allocation. An **inline** (stack) buffer ([`ArrayBuf`])
53/// satisfies it too if: a completion backend moves the buffer into its op-slab
54/// *before* taking the pointer and does not move it again until the CQE, so the
55/// address the kernel sees stays fixed for the op.
56pub unsafe trait IoBuf: Send + 'static {
57 /// The initialized (readable / already-written) bytes.
58 ///
59 /// The core accessor: pointer and length both derive from it, so an impl is a
60 /// one-liner and use-sites get a safe, bounded slice instead of raw ptr+len.
61 fn as_init(&self) -> &[u8];
62
63 /// Number of initialized bytes (`as_init().len()`).
64 #[inline]
65 fn buf_len(&self) -> usize {
66 self.as_init().len()
67 }
68
69 /// Raw const pointer to the start of the initialized region: for handing an
70 /// address to a kernel/FFI sink (io_uring `SEND_ZC`, a TLS BIO). Most code
71 /// should prefer [`as_init`](IoBuf::as_init).
72 ///
73 /// Warning: make sure to read SAFETY rules of [`IoBuf`]
74 #[inline]
75 fn buf_ptr(&self) -> *const u8 {
76 self.as_init().as_ptr()
77 }
78
79 /// Restrict this buffer to the sub-range `[begin, end)`, consuming it.
80 ///
81 /// Used by writers to express "the not-yet-written tail" without copying.
82 /// Recover the original buffer with [`Slice::into_inner`].
83 ///
84 /// The range must lie within the **initialized** region `[0, buf_len)`, the
85 /// resulting [`Slice`] reports its whole length as initialized, so slicing into
86 /// the uninitialized spare would expose uninitialized bytes as readable.
87 /// Panics if the range is out of bounds for [`buf_len`](IoBuf::buf_len).
88 fn slice(self, range: impl RangeBounds<usize>) -> Slice<Self>
89 where
90 Self: Sized,
91 {
92 #[expect(
93 clippy::expect_used,
94 reason = "a valid buffer range bound + 1 cannot overflow usize"
95 )]
96 let begin = match range.start_bound() {
97 Bound::Included(&n) => n,
98 Bound::Excluded(&n) => n.checked_add(1).expect("slice range out of bounds"),
99 Bound::Unbounded => 0,
100 };
101 #[expect(
102 clippy::expect_used,
103 reason = "a valid buffer range bound + 1 cannot overflow usize"
104 )]
105 let end = match range.end_bound() {
106 Bound::Included(&n) => n.checked_add(1).expect("slice range out of bounds"),
107 Bound::Excluded(&n) => n,
108 Bound::Unbounded => self.buf_len(),
109 };
110 assert!(
111 begin <= end && end <= self.buf_len(),
112 "slice range out of bounds (must be within the initialized region)"
113 );
114 Slice {
115 buf: self,
116 begin,
117 end,
118 }
119 }
120}
121
122/// Declare how many leading bytes of a buffer are initialized.
123///
124/// Split out of [`IoBufMut`] (mirroring compio's `SetLen`) so views like
125/// [`Slice`] and [`Uninit`] can forward length-setting without re-implementing the
126/// whole mutable-buffer surface.
127pub trait SetLen {
128 /// Set the initialized length to `len`.
129 ///
130 /// # Safety
131 /// The first `len` bytes must actually be initialized and `len` must be `<=`
132 /// the buffer's total capacity.
133 unsafe fn set_len(&mut self, len: usize);
134}
135
136/// An [`IoBuf`] that can also be written into (i.e. used as a read destination).
137///
138/// # Safety
139/// [`as_uninit`](IoBufMut::as_uninit) must expose the buffer's full backing storage
140/// — the initialized prefix `[0, buf_len)` followed by writable spare
141/// `[buf_len, capacity)` — and the spare must be safe to write. Same address
142/// stability contract as [`IoBuf`].
143pub unsafe trait IoBufMut: IoBuf + SetLen {
144 /// The full backing storage as possibly-uninitialized bytes: the initialized
145 /// prefix `[0, buf_len)` plus the writable spare `[buf_len, capacity)`.
146 ///
147 /// The core accessor: capacity, the raw pointer, and the read-target spare
148 /// all derive from it, and a reader fills [`spare_mut`](IoBufMut::spare_mut)
149 /// (a safe `&mut [MaybeUninit<u8>]`) instead of doing raw pointer arithmetic.
150 fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>];
151
152 /// Total capacity (`as_uninit().len()`).
153 #[inline]
154 fn buf_capacity(&mut self) -> usize {
155 self.as_uninit().len()
156 }
157
158 /// Raw mutable pointer to the start of the backing storage: for a kernel/FFI
159 /// read target (io_uring recv). Most code should prefer
160 /// [`spare_mut`](IoBufMut::spare_mut).
161 ///
162 /// Warning: make sure to read SAFETY rules of [`IoBufMut`]
163 #[inline]
164 fn buf_mut_ptr(&mut self) -> *mut u8 {
165 self.as_uninit().as_mut_ptr().cast()
166 }
167
168 /// The writable spare tail `[buf_len, capacity)` — the read destination.
169 #[inline]
170 fn spare_mut(&mut self) -> &mut [MaybeUninit<u8>] {
171 let len = self.buf_len();
172 &mut self.as_uninit()[len..]
173 }
174
175 /// The **initialized** region `[0, buf_len)` as a mutable slice, for an
176 /// in-place transform (e.g. decrypting a TLS record inside the very buffer it
177 /// arrived in, no copy).
178 #[inline]
179 fn as_mut_slice(&mut self) -> &mut [u8] {
180 let len = self.buf_len();
181 // SAFETY: `[0, buf_len)` is initialized.
182 unsafe { self.as_uninit()[..len].assume_init_mut() }
183 }
184
185 /// A read-target view of just the writable spare `[buf_len, capacity)`.
186 ///
187 /// Reading into it **appends** to `self` (the view's offset 0 is `self`'s
188 /// `buf_len`), so it's how you accumulate on top of the overwrite-from-0 read
189 /// primitive, e.g. `read_exact` loops `read_owned(buf.uninit())`. The owned
190 /// analogue of compio's `Uninit`.
191 fn uninit(self) -> Uninit<Self>
192 where
193 Self: Sized,
194 {
195 let begin = self.buf_len();
196 Uninit { buf: self, begin }
197 }
198}
199
200/// A sub-range view `[begin, end)` of an owned [`IoBuf`], itself an [`IoBuf`].
201///
202/// Created by [`IoBuf::slice`]; the underlying buffer is recovered with
203/// [`into_inner`](Slice::into_inner).
204#[derive(Debug)]
205pub struct Slice<B> {
206 buf: B,
207 begin: usize,
208 end: usize,
209}
210
211impl<B> Slice<B> {
212 /// Recover the underlying buffer.
213 #[inline]
214 pub fn into_inner(self) -> B {
215 self.buf
216 }
217}
218
219// SAFETY: the view points into `buf`'s stable storage and `[begin, end)` was
220// checked to be within `buf`'s initialized region in `IoBuf::slice`, so it's a
221// valid write source. (A read *target* is a [`Uninit`], not a [`Slice`].)
222unsafe impl<B: IoBuf> IoBuf for Slice<B> {
223 #[inline]
224 fn as_init(&self) -> &[u8] {
225 &self.buf.as_init()[self.begin..self.end]
226 }
227}
228
229/// A read-target view of a buffer's writable spare `[buf_len, capacity)`: reading
230/// into it **appends** to the underlying (the view's offset 0 is the underlying's
231/// `buf_len`). Created by [`IoBufMut::uninit`]; recover the buffer with
232/// [`into_inner`](Uninit::into_inner).
233#[derive(Debug)]
234pub struct Uninit<B> {
235 buf: B,
236 begin: usize,
237}
238
239impl<B> Uninit<B> {
240 /// Recover the underlying buffer, with whatever was read into the spare now
241 /// part of its initialized region.
242 #[inline]
243 pub fn into_inner(self) -> B {
244 self.buf
245 }
246}
247
248// SAFETY: a spare view exposes no readable bytes, `as_init` is empty.
249unsafe impl<B: IoBuf> IoBuf for Uninit<B> {
250 #[inline]
251 fn as_init(&self) -> &[u8] {
252 &[]
253 }
254}
255
256impl<B: SetLen + IoBuf> SetLen for Uninit<B> {
257 #[inline]
258 unsafe fn set_len(&mut self, len: usize) {
259 // `len` is relative to the spare's start, declare `begin + len` init on the
260 // underlying buffer.
261 unsafe { self.buf.set_len(self.begin + len) };
262 }
263}
264
265// SAFETY: `[begin, capacity)` of `buf`'s backing is owned, writable spare: same
266// stability contract as `buf`.
267unsafe impl<B: IoBufMut> IoBufMut for Uninit<B> {
268 #[inline]
269 fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
270 let begin = self.begin;
271 &mut self.buf.as_uninit()[begin..]
272 }
273}
274
275/// An owned buffer slot
276///
277/// The buffer slot threaded through [`poll_read_owned`](AsyncReadOwned::poll_read_owned)
278/// / [`poll_write_owned`](AsyncWriteOwned::poll_write_owned). It names *who owns
279/// the buffer* during an in-flight op, so the in-flight state is never an
280/// ambiguous `None`, and dropping it is always safe:
281///
282/// - [`Ready`](BufSlot::Ready): the buffer is here, idle; read into / write from it.
283/// - [`InFlight`](BufSlot::InFlight): a **completion** backend moved the buffer
284/// into its op-slab, the slot holds nothing. Dropping is safe, the leaf reaps it.
285/// - [`Parked`](BufSlot::Parked): a **readiness** backend (tokio) keeps the
286/// buffer here between polls (it has no kernel op to hold it). Nothing is DMA-ing
287/// into it, so dropping is safe too.
288///
289///
290/// # Usage
291/// The consumer holds it in a **persistent field**, hands a buffer in once, and it
292/// is reused: tokio recycles via `Ready`/`Parked`, a completion backend registers
293/// it (`InFlight`) and hands it back. Construct with [`new`](BufSlot::new), read
294/// the result via [`ready_mut`](BufSlot::ready_mut). The buffer-carrying variants
295/// are constructed only by leaves (via [`park`](BufSlot::park) /
296/// [`fill`](BufSlot::fill)), so a consumer can't fish a buffer out mid-op.
297#[derive(Debug)]
298pub enum BufSlot<B> {
299 /// The buffer is here and idle, no op in flight, so it can be read or handed
300 /// to a new op. This is both a fresh buffer and one holding a completed result,
301 /// the slot tracks where the buffer is, not what's in it.
302 Ready(B),
303 /// In flight on a completion backend, the buffer is in the leaf's op-slab.
304 InFlight,
305 /// Held by a readiness backend between polls (buffer present, mid-operation).
306 Parked(B),
307}
308
309impl<B> BufSlot<B> {
310 /// A fresh slot holding `buf`, ready to read into / write from.
311 #[inline]
312 pub fn new(buf: B) -> Self {
313 Self::Ready(buf)
314 }
315
316 /// The completed buffer by shared ref, only when idle ([`Ready`](Self::Ready),
317 /// the op finished). `None` while a read/write is still in flight (`InFlight`
318 /// *or* readiness-held `Parked`): a mid-operation buffer is never handed out for
319 /// reading.
320 #[inline]
321 pub fn ready(&self) -> Option<&B> {
322 match self {
323 Self::Ready(b) => Some(b),
324 _ => None,
325 }
326 }
327
328 /// The buffer when idle ([`Ready`](Self::Ready)), where the consumer reads
329 /// the result after a completed read. `None` while in flight.
330 #[inline]
331 pub fn ready_mut(&mut self) -> Option<&mut B> {
332 match self {
333 Self::Ready(b) => Some(b),
334 _ => None,
335 }
336 }
337
338 /// Take the completed buffer, consuming the slot, only when idle
339 /// ([`Ready`](Self::Ready), the op finished). `None` if a read/write is still in
340 /// flight (`InFlight` *or* readiness-held `Parked`), so a caller can't mistake a
341 /// mid-operation buffer for a result. This is the normal "the read/write
342 /// returned `Ready`, give me my buffer back" path. To recover the buffer
343 /// *regardless* of op state, use [`reclaim`](Self::reclaim).
344 #[inline]
345 pub fn take_ready(self) -> Option<B> {
346 match self {
347 Self::Ready(b) => Some(b),
348 _ => None,
349 }
350 }
351
352 /// Recover the buffer whenever it's present (idle `Ready` *or* readiness-held
353 /// `Parked`), consuming the slot. `None` only while a completion op owns it
354 /// (`InFlight`). For adapters that drive the poll themselves and reclaim the
355 /// buffer across a `Pending`/`Err` (e.g. reusing its staging capacity,
356 /// or a racing timeout short-circuiting a read): the contents may be
357 /// mid-operation, so this is *recovery*, not result extraction, use
358 /// [`take_ready`](Self::take_ready) for the latter.
359 #[inline]
360 pub fn reclaim(self) -> Option<B> {
361 match self {
362 Self::Ready(b) | Self::Parked(b) => Some(b),
363 Self::InFlight => None,
364 }
365 }
366
367 /// Whether the buffer is idle and here ([`Ready`](Self::Ready)), i.e. it can
368 /// be read into / written from right now. `false` means a read/write is still
369 /// in flight (`InFlight` on a completion backend, `Parked` on a readiness one)
370 /// and must be resumed via the leaf before the buffer is touched again.
371 #[inline]
372 pub fn is_ready(&self) -> bool {
373 matches!(self, Self::Ready(_))
374 }
375
376 /// Take the buffer to drive an op, leaving the slot [`InFlight`](Self::InFlight).
377 /// A readiness leaf restores it with [`park`](Self::park)/[`fill`](Self::fill);
378 /// a completion leaf leaves it `InFlight` until the op returns it via `fill`.
379 #[inline]
380 pub fn take(&mut self) -> Option<B> {
381 match core::mem::replace(self, Self::InFlight) {
382 Self::Ready(b) | Self::Parked(b) => Some(b),
383 Self::InFlight => None,
384 }
385 }
386
387 /// Put the buffer back as idle (a read/write completed).
388 #[inline]
389 pub fn fill(&mut self, buf: B) {
390 *self = Self::Ready(buf);
391 }
392
393 /// Park the buffer (a readiness backend holds it across `Pending`).
394 #[inline]
395 pub fn park(&mut self, buf: B) {
396 *self = Self::Parked(buf);
397 }
398}
399
400/// Read bytes into an owned buffer, returning the buffer with the result.
401///
402/// The read **overwrites from offset 0** and, on success, sets the initialized
403/// length to the bytes read, so reusing a buffer without clearing it is safe.
404/// Pass a buffer with capacity (e.g. `Vec::with_capacity(n)`), a zero-capacity
405/// buffer has nowhere to read into. To *accumulate* on top of prior bytes instead,
406/// read into [`buf.uninit()`](IoBufMut::uninit) (a spare-view that appends).
407pub trait AsyncReadOwned {
408 /// **The primitive.** Poll a read into the owned buffer in `slot`, returning
409 /// the bytes read (`0` = EOF). The buffer is moved *through* the read op:
410 ///
411 /// - **readiness backend (tokio)**: take the buffer, `poll_read` into it from
412 /// offset 0, put it back, the buffer sits in `slot` across `Pending`.
413 /// - **completion backend (io_uring)**: take the buffer, submit the op (the
414 /// op-slab owns it, `slot` left `InFlight`), on the CQE return it filled.
415 ///
416 /// Zero-copy on both, never lends a borrowed buffer to the kernel. Generic
417 /// (so not object-safe), the dyn erasure boundaries add a concrete shim.
418 ///
419 /// Pass a buffer with capacity, the read overwrites from offset 0 and sets the
420 /// initialized length to the bytes read.
421 fn poll_read_owned<B: IoBufMut>(
422 &mut self,
423 cx: &mut Context<'_>,
424 slot: &mut BufSlot<B>,
425 ) -> Poll<io::Result<usize>>;
426
427 /// Derived async convenience over [`poll_read_owned`](Self::poll_read_owned):
428 /// move `buf` in, drive to completion, hand it back filled. **Not a second
429 /// impl**: every owned reader gets it for free, relays and `read_exact_into`
430 /// use it.
431 ///
432 /// `async fn read(buf) -> BufResult` is the primitive completion runtimes
433 /// (compio / monoio) settled on. We keep it *derived* rather than primitive
434 /// because a manual `Future::poll` consumer (e.g. hyper's connection core)
435 /// can't drive an `async fn` without boxing its future, so `poll_read_owned`
436 /// stays the real primitive.
437 fn read_owned<B: IoBufMut>(
438 &mut self,
439 buf: B,
440 ) -> impl Future<Output = BufResult<usize, B>> + Send
441 where
442 Self: Send,
443 {
444 async move {
445 let mut slot = BufSlot::new(buf);
446 let res = poll_fn(|cx| self.poll_read_owned(cx, &mut slot)).await;
447 #[expect(
448 clippy::expect_used,
449 reason = "poll_fn resolved the op, so the slot holds the buffer"
450 )]
451 let buf = slot
452 .reclaim()
453 .expect("the op resolved (Ok or Err), leaving the buffer in the slot");
454 (res, buf)
455 }
456 }
457}
458
459/// Write bytes from an owned buffer, returning the buffer with the result.
460pub trait AsyncWriteOwned {
461 /// **The primitive.** Poll a write of the initialized bytes `[0, buf_len)`
462 /// of the buffer in `slot`, returning the bytes written. The buffer is moved
463 /// *through* the write op (readiness: borrowed-then-restored; completion /
464 /// SEND_ZC: moved into the op, `slot` left `InFlight`, returned on the NOTIF) and
465 /// left back in `slot` on `Ready`. The caller advances its own cursor by the
466 /// returned count.
467 fn poll_write_owned<B: IoBuf>(
468 &mut self,
469 cx: &mut Context<'_>,
470 slot: &mut BufSlot<B>,
471 ) -> Poll<io::Result<usize>>;
472
473 /// Poll a flush of any buffered data to the underlying sink.
474 fn poll_flush_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
475
476 /// Poll a shutdown of the write side of this connection.
477 fn poll_shutdown_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
478
479 /// Hint whether vectored writes are efficient on this writer (mirrors
480 /// [`tokio::io::AsyncWrite::is_write_vectored`]), lets a frame encoder pick a
481 /// chain threshold. Defaults to `false`, the blanket-over-tokio impl and thin
482 /// adapters forward the underlying writer's answer.
483 fn is_write_vectored_owned(&self) -> bool {
484 false
485 }
486
487 // TODO add a dedicated poll_write_owned_vectored
488
489 /// Derived async convenience over [`poll_write_owned`](Self::poll_write_owned):
490 /// move `buf` in, issue one write, hand it back with the bytes written. **Not
491 /// a second impl**: every owned writer gets it for free.
492 ///
493 /// `async fn write(buf) -> BufResult` is the primitive completion runtimes
494 /// (compio / monoio) settled on. We keep it *derived* rather than primitive
495 /// because a manual `Future::poll` consumer (e.g. hyper's connection core)
496 /// can't drive an `async fn` without boxing its future, so `poll_write_owned`
497 /// stays the real primitive.
498 fn write_owned<B: IoBuf>(&mut self, buf: B) -> impl Future<Output = BufResult<usize, B>> + Send
499 where
500 Self: Send,
501 {
502 async move {
503 let mut slot = BufSlot::new(buf);
504 let res = poll_fn(|cx| self.poll_write_owned(cx, &mut slot)).await;
505 #[expect(
506 clippy::expect_used,
507 reason = "poll_fn resolved the op, so the slot holds the buffer"
508 )]
509 let buf = slot
510 .reclaim()
511 .expect("the op resolved (Ok or Err), leaving the buffer in the slot");
512 (res, buf)
513 }
514 }
515
516 /// Derived async flush over [`poll_flush_owned`](Self::poll_flush_owned).
517 fn flush_owned(&mut self) -> impl Future<Output = io::Result<()>> + Send
518 where
519 Self: Send,
520 {
521 async move { poll_fn(|cx| self.poll_flush_owned(cx)).await }
522 }
523
524 /// Derived async shutdown over [`poll_shutdown_owned`](Self::poll_shutdown_owned).
525 fn shutdown_owned(&mut self) -> impl Future<Output = io::Result<()>> + Send
526 where
527 Self: Send,
528 {
529 async move { poll_fn(|cx| self.poll_shutdown_owned(cx)).await }
530 }
531}
532
533// Every tokio `AsyncRead` is an owned reader for free: `read_owned` is an inlined
534// shim over `poll_read` that moves the buffer in and out with no copy. Because
535// `read_owned` is uniquely named, this never clashes with `AsyncReadExt::read`.
536//
537// Coherence: an owned-native backend (io_uring leaf, sans-io TLS stream) impls
538// `AsyncReadOwned` directly and therefore must NOT also impl tokio `AsyncRead`,
539// or it would overlap this blanket.
540impl<T: AsyncRead + Unpin + Send + ?Sized> AsyncReadOwned for T {
541 #[inline]
542 fn poll_read_owned<B: IoBufMut>(
543 &mut self,
544 cx: &mut Context<'_>,
545 slot: &mut BufSlot<B>,
546 ) -> Poll<io::Result<usize>> {
547 #[expect(
548 clippy::expect_used,
549 reason = "readiness never moves the buffer to a leaf, so the slot is filled here"
550 )]
551 let mut buf = slot
552 .take()
553 .expect("poll_read_owned: slot empty (readiness never moves the buffer to a leaf)");
554
555 // overwrite-from-0: read into the whole buffer from offset 0 (the ecosystem
556 // semantic). Accumulate via `buf.uninit()`, which passes a spare-view here.
557 let (res, n) = {
558 let mut rb = ReadBuf::uninit(buf.as_uninit());
559 let res = Pin::new(&mut *self).poll_read(cx, &mut rb);
560 let n = rb.filled().len();
561 (res, n)
562 };
563
564 // SAFETY: `poll_read` initialized the first `n` bytes of the buffer.
565 unsafe { buf.set_len(n) };
566
567 match res {
568 Poll::Ready(Ok(())) => {
569 slot.fill(buf);
570 Poll::Ready(Ok(n))
571 }
572 Poll::Ready(Err(e)) => {
573 slot.fill(buf);
574 Poll::Ready(Err(e))
575 }
576 // readiness parks the buffer in the slot across `Pending`.
577 Poll::Pending => {
578 slot.park(buf);
579 Poll::Pending
580 }
581 }
582 }
583}
584
585// Every tokio `AsyncWrite` is an owned writer for free (same shim story).
586impl<T: AsyncWrite + Unpin + Send + ?Sized> AsyncWriteOwned for T {
587 #[inline]
588 fn poll_write_owned<B: IoBuf>(
589 &mut self,
590 cx: &mut Context<'_>,
591 slot: &mut BufSlot<B>,
592 ) -> Poll<io::Result<usize>> {
593 // Take + park a buffer similar to how poll_read_owned works and
594 // how a completion based backend would work. This way bugs surface
595 // in the same way.
596 #[expect(
597 clippy::expect_used,
598 reason = "readiness never moves the buffer to a leaf, so the slot is filled here"
599 )]
600 let buf = slot.take().expect("poll_write_owned: slot empty");
601 match Pin::new(&mut *self).poll_write(cx, buf.as_init()) {
602 Poll::Ready(res) => {
603 slot.fill(buf);
604 Poll::Ready(res)
605 }
606 Poll::Pending => {
607 slot.park(buf);
608 Poll::Pending
609 }
610 }
611 }
612
613 #[inline]
614 fn poll_flush_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
615 Pin::new(&mut *self).poll_flush(cx)
616 }
617
618 #[inline]
619 fn poll_shutdown_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
620 Pin::new(&mut *self).poll_shutdown(cx)
621 }
622
623 #[inline]
624 fn is_write_vectored_owned(&self) -> bool {
625 Self::is_write_vectored(self)
626 }
627}
628
629// SAFETY: a `Vec`'s heap allocation is stable across moves of the `Vec` handle,
630// `len`/`capacity` bound the initialized and total regions; `set_len` is the
631// canonical way to declare initialized bytes.
632unsafe impl IoBuf for Vec<u8> {
633 #[inline]
634 fn as_init(&self) -> &[u8] {
635 self
636 }
637}
638
639impl SetLen for Vec<u8> {
640 #[inline]
641 unsafe fn set_len(&mut self, len: usize) {
642 // SAFETY: forwarded contract: caller guarantees `len` bytes are
643 // initialized and `len <= capacity()`.
644 unsafe { Self::set_len(self, len) };
645 }
646}
647
648unsafe impl IoBufMut for Vec<u8> {
649 #[inline]
650 fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
651 let cap = self.capacity();
652 let ptr = self.as_mut_ptr().cast::<MaybeUninit<u8>>();
653 // SAFETY: `[0, cap)` is the Vec's allocation, the init prefix stays valid
654 // and the spare is writable.
655 unsafe { std::slice::from_raw_parts_mut(ptr, cap) }
656 }
657}
658
659// SAFETY: `BytesMut`'s allocation is stable across moves of the handle.
660unsafe impl IoBuf for BytesMut {
661 #[inline]
662 fn as_init(&self) -> &[u8] {
663 self
664 }
665}
666
667impl SetLen for BytesMut {
668 #[inline]
669 unsafe fn set_len(&mut self, len: usize) {
670 // SAFETY: forwarded contract: caller guarantees `len` bytes are
671 // initialized and `len <= capacity()`.
672 unsafe { Self::set_len(self, len) };
673 }
674}
675
676unsafe impl IoBufMut for BytesMut {
677 #[inline]
678 fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
679 let cap = self.capacity();
680 let ptr = self.as_mut_ptr().cast::<MaybeUninit<u8>>();
681 // SAFETY: `[0, cap)` is `BytesMut`'s contiguous allocation, init prefix
682 // stays valid and the spare is writable.
683 unsafe { std::slice::from_raw_parts_mut(ptr, cap) }
684 }
685}
686
687// `Bytes` is immutable shared storage: readable but not a valid read target
688//
689// SAFETY: `Bytes`'s backing storage is stable across moves of the handle.
690unsafe impl IoBuf for Bytes {
691 #[inline]
692 fn as_init(&self) -> &[u8] {
693 self
694 }
695}
696
697/// A fixed-size, **inline** (stack) owned read buffer, no heap allocation.
698///
699/// The owned analogue of `arrayvec::ArrayVec<u8, N>`: an `[u8; N]` plus an
700/// initialized-length counter, so a reader can fill it across multiple polls
701/// (`spare_mut()` shrinks as bytes arrive). Backs the fixed-size field readers
702/// so a wire-protocol parser reads a small header with zero per-call allocation.
703/// io_uring-safe: like any [`IoBuf`] it is moved into the backend's op-slab for the
704/// duration of an in-flight op, which pins its address for the kernel.
705pub struct ArrayBuf<const N: usize> {
706 buf: [MaybeUninit<u8>; N],
707 init: usize,
708}
709
710impl<const N: usize> ArrayBuf<N> {
711 /// A fresh, empty buffer (init `0`, capacity `N`).
712 #[inline]
713 pub const fn new() -> Self {
714 Self {
715 buf: [MaybeUninit::uninit(); N],
716 init: 0,
717 }
718 }
719
720 /// Try to recover the backing array.
721 #[inline]
722 pub fn try_into_array(self) -> Result<[u8; N], Self> {
723 if self.init != N {
724 return Err(self);
725 }
726
727 // SAFETY: `init == N`, so every element is initialized.
728 Ok(self.buf.map(|b| unsafe { b.assume_init() }))
729 }
730}
731
732impl<const N: usize> Default for ArrayBuf<N> {
733 #[inline]
734 fn default() -> Self {
735 Self::new()
736 }
737}
738
739// SAFETY: the inline `[u8; N]` is the backing storage and `init <= N`. Address
740// stability across an in-flight op is provided by the op-slab (see `IoBuf`).
741unsafe impl<const N: usize> IoBuf for ArrayBuf<N> {
742 #[inline]
743 fn as_init(&self) -> &[u8] {
744 // SAFETY: `[0, init)` is initialized (upheld by `SetLen::set_len`).
745 unsafe { self.buf[..self.init].assume_init_ref() }
746 }
747}
748
749impl<const N: usize> SetLen for ArrayBuf<N> {
750 #[inline]
751 unsafe fn set_len(&mut self, len: usize) {
752 debug_assert!(len <= N);
753 self.init = len;
754 }
755}
756
757unsafe impl<const N: usize> IoBufMut for ArrayBuf<N> {
758 #[inline]
759 fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
760 &mut self.buf
761 }
762}
763
764/// Split an IO into owned read and write halves usable concurrently.
765///
766/// The blanket impl over tokio IO delegates to [`tokio::io::split`] — whose
767/// half-locking already handles concurrent read+write — and the resulting halves
768/// are owned for free via the blanket owned impls. An owned-native backend (an
769/// io_uring leaf) can implement this directly to split at the ring level.
770pub trait SplitIo: Sized {
771 /// The owned read half.
772 type ReadHalf: AsyncReadOwned + Send;
773 /// The owned write half.
774 type WriteHalf: AsyncWriteOwned + Send;
775 /// Split into owned read and write halves.
776 fn split_io(self) -> (Self::ReadHalf, Self::WriteHalf);
777}
778
779impl<T: AsyncRead + AsyncWrite + Send> SplitIo for T {
780 type ReadHalf = tokio::io::ReadHalf<T>;
781 type WriteHalf = tokio::io::WriteHalf<T>;
782
783 #[inline]
784 fn split_io(self) -> (Self::ReadHalf, Self::WriteHalf) {
785 tokio::io::split(self)
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792
793 #[test]
794 fn io_buf_slice_rejects_overflowing_bounds() {
795 let start = std::panic::catch_unwind(|| {
796 b"abc"
797 .to_vec()
798 .slice((Bound::Excluded(usize::MAX), Bound::Unbounded))
799 });
800 start.unwrap_err();
801
802 let end = std::panic::catch_unwind(|| {
803 b"abc"
804 .to_vec()
805 .slice((Bound::Unbounded, Bound::Included(usize::MAX)))
806 });
807 end.unwrap_err();
808 }
809
810 #[test]
811 fn array_buf_try_into_array_requires_full_init() {
812 let mut partial = ArrayBuf::<4>::new();
813 unsafe { partial.set_len(2) };
814 let partial = partial.try_into_array().unwrap_err();
815 assert_eq!(partial.buf_len(), 2);
816
817 let mut full = ArrayBuf::<4>::new();
818 full.as_uninit()[..4].copy_from_slice(&[
819 MaybeUninit::new(b't'),
820 MaybeUninit::new(b'e'),
821 MaybeUninit::new(b's'),
822 MaybeUninit::new(b't'),
823 ]);
824 unsafe { full.set_len(4) };
825
826 let Ok(full) = full.try_into_array() else {
827 panic!("fully initialized ArrayBuf should convert to an array")
828 };
829 assert_eq!(full, *b"test");
830 }
831
832 #[tokio::test]
833 async fn owned_roundtrip_vec() {
834 // a tokio duplex half is an owned transport for free (blanket).
835 let (mut a, mut b) = tokio::io::duplex(64);
836
837 let (r, wbuf) = a.write_owned(b"hello".to_vec()).await;
838 assert_eq!(r.unwrap(), 5);
839 // write returns the buffer intact (ownership round-trips, no consume).
840 assert_eq!(wbuf, b"hello".to_vec());
841
842 let (r, rbuf) = b.read_owned(Vec::with_capacity(16)).await;
843 assert_eq!(r.unwrap(), 5);
844 assert_eq!(&rbuf[..], b"hello");
845 // set_len set the length to the bytes read.
846 assert_eq!(rbuf.len(), 5);
847 }
848
849 #[tokio::test]
850 async fn owned_read_overwrites_from_zero() {
851 let (mut a, mut b) = tokio::io::duplex(64);
852
853 let (w, _) = a.write_owned(b"world".to_vec()).await;
854 assert_eq!(w.unwrap(), 5);
855
856 let mut buf = Vec::with_capacity(16);
857 buf.extend_from_slice(b"pre-"); // init len 4
858 let (r, buf) = b.read_owned(buf).await;
859 assert_eq!(r.unwrap(), 5);
860 // overwrite-from-0: the read fills from the start, discarding "pre-".
861 assert_eq!(&buf[..], b"world");
862 }
863
864 #[tokio::test]
865 async fn owned_read_into_uninit_appends() {
866 let (mut a, mut b) = tokio::io::duplex(64);
867
868 let (w, _) = a.write_owned(b"world".to_vec()).await;
869 assert_eq!(w.unwrap(), 5);
870
871 let mut buf = Vec::with_capacity(16);
872 buf.extend_from_slice(b"pre-"); // init len 4
873 // `uninit()` is the explicit accumulate view: read into the spare.
874 let (r, view) = b.read_owned(buf.uninit()).await;
875 assert_eq!(r.unwrap(), 5);
876 let buf = view.into_inner();
877 assert_eq!(&buf[..], b"pre-world");
878 }
879
880 #[tokio::test]
881 async fn owned_roundtrip_bytes_mut_and_bytes() {
882 let (mut a, mut b) = tokio::io::duplex(64);
883
884 // write from an immutable `Bytes` (IoBuf, not IoBufMut).
885 let (w, _) = a.write_owned(Bytes::from_static(b"xyz")).await;
886 assert_eq!(w.unwrap(), 3);
887
888 let (r, buf) = b.read_owned(BytesMut::with_capacity(8)).await;
889 assert_eq!(r.unwrap(), 3);
890 assert_eq!(&buf[..], b"xyz");
891 }
892
893 #[tokio::test]
894 async fn poll_read_owned_reads_then_signals_eof() {
895 let (mut a, mut b) = tokio::io::duplex(64);
896
897 let (w, _) = a.write_owned(b"chunk".to_vec()).await;
898 w.unwrap();
899 // poll_read_owned (blanket) fills the slot buffer.
900 let mut slot = BufSlot::new(BytesMut::with_capacity(16));
901 let n = core::future::poll_fn(|cx| b.poll_read_owned(cx, &mut slot))
902 .await
903 .unwrap();
904 assert_eq!(&slot.ready_mut().unwrap()[..n], b"chunk");
905
906 // dropping the writer -> EOF -> 0 bytes.
907 drop(a);
908 let mut slot = BufSlot::new(BytesMut::with_capacity(16));
909 let eof = core::future::poll_fn(|cx| b.poll_read_owned(cx, &mut slot))
910 .await
911 .unwrap();
912 assert_eq!(eof, 0);
913 }
914
915 #[tokio::test]
916 async fn split_io_halves_are_owned_and_concurrent() {
917 use tokio::io::{AsyncReadExt, AsyncWriteExt};
918
919 let (a, mut b) = tokio::io::duplex(64);
920 let (mut a_r, mut a_w) = a.split_io();
921
922 // write via the owned write half, read on the plain tokio side.
923 let (res, _) = a_w.write_owned(b"split".to_vec()).await;
924 res.unwrap();
925 let mut got = [0u8; 5];
926 b.read_exact(&mut got).await.unwrap();
927 assert_eq!(&got, b"split");
928
929 // read via the owned read half.
930 b.write_all(b"back!").await.unwrap();
931 let (res, buf) = a_r.read_owned(Vec::with_capacity(8)).await;
932 assert_eq!(res.unwrap(), 5);
933 assert_eq!(&buf[..], b"back!");
934 }
935}