Skip to main content

Crate nix_bindings

Crate nix_bindings 

Source
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:

TraitWhat it meansAllowed
SendMove ownership of a value to another threadyes
!SyncShare &T with another thread (incl. via Arc)no

In practice that gives you three usage patterns:

  1. Single-threaded. The common case. Build [Context], [Store], [EvalState] on one thread and stay there. Nothing extra to do.
  2. Move to a worker. Build the wrappers on the main thread, std::thread::spawn and move them in. The destination thread becomes the new sole owner.
  3. Concurrent access. Wrap the [Context] (or higher-level wrapper) in Arc<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.