nix-bindings
This crate provides a safe, ergonomic Rust API built on top of
nix-bindings-sys. It wraps the raw FFI calls in idiomatic Rust types with
automatic resource management, type-safe conversions, and comprehensive error
handling. The crate is organized into the following modules, each gated by a
Cargo feature:
store(storefeature): Store, store path, and derivation management (opening stores, parsing store paths, realizing derivations, copying closures)attrs(requiresexprfeature): Attribute set access (get_attr,has_attr,attr_keys,AttrIterator)lists(requiresexprfeature): List operations (list_len,list_get,list_iter,ListIterator)flake(flake): Flake support (FlakeSettings,FlakeReference,LockedFlake,LockFlags,FetchersSettings)primop(primop): Custom Nix primitive operations via Rust closures (global builtins or value-embedded)external(external): Embed arbitrary Rust values as Nix external values with safe downcasting
A few key types are provided at the crate root and are gated by the store and
expr features (both included by default). Namely: Context, EvalState,
EvalStateBuilder, Store, StorePath, Derivation, Value and verbosity
may be used to manage C API context lifetime, expression evaluation, store
handle, derivation from JSON and Nix value with type-safe accessors
specifically. See crate documentation for more details.
Usage
Add nix-bindings to your Cargo.toml:
# Cargo.toml
[]
= "0.2347.3"
The full crate (default) enables all modules. To exclude features you don't need, disable defaults and pick:
[]
= { = "0.2347.3", = false, = ["store", "primop"] }
Available features are store, expr (implies store), flake, external,
primop. util and main pass through to the underlying sys crate but do not
gate any high-level modules. full (default) enables everything.
Quick example evaluating a Nix expression:
use Arc;
use ;
let ctx = new;
let store = new;
let state = new?.build?;
let result = state.eval_from_string?;
println!;
Registering a custom primop as a global builtin (must happen before any
EvalState is built):
use Arc;
use ;
let ctx = new;
new?
.register?;
let store = new;
let state = new?.build?;
let result = state.eval_from_string?;
assert_eq!;
Thread safety
All public wrapper types (Context, EvalState, Store, StorePath,
Derivation, Value, primop and flake wrappers) are Send but not Sync. You
can move them across threads, but you cannot share &T across threads.
The reason is nix_c_context: every nix_* call mutates a per-context error
buffer, so sharing &Context would race on that buffer. The crate makes the
public surface !Sync by construction so this is a compile error rather than a
runtime data race.
Arc<Context> is the documented lifetime-sharing pattern. It is for shared
ownership, not shared access: if you Arc::clone a Context and move both
halves to different threads, you must drop all but one half before any thread
touches a wrapper that holds it. The full contract is in the # Thread Safety
section at the top of src/lib.rs.
Testing
nix-bindings includes both unit tests (inline in each module) and integration
tests in tests/. Tests are gated by the same features
as the modules they exercise. Run them with:
# Full test suite
# Tests for a specific feature set
# You may use plain cargo test if you don't have cargo-nextest
The test suite is rather large, and it covers a lot including:
- Store operations (open, query URI/dir/version, path validation)
- Expression evaluation (arithmetic, strings, booleans, functions, interpolation)
- Value construction and type conversion (int, float, bool, string, null)
- Attribute set manipulation (get, has, keys, iteration)
- List operations (length, indexing, iteration)
- String context handling (plain strings, store-path contexts)
- Derivation realization
- Resource cleanup (ensuring no leaks across repeated create/drop cycles)
- Flake settings and lock flags
- Custom primops (value-embedded)
- External values (creation, downcast, type-safe retrieval)
- Error handling (parse errors, type mismatches)