Skip to main content

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}