safer-ring

Crates.iosafer-ring
lib.rssafer-ring
version0.0.1
created_at2025-09-02 23:13:38.17518+00
updated_at2025-09-02 23:13:38.17518+00
descriptionA safe Rust wrapper around io_uring with zero-cost abstractions and compile-time memory safety guarantees
homepagehttps://github.com/whit3rabbit/safer-ring
repositoryhttps://github.com/whit3rabbit/safer-ring
max_upload_size
id1821786
size1,175,552
whit3rabbit (whit3rabbit)

documentation

https://docs.rs/safer-ring

README

Safer-Ring

Safer-Ring is a memory-safe Rust wrapper for Linux's io_uring that provides zero-cost abstractions while preventing common memory safety issues through compile-time guarantees.

This library's core innovation is an ownership transfer model that transforms the "if it compiles, it might panic" problem of some io_uring crates into Rust's standard "if it compiles, it's memory safe" guarantee.

Table of Contents

Core Safety Model: The "Hot Potato" Pattern

The library is built on an ownership transfer model, colloquially known as the "hot potato" pattern. This pattern ensures memory safety by managing buffer ownership explicitly:

  1. Your application provides an OwnedBuffer to the Ring.
  2. Ownership of the buffer is transferred to the kernel for the duration of the I/O operation.
  3. During this time, your application cannot access the buffer's memory, which is enforced at compile time.
  4. Once the kernel completes the operation, ownership of the buffer is returned to your application along with the result.

This cycle prevents use-after-free errors, data races, and other common memory safety issues associated with completion-based asynchronous I/O, all without incurring runtime overhead.

use safer_ring::{Ring, OwnedBuffer};

# async fn doc_example() -> Result<(), Box<dyn std::error::Error>> {
let ring = Ring::new(32)?;
let buffer = OwnedBuffer::new(1024);

// Give the buffer to the kernel for the read operation.
// When the await completes, we get the buffer back.
let (bytes_read, returned_buffer) = ring.read_owned(0, buffer).await?;

println!("Read {} bytes", bytes_read);

// The returned buffer can be safely reused for the next operation.
let (bytes_written, _final_buffer) = ring.write_owned(1, returned_buffer).await?;
# Ok(())
# }

Key Features

  • Memory Safety: Compile-time guarantees that buffers outlive their operations, preventing use-after-free bugs.
  • Cancellation Safety: Dropped Futures are handled gracefully. The underlying I/O operation will complete, and its buffer will be safely managed by an "orphan tracker" to prevent memory leaks.
  • Type-Safe State Machine: An internal, type-level state machine prevents invalid operation sequences (e.g., submitting an operation twice) at compile time.
  • High Performance: Aims for zero-cost abstractions over raw io_uring, with support for batch operations and buffer pooling to minimize syscalls and allocations.
  • Ergonomic Async API: Integrates seamlessly with Rust's async/await ecosystem and provides compatibility layers for tokio::io::AsyncRead and AsyncWrite.
  • Runtime Detection: Automatically detects io_uring support and can fall back to an epoll-based backend where io_uring is unavailable or restricted.

Performance Benchmarks

These benchmarks demonstrate that safer-ring delivers memory safety with excellent performance:

File Copy Performance (Cached I/O)

Benchmarked: September 1, 2025

Implementation Latency Throughput Safety Overhead
safer_ring 44.2µs 1.38 GiB/s 1.35x
raw_io_uring 32.8µs 1.86 GiB/s 1.0x (baseline)
std::fs 7.7µs 7.94 GiB/s N/A (kernel optimized)

Network I/O Performance (Pseudo-device)

Benchmarked: September 1, 2025

Implementation Latency (64B) Latency (1KB) Latency (4KB) Safety Overhead
safer_ring 21.4µs 21.7µs 26.0µs 1.68x
raw_io_uring 12.7µs 13.7µs 15.6µs 1.0x (baseline)

Direct I/O Performance (O_DIRECT)

Benchmarked: September 1, 2025

Implementation Latency (64KB) Throughput Use Case
safer_ring_direct 487µs 128 MiB/s Bypasses page cache

Key Insights

  • Excellent Safety Trade-off: Only 35-68% overhead for complete memory safety guarantees
  • Competitive Performance: safer_ring performs within 1.68x of raw io_uring implementations
  • High Throughput: Sustained multi-GB/s performance with predictable latency (20-60µs typical)
  • Production Ready: Performance characteristics suitable for high-concurrency applications

Benchmarks run on Linux 6.12.33+kali-arm64 with io_uring support enabled

When to Use Safer-Ring

This library is ideal for building high-performance, I/O-bound applications on Linux where memory safety is critical.

Choose Safer-Ring for:

  • Network services (web servers, proxies, API gateways).
  • Storage systems (databases, file servers, message queues).
  • Applications requiring high concurrency with predictable latency.
  • Safely migrating existing tokio-based applications to io_uring.

Consider alternatives if:

  • Your application is not primarily I/O-bound.
  • You require support for non-Linux platforms (as io_uring is Linux-specific).
  • Absolute maximum performance is required, and you are willing to manage unsafe code directly.

Quick Start

Add safer-ring to your Cargo.toml:

[dependencies]
safer-ring = "0.1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Here is a complete example of reading from a file using the recommended OwnedBuffer API.

use safer_ring::{Ring, OwnedBuffer};
use std::fs::File;
use std::os::unix::io::AsRawFd;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create a Ring. It can be shared immutably.
    let ring = Ring::new(32)?;

    // 2. Open a file and get its raw file descriptor.
    let file = File::open("README.md")?;
    let fd = file.as_raw_fd();

    // 3. Create a buffer whose ownership will be transferred.
    let buffer = OwnedBuffer::new(4096);

    // 4. Perform the read operation.
    // Ownership of `buffer` is given to the operation.
    let (bytes_read, returned_buffer) = ring.read_owned(fd, buffer).await?;
    // After `.await`, ownership of the buffer is returned.

    println!("Successfully read {} bytes.", bytes_read);

    // 5. Access the data safely.
    if let Some(guard) = returned_buffer.try_access() {
        let content = std::str::from_utf8(&guard[..bytes_read])?;
        println!("File content starts with: '{}...'", content.lines().next().unwrap_or(""));
    }

    Ok(())
}

API Guide: The OwnedBuffer API

The primary and recommended API is based on OwnedBuffer and methods with an _owned suffix. This API enforces the safe ownership transfer model. For a complete reference, please see docs/API.md.

File I/O

For file I/O, use the _at_owned variants, which are ideal for sequential or random-access patterns like file copying.

use safer_ring::{Ring, OwnedBuffer};
# use std::os::unix::io::RawFd;
# async fn doc_example(ring: &Ring<'_>, fd: RawFd) -> Result<(), Box<dyn std::error::Error>> {

let mut buffer = OwnedBuffer::new(8192);
let mut offset = 0;

// Read a chunk from the file
let (bytes_read, returned_buffer) = ring.read_at_owned(fd, buffer, offset).await?;
buffer = returned_buffer; // Reclaim ownership to reuse the buffer

// Write that chunk to another file
let (bytes_written, returned_buffer) = ring.write_at_owned(fd, buffer, offset, bytes_read).await?;
buffer = returned_buffer; // Reclaim ownership again
# Ok(())
# }

Batch Operations

For submitting multiple operations with a single syscall, use Batch with the submit_batch_standalone method. This returns a Future that does not hold a mutable borrow of the Ring, making it easier to compose with other async operations.

use safer_ring::{Ring, Batch, Operation, PinnedBuffer};
use std::future::poll_fn;
# async fn doc_example() -> Result<(), Box<dyn std::error::Error>> {
# let mut ring = Ring::new(32)?;
# let mut buffer1 = PinnedBuffer::with_capacity(1024);
# let mut batch = Batch::new();
# batch.add_operation(Operation::read().fd(0).buffer(buffer1.as_mut_slice()))?;

// submit_batch_standalone does not borrow the ring mutably in the future.
let mut batch_future = ring.submit_batch_standalone(batch)?;

// You can still use the ring for other operations here.

// Poll the future by providing the ring when needed.
let batch_result = poll_fn(|cx| {
    batch_future.poll_with_ring(&mut ring, cx)
}).await?;

println!("Batch completed with {} results.", batch_result.results.len());
# Ok(())
# }

Tokio AsyncRead/AsyncWrite Compatibility

For easy integration with existing code, safer-ring provides a compatibility layer. Note that this layer introduces memory copies and has higher overhead than the native _owned API.

use safer_ring::{Ring, compat::AsyncCompat};
use tokio::io::AsyncReadExt;
# async fn doc_example() -> Result<(), Box<dyn std::error::Error>> {
# let ring = Ring::new(32)?;
# let fd = 0;
// Wrap a file descriptor in a Tokio-compatible type
let mut file_reader = ring.file(fd);

let mut buffer = vec![0; 1024];
let bytes_read = file_reader.read(&mut buffer).await?;
# Ok(())
# }

A Note on the PinnedBuffer API

You may see a PinnedBuffer type and methods like read() or write() in the codebase.

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

Platform Support

This library is designed for Linux systems that support io_uring.

  • Minimum Kernel: Linux 5.1
  • Recommended Kernel: Linux 5.19+ (for advanced features like multi-shot operations)
  • Optimal Kernel: Linux 6.0+ (for the latest performance improvements)

On non-Linux platforms, the library compiles with stub implementations, but creating a Ring will return an Unsupported error at runtime.

Getting Started

Building

cargo build

Testing

The library includes an extensive test suite, including compile-fail tests that verify safety invariants at compile time.

cargo test

Further Resources

  • docs/API.md: A comprehensive cheat sheet and reference for the public API.
  • examples/ Directory: Contains practical, runnable examples showcasing various features.
    • safer_ring_demo.rs: A high-level tour of the library's safety features.
    • file_copy.rs: A complete file-copy utility demonstrating the OwnedBuffer pattern.
    • echo_server_main.rs: A TCP echo server.
    • async_demo.rs: A showcase of various asynchronous patterns.

Contributing

Contributions are welcome. Please feel free to open an issue for bug reports and feature requests, or submit a pull request.

License

This project is licensed under either of the MIT license or Apache License, Version 2.0, at your option.

Commit count: 31

cargo fmt