PinnedBuffer

Struct PinnedBuffer 

Source
pub struct PinnedBuffer<T: ?Sized> { /* private fields */ }
Expand description

A buffer that is pinned in memory, primarily for educational purposes.

§⚠️ FUNDAMENTALLY LIMITED - DO NOT USE FOR I/O OPERATIONS

This API is considered educational and is not suitable for practical applications involving I/O. It suffers from fundamental lifetime constraints in Rust that make it impossible to use in loops or for multiple concurrent operations on the same Ring instance. It exists to demonstrate the complexities that the OwnedBuffer model successfully solves.

For all applications, you MUST use OwnedBuffer with the *_owned methods on Ring.

§The Core Problem

The Ring methods that accept PinnedBuffer (e.g., ring.read()) return a Future that holds a mutable borrow on both the Ring and the buffer for their entire lifetimes. This makes it impossible for the borrow checker to allow a second operation in a loop or concurrently, as the first borrow is never released.

use safer_ring::{Ring, PinnedBuffer};

let mut ring = Ring::new(32)?;
let mut buffer = PinnedBuffer::with_capacity(1024);

// This fails to compile due to lifetime constraints:
for _ in 0..2 {
    let (_, buf) = ring.read(0, buffer.as_mut_slice())?.await?;
    buffer = buf;  // Error: cannot use ring again while borrowed
}

§When is PinnedBuffer useful?

  • Benchmarking allocation strategies (e.g., with_capacity_aligned, with_capacity_numa).
  • Situations where you need a single, one-shot I/O operation and the buffer and ring will be dropped immediately after.
  • As a building block for more complex, unsafe abstractions.

For all other cases, and especially for application-level code, use OwnedBuffer.

§Memory Layout

The buffer uses heap allocation via Pin<Box<T>> which guarantees:

  • Stable memory addresses (required for io_uring)
  • Automatic cleanup when dropped
  • Zero-copy semantics for I/O operations

§Generation Tracking

Each buffer includes a GenerationCounter for lifecycle tracking and debugging. This helps identify buffer reuse patterns and can assist in detecting potential use-after-free scenarios during development.

§Examples

Valid use cases (allocation benchmarking):

use safer_ring::buffer::PinnedBuffer;
use std::pin::Pin;

// Benchmarking different allocation strategies
let standard_buffer = PinnedBuffer::with_capacity(4096);
let aligned_buffer = PinnedBuffer::with_capacity_aligned(4096);
let numa_buffer = PinnedBuffer::with_capacity_numa(4096, Some(0));

// Single, one-shot operation (not practical for real apps)
let buffer = PinnedBuffer::new([1, 2, 3, 4]);
let pinned_ref: Pin<&[u8; 4]> = buffer.as_pin();

Implementations§

Source§

impl<T: ?Sized> PinnedBuffer<T>

Source

pub fn as_pin(&self) -> Pin<&T>

Returns a pinned reference to the buffer data.

This method provides safe access to the pinned data while maintaining the pinning guarantees required for io_uring operations.

§Examples
use safer_ring::buffer::PinnedBuffer;
use std::pin::Pin;

let buffer = PinnedBuffer::new([1, 2, 3, 4]);
let pinned_ref: Pin<&[u8; 4]> = buffer.as_pin();
assert_eq!(&*pinned_ref, &[1, 2, 3, 4]);
Source

pub fn as_pin_mut(&mut self) -> Pin<&mut T>

Returns a mutable pinned reference to the buffer data.

This method provides safe mutable access to the pinned data while maintaining the pinning guarantees. Essential for io_uring write operations.

§Examples
use safer_ring::buffer::PinnedBuffer;
use std::pin::Pin;

let mut buffer = PinnedBuffer::new([0; 4]);
let mut pinned_ref: Pin<&mut [u8; 4]> = buffer.as_pin_mut();
// Safe to modify through pinned reference
Source

pub fn generation(&self) -> u64

Returns the current generation of this buffer.

The generation counter tracks buffer lifecycle events and can be used for debugging buffer reuse patterns and detecting potential issues.

§Examples
use safer_ring::buffer::PinnedBuffer;

let mut buffer = PinnedBuffer::with_capacity(1024);
let initial_gen = buffer.generation();

buffer.mark_in_use();
assert!(buffer.generation() > initial_gen);
Source

pub fn mark_in_use(&mut self)

Mark this buffer as in use and increment generation.

This method should be called when the buffer is being used for I/O operations. It helps track buffer lifecycle for debugging purposes.

§Examples
use safer_ring::buffer::PinnedBuffer;

let mut buffer = PinnedBuffer::with_capacity(1024);
let gen_before = buffer.generation();

buffer.mark_in_use();
assert_eq!(buffer.generation(), gen_before + 1);
Source

pub fn mark_available(&mut self)

Mark this buffer as available and increment generation.

This method should be called when the buffer is no longer being used for I/O operations and is available for reuse.

§Examples
use safer_ring::buffer::PinnedBuffer;

let mut buffer = PinnedBuffer::with_capacity(1024);
buffer.mark_in_use();
let gen_after_use = buffer.generation();

buffer.mark_available();
assert_eq!(buffer.generation(), gen_after_use + 1);
Source

pub fn is_available(&self) -> bool

Check if this buffer is available for use.

Note: This is a simple implementation - a more sophisticated version might track actual usage state.

Source

pub fn as_ptr(&self) -> *const T

Returns a raw pointer to the buffer data.

§Safety

The pointer is valid only while the buffer exists.

Source

pub fn as_mut_ptr(&mut self) -> *mut T

Returns a mutable raw pointer to the buffer data.

§Safety

The pointer is valid only while the buffer exists.

Source§

impl<T> PinnedBuffer<T>

Source

pub fn new(data: T) -> Self

Creates a new pinned buffer from the given data.

This constructor takes ownership of the provided data and pins it in memory, making it suitable for io_uring operations. The data is moved to the heap and its address becomes stable for the lifetime of the buffer.

§Parameters
  • data - The data to pin in memory. Can be any type T.
§Returns

Returns a new PinnedBuffer<T> with the data pinned and generation counter initialized to 0.

§Examples
use safer_ring::buffer::PinnedBuffer;

// Pin an array
let buffer = PinnedBuffer::new([1, 2, 3, 4]);
assert_eq!(buffer.len(), 4);

// Pin a custom struct
#[derive(Debug, PartialEq)]
struct Data { value: u32 }

let buffer = PinnedBuffer::new(Data { value: 42 });
assert_eq!(buffer.as_pin().value, 42);
Source§

impl PinnedBuffer<[u8]>

Source

pub fn with_capacity(size: usize) -> Self

Creates a new zero-initialized pinned buffer with the specified size.

This is the primary method for creating buffers for I/O operations. The buffer is heap-allocated, zero-initialized, and pinned for stable memory addresses required by io_uring.

§Parameters
  • size - The size of the buffer in bytes. Must be greater than 0 for meaningful use.
§Returns

Returns a PinnedBuffer<[u8]> containing a zero-initialized buffer of the specified size, ready for I/O operations.

§Examples
use safer_ring::buffer::PinnedBuffer;

// Create a 4KB buffer for file I/O
let buffer = PinnedBuffer::with_capacity(4096);
assert_eq!(buffer.len(), 4096);
assert!(buffer.as_slice().iter().all(|&b| b == 0)); // All zeros

// Create buffer for network I/O
let net_buffer = PinnedBuffer::with_capacity(1500); // MTU size
assert_eq!(net_buffer.len(), 1500);
Source

pub fn from_vec(vec: Vec<u8>) -> Self

Creates a new pinned buffer from a vector.

This method takes ownership of a vector and converts it into a pinned buffer. The vector’s data is preserved and the buffer can be used immediately for I/O operations.

§Parameters
  • vec - The vector to convert into a pinned buffer.
§Returns

Returns a PinnedBuffer<[u8]> containing the vector’s data, pinned and ready for I/O operations.

§Examples
use safer_ring::buffer::PinnedBuffer;

let data = vec![1, 2, 3, 4, 5];
let buffer = PinnedBuffer::from_vec(data);
assert_eq!(buffer.as_slice(), &[1, 2, 3, 4, 5]);
assert_eq!(buffer.len(), 5);
Source

pub fn from_boxed_slice(slice: Box<[u8]>) -> Self

Creates a new pinned buffer from a boxed slice.

Source

pub fn from_slice(slice: &[u8]) -> Self

Creates a new pinned buffer by copying from a slice.

Source

pub fn with_capacity_aligned(size: usize) -> Self

Creates a new aligned pinned buffer with the specified size.

This method creates a pinned buffer using page-aligned allocation (4096 bytes) for optimal DMA performance with io_uring operations. The alignment helps reduce memory copy overhead in the kernel.

§Parameters
  • size - The size of the buffer in bytes. The buffer will be page-aligned regardless of the size specified.
§Returns

Returns a PinnedBuffer<[u8]> with page-aligned, zero-initialized memory optimized for high-performance I/O operations.

§Performance Notes

Page-aligned buffers can provide significant performance benefits for:

  • Large sequential I/O operations
  • Direct memory access (DMA) operations
  • Kernel bypass operations with io_uring
§Examples
use safer_ring::buffer::PinnedBuffer;

// Create aligned buffer for high-performance I/O
let buffer = PinnedBuffer::with_capacity_aligned(8192);
assert_eq!(buffer.len(), 8192);
assert!(buffer.as_slice().iter().all(|&b| b == 0)); // Zero-initialized

// Even small sizes get page alignment benefits
let small_aligned = PinnedBuffer::with_capacity_aligned(64);
assert_eq!(small_aligned.len(), 64);
Source

pub fn with_capacity_numa(size: usize, numa_node: Option<usize>) -> Self

Creates a new NUMA-aware pinned buffer with the specified size. On Linux, attempts to allocate memory on the specified NUMA node.

Source

pub fn as_mut_slice(&mut self) -> Pin<&mut [u8]>

Returns a mutable slice reference with pinning guarantees.

Source

pub fn as_slice(&self) -> &[u8]

Returns an immutable slice reference.

Source

pub fn len(&self) -> usize

Returns the length of the buffer.

Source

pub fn is_empty(&self) -> bool

Checks if the buffer is empty.

Source§

impl<const N: usize> PinnedBuffer<[u8; N]>

Source

pub fn from_array(array: [u8; N]) -> Self

Creates a new pinned buffer from a fixed-size array.

Source

pub fn zeroed() -> Self

Creates a new zero-initialized pinned buffer.

Source

pub fn as_slice(&self) -> &[u8]

Returns an immutable slice reference to the array.

Source

pub fn as_mut_slice(&mut self) -> Pin<&mut [u8]>

Returns a mutable slice reference with pinning guarantees.

Source

pub const fn len(&self) -> usize

Returns the length of the buffer.

Source

pub const fn is_empty(&self) -> bool

Checks if the buffer is empty.

Trait Implementations§

Source§

impl<T: Send + ?Sized> Send for PinnedBuffer<T>

Source§

impl<T: Sync + ?Sized> Sync for PinnedBuffer<T>

Auto Trait Implementations§

§

impl<T> !Freeze for PinnedBuffer<T>

§

impl<T> RefUnwindSafe for PinnedBuffer<T>
where T: RefUnwindSafe + ?Sized,

§

impl<T> Unpin for PinnedBuffer<T>
where T: ?Sized,

§

impl<T> UnwindSafe for PinnedBuffer<T>
where T: UnwindSafe + ?Sized,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.