Skip to main content

nix_bindings/
lib.rs

1#![warn(missing_docs)]
2// The crate intentionally uses `Arc<Context>` for shared-ownership
3// lifetime extension within a single thread. `Context` is `Send` but
4// not `Sync`, which trips clippy's `arc_with_non_send_sync` lint on
5// every `Arc::new(Context::new()?)` in the test suite even though the
6// pattern is the documented one. See the crate-level `# Thread Safety`
7// section.
8#![cfg_attr(test, allow(clippy::arc_with_non_send_sync))]
9//! High-level, safe Rust bindings for the Nix build tool.
10//!
11//! This crate provides ergonomic and idiomatic Rust APIs for interacting
12//! with Nix using its C API.
13//!
14//! # Quick Start
15//!
16//! ```no_run
17//! #[cfg(feature = "store")]
18//! {
19//!   use std::sync::Arc;
20//!
21//!   use nix_bindings::{Context, EvalStateBuilder, Store};
22//!
23//!   fn main() -> Result<(), Box<dyn std::error::Error>> {
24//!     let ctx = Arc::new(Context::new()?);
25//!     let store = Arc::new(Store::open(&ctx, None)?);
26//!     let state = EvalStateBuilder::new(&store)?.build()?;
27//!
28//!     let result = state.eval_from_string("1 + 2", "<eval>")?;
29//!     println!("Result: {}", result.as_int()?);
30//!
31//!     Ok(())
32//!   }
33//! }
34//! ```
35//!
36//! # Thread Safety
37//!
38//! The underlying Nix C API stores per-call error state on
39//! [`Context`]. Every C entry point that takes a `nix_c_context *`
40//! rewrites that buffer, so two threads sharing a [`Context`]
41//! concurrently race on it. The C++ evaluator is also not designed for
42//! concurrent mutation from multiple threads.
43//!
44//! To make this hard to misuse, every wrapper in this crate is **`Send`
45//! but not `Sync`**:
46//!
47//! | Trait   | What it means                                     | Allowed |
48//! |---------|---------------------------------------------------|---------|
49//! | `Send`  | Move ownership of a value to another thread       | yes     |
50//! | `!Sync` | Share `&T` with another thread (incl. via `Arc`)  | no      |
51//!
52//! In practice that gives you three usage patterns:
53//!
54//! 1. **Single-threaded.** The common case. Build [`Context`], [`Store`],
55//!    [`EvalState`] on one thread and stay there. Nothing extra to do.
56//! 2. **Move to a worker.** Build the wrappers on the main thread,
57//!    `std::thread::spawn` and move them in. The destination thread becomes the
58//!    new sole owner.
59//! 3. **Concurrent access.** Wrap the [`Context`] (or higher-level wrapper) in
60//!    `Arc<Mutex<_>>` yourself. The bindings will not do this for you because
61//!    most users do not need it, and the lock would hide the underlying
62//!    single-threaded contract.
63//!
64//! ## A note on `Arc<Context>`
65//!
66//! [`Store`], [`EvalState`], and the flake/primop/external types hold
67//! `Arc<Context>` so the C context lives as long as any wrapper that
68//! references it. Because [`Context`] is not `Sync`, `Arc<Context>` is
69//! not `Send` by Rust's auto-traits. The wrappers nonetheless implement
70//! `Send` through an `unsafe impl`. The unsafe assertion is: *when you
71//! move a wrapper across threads, no other thread retains an alias to
72//! the same `Arc<Context>` that it will continue to call into.*
73//!
74//! Concretely: do not clone `Arc<Context>`, build two stores from it,
75//! send one store to thread B, and keep using the other from thread A.
76//! That is a data race the compiler cannot catch. Either move both
77//! wrappers together, or put a `Mutex` in front of [`Context`].
78//!
79//! ## Callback-scoped types
80//!
81//! Inside a primop callback the trampoline hands you wrappers
82//! ([`primop::PrimOpArg`], [`primop::PrimOpRet`], [`primop::PrimOpValue`],
83//! [`primop::ArgAttrs`], [`primop::ArgList`]) that borrow raw pointers
84//! valid only for that one call. They are neither `Send` nor `Sync` by
85//! construction; do not stash them in a thread-local or send them off
86//! the trampoline.
87//!
88//! # Value Formatting
89//!
90//! Values support multiple formatting options:
91//!
92//! ```no_run
93//! #[cfg(feature = "expr")]
94//! {
95//!   use std::sync::Arc;
96//!
97//!   use nix_bindings::{Context, EvalStateBuilder, Store};
98//!   fn main() -> Result<(), Box<dyn std::error::Error>> {
99//!     let ctx = Arc::new(Context::new()?);
100//!     let store = Arc::new(Store::open(&ctx, None)?);
101//!     let state = EvalStateBuilder::new(&store)?.build()?;
102//!     let value = state.eval_from_string("\"hello world\"", "<eval>")?;
103//!
104//!     // Display formatting (user-friendly)
105//!     println!("{}", value); // => hello world
106//!
107//!     // Debug formatting (with type info)
108//!     println!("{:?}", value); // => Value::String("hello world")
109//!
110//!     // Nix syntax formatting
111//!     println!("{}", value.to_nix_string()?); // => "hello world"
112//!     //
113//!     Ok(())
114//!   }
115//! }
116//! ```
117
118/// Raw, unsafe FFI bindings to the Nix C API.
119///
120/// # Warning
121///
122/// This module exposes the low-level, unsafe C bindings. Prefer using the
123/// safe, high-level APIs provided by this crate. Use at your own risk.
124#[doc(hidden)]
125pub mod sys {
126  pub use nix_bindings_sys::*;
127}
128
129mod error;
130pub use error::{Error, Result};
131// Crate-internal re-exports so the legacy `crate::check_err` /
132// `crate::string_from_callback` paths in the module bodies keep working
133// without each module having to update its imports.
134#[cfg(feature = "store")]
135pub(crate) use error::{
136  check_err,
137  check_ptr,
138  checked_string_from_callback,
139  string_from_callback,
140};
141
142#[cfg(feature = "store")] mod context;
143#[cfg(feature = "store")]
144pub use context::{Context, Verbosity, is_pure_eval, nix_version};
145
146#[cfg(feature = "store")] mod store;
147#[cfg(feature = "store")]
148pub use store::{Derivation, Store, StorePath};
149
150#[cfg(feature = "expr")] mod attrs;
151#[cfg(feature = "expr")] mod eval;
152#[cfg(feature = "expr")] mod lists;
153#[cfg(feature = "expr")] mod value;
154#[cfg(feature = "expr")] mod value_ops;
155
156#[cfg(feature = "expr")]
157pub use eval::{EvalState, EvalStateBuilder};
158#[cfg(feature = "expr")] pub use value::{Value, ValueType};
159#[cfg(feature = "expr")] pub use value_ops::NixValueOps;
160
161#[cfg(feature = "external")] pub mod external;
162#[cfg(feature = "flake")] pub mod flake;
163#[cfg(feature = "primop")] pub mod primop;
164
165#[cfg(all(test, any(feature = "store", feature = "expr")))]
166mod tests {
167  #[cfg(feature = "expr")] use std::sync::Arc;
168
169  #[cfg(feature = "expr")] use serial_test::serial;
170
171  #[cfg(feature = "store")] use super::*;
172
173  #[cfg(feature = "store")]
174  #[test]
175  #[serial]
176  fn test_context_creation() {
177    let _ctx = Context::new().expect("Failed to create context");
178  }
179
180  #[cfg(feature = "store")]
181  #[test]
182  #[serial]
183  fn test_nix_version() {
184    let version = nix_version();
185    assert!(!version.is_empty(), "Version should not be empty");
186  }
187
188  #[cfg(feature = "expr")]
189  #[test]
190  #[serial]
191  fn test_eval_state_builder() {
192    let ctx = Arc::new(Context::new().expect("Failed to create context"));
193    let store =
194      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
195    let _state = EvalStateBuilder::new(&store)
196      .expect("Failed to create builder")
197      .build()
198      .expect("Failed to build state");
199  }
200
201  #[cfg(feature = "expr")]
202  #[test]
203  #[serial]
204  fn test_simple_evaluation() {
205    let ctx = Arc::new(Context::new().expect("Failed to create context"));
206    let store =
207      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
208    let state = EvalStateBuilder::new(&store)
209      .expect("Failed to create builder")
210      .build()
211      .expect("Failed to build state");
212
213    let result = state
214      .eval_from_string("1 + 2", "<eval>")
215      .expect("Failed to evaluate expression");
216
217    assert_eq!(result.value_type(), ValueType::Int);
218    assert_eq!(result.as_int().expect("Failed to get int value"), 3);
219  }
220
221  #[cfg(feature = "expr")]
222  #[test]
223  #[serial]
224  fn test_value_types() {
225    let ctx = Arc::new(Context::new().expect("Failed to create context"));
226    let store =
227      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
228    let state = EvalStateBuilder::new(&store)
229      .expect("Failed to create builder")
230      .build()
231      .expect("Failed to build state");
232
233    let int_val = state
234      .eval_from_string("42", "<eval>")
235      .expect("Failed to evaluate int");
236    assert_eq!(int_val.value_type(), ValueType::Int);
237    assert_eq!(int_val.as_int().expect("Failed to get int"), 42);
238
239    let bool_val = state
240      .eval_from_string("true", "<eval>")
241      .expect("Failed to evaluate bool");
242    assert_eq!(bool_val.value_type(), ValueType::Bool);
243    assert!(bool_val.as_bool().expect("Failed to get bool"));
244
245    let str_val = state
246      .eval_from_string("\"hello\"", "<eval>")
247      .expect("Failed to evaluate string");
248    assert_eq!(str_val.value_type(), ValueType::String);
249    assert_eq!(str_val.as_string().expect("Failed to get string"), "hello");
250  }
251
252  #[cfg(feature = "expr")]
253  #[test]
254  #[serial]
255  fn test_value_construction() {
256    let ctx = Arc::new(Context::new().expect("Failed to create context"));
257    let store =
258      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
259    let state = EvalStateBuilder::new(&store)
260      .expect("Failed to create builder")
261      .build()
262      .expect("Failed to build state");
263
264    let int_val = state.make_int(99).expect("Failed to make int");
265    assert_eq!(int_val.as_int().unwrap(), 99);
266
267    let float_val = state.make_float(2.5).expect("Failed to make float");
268    assert!((float_val.as_float().unwrap() - 2.5).abs() < 1e-9);
269
270    let bool_val = state.make_bool(true).expect("Failed to make bool");
271    assert!(bool_val.as_bool().unwrap());
272
273    let null_val = state.make_null().expect("Failed to make null");
274    assert_eq!(null_val.value_type(), ValueType::Null);
275
276    let str_val = state.make_string("hello").expect("Failed to make string");
277    assert_eq!(str_val.as_string().unwrap(), "hello");
278  }
279
280  #[cfg(feature = "expr")]
281  #[test]
282  #[serial]
283  fn test_make_list() {
284    let ctx = Arc::new(Context::new().expect("Failed to create context"));
285    let store =
286      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
287    let state = EvalStateBuilder::new(&store)
288      .expect("Failed to create builder")
289      .build()
290      .expect("Failed to build state");
291
292    let a = state.make_int(1).unwrap();
293    let b = state.make_int(2).unwrap();
294    let c = state.make_int(3).unwrap();
295
296    let list = state.make_list(&[&a, &b, &c]).expect("Failed to make list");
297    assert_eq!(list.value_type(), ValueType::List);
298    assert_eq!(list.list_len().unwrap(), 3);
299  }
300
301  #[cfg(feature = "expr")]
302  #[test]
303  #[serial]
304  fn test_make_attrs() {
305    let ctx = Arc::new(Context::new().expect("Failed to create context"));
306    let store =
307      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
308    let state = EvalStateBuilder::new(&store)
309      .expect("Failed to create builder")
310      .build()
311      .expect("Failed to build state");
312
313    let a = state.make_int(42).unwrap();
314    let b = state.make_string("hello").unwrap();
315
316    let attrs = state
317      .make_attrs(&[("answer", &a), ("greeting", &b)])
318      .expect("Failed to make attrs");
319    assert_eq!(attrs.value_type(), ValueType::Attrs);
320
321    let answer = attrs.get_attr("answer").unwrap();
322    // as_int auto-forces lazy thunks.
323    assert_eq!(answer.as_int().unwrap(), 42);
324  }
325
326  #[cfg(feature = "expr")]
327  #[test]
328  #[serial]
329  fn test_value_call() {
330    let ctx = Arc::new(Context::new().expect("Failed to create context"));
331    let store =
332      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
333    let state = EvalStateBuilder::new(&store)
334      .expect("Failed to create builder")
335      .build()
336      .expect("Failed to build state");
337
338    let f = state
339      .eval_from_string("x: x + 1", "<eval>")
340      .expect("Failed to evaluate function");
341    let arg = state.make_int(41).unwrap();
342    let result = f.call(&arg).expect("Failed to call function");
343    assert_eq!(result.as_int().unwrap(), 42);
344  }
345
346  #[cfg(feature = "expr")]
347  #[test]
348  #[serial]
349  fn test_value_copy() {
350    let ctx = Arc::new(Context::new().expect("Failed to create context"));
351    let store =
352      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
353    let state = EvalStateBuilder::new(&store)
354      .expect("Failed to create builder")
355      .build()
356      .expect("Failed to build state");
357
358    let orig = state.make_int(7).unwrap();
359    let copy = orig.copy().expect("Failed to copy value");
360    assert_eq!(copy.as_int().unwrap(), 7);
361  }
362
363  #[cfg(feature = "expr")]
364  #[test]
365  #[serial]
366  fn test_as_string_with_context_plain() {
367    let ctx = Arc::new(Context::new().expect("Failed to create context"));
368    let store =
369      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
370    let state = EvalStateBuilder::new(&store)
371      .expect("Failed to create builder")
372      .build()
373      .expect("Failed to build state");
374
375    let val = state
376      .eval_from_string("\"hello\"", "<eval>")
377      .expect("Failed to evaluate string");
378    let (s, ctx_paths) = val
379      .as_string_with_context()
380      .expect("as_string_with_context failed");
381    assert_eq!(s, "hello");
382    assert!(
383      ctx_paths.is_empty(),
384      "Plain string should have no context paths"
385    );
386  }
387
388  #[cfg(feature = "expr")]
389  #[test]
390  #[serial]
391  fn test_eval_from_file() {
392    use std::io::Write as _;
393    let ctx = Arc::new(Context::new().expect("Failed to create context"));
394    let store =
395      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
396    let state = EvalStateBuilder::new(&store)
397      .expect("Failed to create builder")
398      .build()
399      .expect("Failed to build state");
400
401    let mut tmp =
402      tempfile::NamedTempFile::new().expect("Failed to create temp file");
403    write!(tmp, "1 + 1").expect("Failed to write temp file");
404    let result = state
405      .eval_from_file(tmp.path())
406      .expect("eval_from_file failed");
407    assert_eq!(result.as_int().unwrap(), 2);
408  }
409
410  #[cfg(feature = "expr")]
411  #[test]
412  #[serial]
413  fn test_no_load_config() {
414    let ctx = Arc::new(Context::new().expect("Failed to create context"));
415    let store =
416      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
417    let state = EvalStateBuilder::new(&store)
418      .expect("Failed to create builder")
419      .no_load_config()
420      .build()
421      .expect("Failed to build state with no_load_config");
422    let val = state
423      .eval_from_string("1 + 1", "<eval>")
424      .expect("Evaluation failed");
425    assert_eq!(val.as_int().unwrap(), 2);
426  }
427}