Expand description
High-level, safe Rust bindings for the Nix build tool.
This crate provides ergonomic and idiomatic Rust APIs for interacting with Nix using its C API.
§Quick Start
#[cfg(feature = "store")]
{
use std::sync::Arc;
use nix_bindings::{Context, EvalStateBuilder, Store};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let ctx = Arc::new(Context::new()?);
let store = Arc::new(Store::open(&ctx, None)?);
let state = EvalStateBuilder::new(&store)?.build()?;
let result = state.eval_from_string("1 + 2", "<eval>")?;
println!("Result: {}", result.as_int()?);
Ok(())
}
}§Thread Safety
The underlying Nix C API stores per-call error state on
[Context]. Every C entry point that takes a nix_c_context *
rewrites that buffer, so two threads sharing a [Context]
concurrently race on it. The C++ evaluator is also not designed for
concurrent mutation from multiple threads.
To make this hard to misuse, every wrapper in this crate is Send
but not Sync:
| Trait | What it means | Allowed |
|---|---|---|
Send | Move ownership of a value to another thread | yes |
!Sync | Share &T with another thread (incl. via Arc) | no |
In practice that gives you three usage patterns:
- Single-threaded. The common case. Build [
Context], [Store], [EvalState] on one thread and stay there. Nothing extra to do. - Move to a worker. Build the wrappers on the main thread,
std::thread::spawnand move them in. The destination thread becomes the new sole owner. - Concurrent access. Wrap the [
Context] (or higher-level wrapper) inArc<Mutex<_>>yourself. The bindings will not do this for you because most users do not need it, and the lock would hide the underlying single-threaded contract.
§A note on Arc<Context>
[Store], [EvalState], and the flake/primop/external types hold
Arc<Context> so the C context lives as long as any wrapper that
references it. Because [Context] is not Sync, Arc<Context> is
not Send by Rust’s auto-traits. The wrappers nonetheless implement
Send through an unsafe impl. The unsafe assertion is: when you
move a wrapper across threads, no other thread retains an alias to
the same Arc<Context> that it will continue to call into.
Concretely: do not clone Arc<Context>, build two stores from it,
send one store to thread B, and keep using the other from thread A.
That is a data race the compiler cannot catch. Either move both
wrappers together, or put a Mutex in front of [Context].
§Callback-scoped types
Inside a primop callback the trampoline hands you wrappers
([primop::PrimOpArg], [primop::PrimOpRet], [primop::PrimOpValue],
[primop::ArgAttrs], [primop::ArgList]) that borrow raw pointers
valid only for that one call. They are neither Send nor Sync by
construction; do not stash them in a thread-local or send them off
the trampoline.
§Value Formatting
Values support multiple formatting options:
#[cfg(feature = "expr")]
{
use std::sync::Arc;
use nix_bindings::{Context, EvalStateBuilder, Store};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let ctx = Arc::new(Context::new()?);
let store = Arc::new(Store::open(&ctx, None)?);
let state = EvalStateBuilder::new(&store)?.build()?;
let value = state.eval_from_string("\"hello world\"", "<eval>")?;
// Display formatting (user-friendly)
println!("{}", value); // => hello world
// Debug formatting (with type info)
println!("{:?}", value); // => Value::String("hello world")
// Nix syntax formatting
println!("{}", value.to_nix_string()?); // => "hello world"
//
Ok(())
}
}Enums§
- Error
- Error types for Nix operations.
Type Aliases§
- Result
- Result type for Nix operations.