Crates.io | axboe-liburing |
lib.rs | axboe-liburing |
version | 2.12.0 |
created_at | 2025-03-02 14:43:37.16073+00 |
updated_at | 2025-08-24 17:55:25.551447+00 |
description | A Rust transliteration of axboe's liburing |
homepage | |
repository | https://github.com/cmazakas/liburing |
max_upload_size | |
id | 1574643 |
size | 310,573 |
Building this crate successfully may require the following in your .cargo/config.toml
:
[env]
CLANG_PATH = "/usr/bin/clang-20"
LIBCLANG_PATH = "/usr/lib/llvm-20/lib"
This crate implements an almost pure Rust version of Jens Axboe's liburing.
All the good names were taken so the package's name is axboe-liburing but the imported crate is liburing_rs
.
liburing has tremendous value in that it's a low-level unopinionated set of helpers that form a complete vocabulary for using io_uring. Originally designed as a set of test helpers, liburing is used to setup and teardown rings, register buffer groups, and create and manage SQEs and CQEs. liburing's API offers users a comprehensive way of using io_uring in a simple manner.
axboe-liburing implements the entire header liburing.h
in native Rust so that everything is as equally inlined as
if you were using the library from C or C++. This covers around 142 public functions.
Add the crate with:
cargo add axboe-liburing
axboe-liburing ships with the liburing source and builds liburing.a
, statically linking it in via the build.rs
. This means
that axboe-liburing requires a C toolchain be present on the system in order to build. Typical conventions are followed,
the environment variables CC
, CXX
are used to set the C and C++ compilers.
CC=clang-19 CXX=clang++-19 cargo build
axboe-liburing includes a few Rust-specific tests to verify the basic flow works but the lion's share of the testing is
in recycling what already exists in liburing proper. To this end, we need objcopy
in order to weaken the io_uring_get_sqe()
symbol present in liburing.a
. When cross-compiling, it can be important to specify the objcopy
being used. The objcopy
binary can be set via the OBJCOPY
environment variable. objcopy
ships with gcc toolchains.
Example building liburing tests with liburing-rs:
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export OBJCOPY=aarch64-linux-gnu-objcopy
export CARGO_TOOLCHAIN=aarch64-unknown-linux-gnu
make -C test liburing_rs_tests -j$(nproc)
axboe-liburing is also tested using sanitizers as well. To build the tests with sanitization enabled, one needs to enable
the sanitizers
feature. This causes the crate to build liburing.a
with sanitization enabled.
RUSTFLAGS='-Zsanitizer=address' cargo +nightly test --features sanitizers --target x86_64-unknown-linux-gnu -Zbuild-std
For building the liburing_rs_tests
target in test/Makefile
, one needs to first configure the project to use sanitization.
# for liburing_rs_test.a, needed for the C test files to link against
./configure --enable-sanitizer
# builds everything, including C the default tests which are relied upon to exist by other tests
make all -j$(nproc)
# build a static library that exports everything from the Rust crate and use a shim header in the
# test files so that they link against the Rust-derived staticlib
make -C test liburing_rs_tests -j$(nproc)
For documentation, see the man pages for the liburing package itself. The Arch Linux pages have relatively up-to-date docs: https://man.archlinux.org/listing/extra/liburing/.
Examples can be found in the main repo: https://github.com/axboe/liburing/tree/master/examples
extern crate liburing_rs;
use std::{mem::zeroed, ptr, time::Duration};
use liburing_rs::*;
#[test]
pub fn queue_init() {
// Setup the ring.
//
// We create a stack-local instance of `struct io_uring` and use
// the `io_uring_queue_init` function to initialize all of its
// fields, using a submission queue size of 64. liburing sets
// the size of the CQ to twice the SQ size by default.
//
let mut ring = unsafe { zeroed::<io_uring>() };
let ring = &raw mut ring;
let r = unsafe { io_uring_queue_init(64, ring, 0) };
assert_eq!(r, 0);
// Grab a pointer to an unused SQE from the SQ
let sqe = unsafe { io_uring_get_sqe(ring) };
assert!(!sqe.is_null());
// We're going to create an SQE that completes after a specified
// amount of time, in this case 250ms. We introduce a cast for `ts`
// because liburing expects a pointer to `__kernel_timespec` which
// is layout compatible.
//
let dur = Duration::from_millis(250);
let mut ts: timespec = dur.into();
let ts = (&raw mut ts).cast();
unsafe { io_uring_prep_timeout(sqe, ts, 0, 0) };
unsafe { io_uring_sqe_set_data64(sqe, 1234) };
// Submit the SQ to the kernel for processing. `io_uring_submit` returns
// the number of submitted entries that are going to be processed. In this case,
// we only have 1 work item.
//
let n = unsafe { io_uring_submit(ring) };
assert_eq!(n, 1);
// Grab the first CQE off the queue and make sure it has the same `user_data`
// field that we originally set.
//
let mut cqe = ptr::null_mut::<io_uring_cqe>();
unsafe { io_uring_wait_cqe(ring, &raw mut cqe) };
assert!(!cqe.is_null());
assert_eq!(unsafe { (*cqe).user_data }, 1234);
// Mark the CQE as seen so that its spot can be reused as more
// completions arrive.
//
unsafe { io_uring_cqe_seen(ring, cqe) };
// Teardown the ring.
//
unsafe { io_uring_queue_exit(ring) };
}