uring-file
Async file I/O for Linux using io_uring.
io_uring is Linux's modern async I/O interface. It's fast and efficient, but the raw API requires managing submission queues, completion queues, memory lifetimes, and substantial unsafe code. This crate builds on io-uring to provide simple async/await file operations at three levels of abstraction.
Three ways to use it
1. High-level functions — Similar to std::fs, but async. Suitable for scripts, tools, and straightforward file operations.
let contents = read_to_string.await?;
2. The default ring — The UringFile trait works on any file handle. Useful when you have files opened elsewhere and want io_uring performance without managing a ring.
use UringFile;
let file = open?;
let result = file.ur_read_at.await?;
3. Custom rings — Full control over ring configuration. Configure queue size, enable kernel polling, register files for faster repeated access.
use ;
let ring = new?;
All three use the same underlying io_uring implementation. Uring is Clone + Send + Sync, and path arguments accept any type implementing AsRef<Path>.
Requirements
- Linux 5.6+ (5.11+ for full feature set)
- tokio runtime
[]
= "0.4"
= { = "1", = ["rt", "macros"] }
Example
use fs;
async
API reference
See docs.rs for full documentation.
uring_file::fs — high-level convenience
// Reading
read.await?; // -> Vec<u8>
read_to_string.await?; // -> String
// Writing
write.await?; // create or truncate
append.await?; // create or append
copy.await?; // -> bytes copied
// Opening files (returns tokio::fs::File)
let file = open.await?; // read-only
let file = create.await?; // write, create/truncate
// Directories
create_dir.await?;
create_dir_all.await?;
remove_dir.await?;
remove_dir_all.await?;
// Files and links
remove_file.await?;
rename.await?;
symlink.await?;
hard_link.await?;
truncate.await?;
// Metadata
metadata.await?; // -> Metadata
exists.await; // -> bool
UringFile trait — for existing file handles
Works with std::fs::File and tokio::fs::File:
use UringFile;
let file = open?;
// Positioned I/O
let result = file.ur_read_at.await?;
let result = file.ur_write_at.await?;
// Durability
file.ur_sync.await?; // fsync
file.ur_datasync.await?; // fdatasync
// Metadata and space management
file.ur_statx.await?;
file.ur_fallocate.await?;
file.ur_fadvise.await?;
file.ur_ftruncate.await?;
Uring — full control
use ;
let ring = new?;
// File operations
let fd = ring.open.await?;
ring.write_at.await?;
ring.read_at.await?;
ring.close.await?;
// Path operations
ring.mkdir.await?;
ring.rename.await?;
ring.unlink.await?;
// Registered files for faster repeated access
let registered = ring.register?;
ring.read_at.await?;
Kernel version requirements
| Feature | Minimum kernel |
|---|---|
| Basic read/write/sync | 5.1 |
| open, statx, fallocate, fadvise | 5.6 |
| close, rename, unlink, mkdir, symlink, link | 5.11 |
| ftruncate | 6.9 |
Limitations
-
No readdir in io_uring —
remove_dir_alluses tokio for directory listing, then io_uring for deletions. This is a kernel limitation. -
~2GB per operation — Single read/write operations are limited to approximately 2GB (
uring::URING_LEN_MAX). Chunk larger transfers.
Architecture
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Async tasks │────▶│ Submission thread │────▶│ io_uring │
└─────────────┘ └──────────────────┘ └────────┬────────┘
▲ │
│ ┌──────────────────┐ │
└────────────│ Completion thread │◀────────────┘
└──────────────────┘
Async tasks send requests to a submission thread that batches them into the io_uring submission queue. A completion thread polls for results and wakes the appropriate futures.
License
MIT OR Apache-2.0