| Crates.io | safer-ring |
| lib.rs | safer-ring |
| version | 0.0.1 |
| created_at | 2025-09-02 23:13:38.17518+00 |
| updated_at | 2025-09-02 23:13:38.17518+00 |
| description | A safe Rust wrapper around io_uring with zero-cost abstractions and compile-time memory safety guarantees |
| homepage | https://github.com/whit3rabbit/safer-ring |
| repository | https://github.com/whit3rabbit/safer-ring |
| max_upload_size | |
| id | 1821786 |
| size | 1,175,552 |
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.
OwnedBuffer API
PinnedBuffer APIThe 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:
OwnedBuffer to the Ring.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(())
# }
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.io_uring, with support for batch operations and buffer pooling to minimize syscalls and allocations.async/await ecosystem and provides compatibility layers for tokio::io::AsyncRead and AsyncWrite.io_uring support and can fall back to an epoll-based backend where io_uring is unavailable or restricted.These benchmarks demonstrate that safer-ring delivers memory safety with excellent performance:
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) |
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) |
Benchmarked: September 1, 2025
| Implementation | Latency (64KB) | Throughput | Use Case |
|---|---|---|---|
safer_ring_direct |
487µs | 128 MiB/s | Bypasses page cache |
safer_ring performs within 1.68x of raw io_uring implementationsBenchmarks run on Linux 6.12.33+kali-arm64 with io_uring support enabled
This library is ideal for building high-performance, I/O-bound applications on Linux where memory safety is critical.
Choose Safer-Ring for:
tokio-based applications to io_uring.Consider alternatives if:
io_uring is Linux-specific).unsafe code directly.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(())
}
OwnedBuffer APIThe 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.
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(())
# }
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(())
# }
AsyncRead/AsyncWrite CompatibilityFor 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(())
# }
PinnedBuffer APIYou 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.
This library is designed for Linux systems that support io_uring.
On non-Linux platforms, the library compiles with stub implementations, but creating a Ring will return an Unsupported error at runtime.
cargo build
The library includes an extensive test suite, including compile-fail tests that verify safety invariants at compile time.
cargo test
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.Contributions are welcome. Please feel free to open an issue for bug reports and feature requests, or submit a pull request.
This project is licensed under either of the MIT license or Apache License, Version 2.0, at your option.