dlsym
Bindings around the platform's dynamic library loading primitives with greatly improved memory safety. The most important safety guarantee of this library is the prevention of dangling symbols that may occur after a library is unloaded.
It comes with dlsym-gen, which turns C headers into Rust bindings (or a
whole crate) that load the library at run time: no link-time dependency, no
-sys crate, and no LLVM needed to build.
Loading symbols
use Library;
// SAFETY: libm's initialisers are trusted.
let libm = unsafe ;
// SAFETY: `cos` has this signature.
let cos = unsafe ;
// SAFETY: `cos` is defined for every `f64`.
assert_eq!;
// drop(libm) here would not compile: `cos` borrows it.
Symbol<'lib, T>borrows itsLibrary, so the borrow checker rejects unloading while a symbol is in use.- Symbols don't
Derefto the raw pointer. Functions are called withcall((args,)), variadic ones withwith(|f| f(..)), and the raw pointer only comes out throughunsafe fn into_inner. ArcSymbol<T>(fromLibrary::get_arc) keeps the library alive through anArc, andLibrary::leakgives'staticsymbols, both withouttransmute.- Only
unsafe externfunction pointers and raw data pointers can be requested:get::<u64>and safeextern "C" fntypes don't compile. - A symbol whose address is null (
NullSymbol) is told apart from a missing one (SymbolNotFound). Library::open_first([...]),Library::this(),library_filename("z"), plusos::unix::OpenOptions(RTLD_LAZY/GLOBAL/NODELETE/NOLOAD/DEEPBIND,dlvsym) andos::windows::OpenOptions(LOAD_LIBRARY_SEARCH_*, already-loaded modules; loader error dialogs are suppressed).
Binding a whole API
library! declares a struct that owns the library and every symbol, using
the same syntax as a Rust 2024 unsafe extern block:
use ;
use Bindings;
library!
}
// SAFETY: libc's initialisers are trusted.
let libc = unsafe ;
assert_eq!;
assert_eq!;
unsafe ;
Every non-#[optional] symbol is resolved when the struct is created, so a
call is a constant-index load plus the call itself. The function pointers
live inside the struct next to the library, so they cannot outlive it.
Generating bindings from C headers
Install the generator with cargo install dlsym --features codegen, or
download a prebuilt dlsym-gen (Linux, macOS universal and Windows, for
x86_64 and aarch64) from the
releases page. It needs a
libclang at run time: one from LLVM, Xcode, or your distribution's
libclang package. It is found through --libclang, $LIBCLANG_PATH,
llvm-config, or the usual install locations.
# A crate (Cargo.toml and lib.rs are only written if missing):
# Or a single module:
use *;
let z = unsafe ?; // tries each -l name in turn
let mut strm = default; // zeroed, like C's `= {0}`
let version = ZLIB_VERSION.as_ptr; // #defines become constants
let rc = unsafe ;
What it handles:
- Functions (variadic, calling conventions), global variables, structs and
unions (packed, aligned,
#pragma pack, bitfields with typed accessors, C11 anonymous members, flexible array members), enums (as newtypes, so an unexpected value from C is never undefined behaviour), typedefs and function pointers. - Object-like
#defines, evaluated by clang: integers, floats, strings and constant expressions. - Struct layouts are checked at compile time against the C compiler's
(
size_of,align_of,offset_of!). - It binds what the given headers declare, plus the headers they include
with
#include "..."(--header-diradds more). Types from elsewhere are pulled in as needed: in full when used by value, opaque when only used behind pointers. - Headers often declare functions that only some builds of a library export
(sqlite's Windows-only functions, optional features). With
-l, the generator opens the installed library and marks whatever it lacks#[optional], so the bindings still load; calling such a function returnsNone. --allow/--blockfilter by name,--optionalmarks more symbols that may be missing, and--safedeclares functions callable withoutunsafe. Unsupported items (e.g.long double) are skipped with a warning.- Tested on zlib, sqlite3, curl, libpng, xz, bzip2, zstd, expat, libarchive, libmagic, ogg, opus, libsodium, libuv, libyaml, lz4, pcre2, SDL2, Xlib, OpenGL, EGL, Vulkan, OpenSSL, libxml2, GLib, D-Bus, cairo, FreeType, ALSA, PulseAudio, ncurses, readline, libpcap, libusb, libudev, libevent, libjpeg, libtiff, libwebp, fontconfig, gdbm and libuuid: every generated module compiles.
From a build.rs, use dlsym::codegen::Builder (see the
codegen docs).
Compared to libloading
| libloading | dlsym | |
|---|---|---|
| Symbols borrow their library | ✓ | ✓ |
| Raw pointer copyable out of a symbol | via *sym |
only unsafe |
'static symbols without transmute |
ArcSymbol, leak |
|
| Symbol type checked to be a pointer | ✓ | |
Whole-API structs (library!) |
✓ | |
| Header → bindings generator | ✓ |
See ROADMAP.md for what's next.
License
MIT