axboe-liburing

Crates.ioaxboe-liburing
lib.rsaxboe-liburing
version2.12.0
created_at2025-03-02 14:43:37.16073+00
updated_at2025-08-24 17:55:25.551447+00
descriptionA Rust transliteration of axboe's liburing
homepage
repositoryhttps://github.com/cmazakas/liburing
max_upload_size
id1574643
size310,573
Christian Mazakas (cmazakas)

documentation

https://man.archlinux.org/listing/extra/liburing/

README

axboe-liburing

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

Building Tests

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)

Docs

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

Example

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) };
}
Commit count: 3375

cargo fmt