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::{check_err, string_from_callback};
136
137#[cfg(feature = "store")] mod context;
138#[cfg(feature = "store")]
139pub use context::{Context, Verbosity, is_pure_eval, nix_version};
140
141#[cfg(feature = "store")] mod store;
142#[cfg(feature = "store")]
143pub use store::{Derivation, Store, StorePath};
144
145#[cfg(feature = "expr")] mod attrs;
146#[cfg(feature = "expr")] mod eval;
147#[cfg(feature = "expr")] mod lists;
148#[cfg(feature = "expr")] mod value;
149#[cfg(feature = "expr")] mod value_ops;
150
151#[cfg(feature = "expr")]
152pub use eval::{EvalState, EvalStateBuilder};
153#[cfg(feature = "expr")] pub use value::{Value, ValueType};
154#[cfg(feature = "expr")] pub use value_ops::NixValueOps;
155
156#[cfg(feature = "external")] pub mod external;
157#[cfg(feature = "flake")] pub mod flake;
158#[cfg(feature = "primop")] pub mod primop;
159
160#[cfg(all(test, any(feature = "store", feature = "expr")))]
161mod tests {
162  #[cfg(feature = "expr")] use std::sync::Arc;
163
164  #[cfg(feature = "expr")] use serial_test::serial;
165
166  #[cfg(feature = "store")] use super::*;
167
168  #[cfg(feature = "store")]
169  #[test]
170  #[serial]
171  fn test_context_creation() {
172    let _ctx = Context::new().expect("Failed to create context");
173  }
174
175  #[cfg(feature = "store")]
176  #[test]
177  #[serial]
178  fn test_nix_version() {
179    let version = nix_version();
180    assert!(!version.is_empty(), "Version should not be empty");
181  }
182
183  #[cfg(feature = "expr")]
184  #[test]
185  #[serial]
186  fn test_eval_state_builder() {
187    let ctx = Arc::new(Context::new().expect("Failed to create context"));
188    let store =
189      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
190    let _state = EvalStateBuilder::new(&store)
191      .expect("Failed to create builder")
192      .build()
193      .expect("Failed to build state");
194  }
195
196  #[cfg(feature = "expr")]
197  #[test]
198  #[serial]
199  fn test_simple_evaluation() {
200    let ctx = Arc::new(Context::new().expect("Failed to create context"));
201    let store =
202      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
203    let state = EvalStateBuilder::new(&store)
204      .expect("Failed to create builder")
205      .build()
206      .expect("Failed to build state");
207
208    let result = state
209      .eval_from_string("1 + 2", "<eval>")
210      .expect("Failed to evaluate expression");
211
212    assert_eq!(result.value_type(), ValueType::Int);
213    assert_eq!(result.as_int().expect("Failed to get int value"), 3);
214  }
215
216  #[cfg(feature = "expr")]
217  #[test]
218  #[serial]
219  fn test_value_types() {
220    let ctx = Arc::new(Context::new().expect("Failed to create context"));
221    let store =
222      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
223    let state = EvalStateBuilder::new(&store)
224      .expect("Failed to create builder")
225      .build()
226      .expect("Failed to build state");
227
228    let int_val = state
229      .eval_from_string("42", "<eval>")
230      .expect("Failed to evaluate int");
231    assert_eq!(int_val.value_type(), ValueType::Int);
232    assert_eq!(int_val.as_int().expect("Failed to get int"), 42);
233
234    let bool_val = state
235      .eval_from_string("true", "<eval>")
236      .expect("Failed to evaluate bool");
237    assert_eq!(bool_val.value_type(), ValueType::Bool);
238    assert!(bool_val.as_bool().expect("Failed to get bool"));
239
240    let str_val = state
241      .eval_from_string("\"hello\"", "<eval>")
242      .expect("Failed to evaluate string");
243    assert_eq!(str_val.value_type(), ValueType::String);
244    assert_eq!(str_val.as_string().expect("Failed to get string"), "hello");
245  }
246
247  #[cfg(feature = "expr")]
248  #[test]
249  #[serial]
250  fn test_value_construction() {
251    let ctx = Arc::new(Context::new().expect("Failed to create context"));
252    let store =
253      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
254    let state = EvalStateBuilder::new(&store)
255      .expect("Failed to create builder")
256      .build()
257      .expect("Failed to build state");
258
259    let int_val = state.make_int(99).expect("Failed to make int");
260    assert_eq!(int_val.as_int().unwrap(), 99);
261
262    let float_val = state.make_float(2.5).expect("Failed to make float");
263    assert!((float_val.as_float().unwrap() - 2.5).abs() < 1e-9);
264
265    let bool_val = state.make_bool(true).expect("Failed to make bool");
266    assert!(bool_val.as_bool().unwrap());
267
268    let null_val = state.make_null().expect("Failed to make null");
269    assert_eq!(null_val.value_type(), ValueType::Null);
270
271    let str_val = state.make_string("hello").expect("Failed to make string");
272    assert_eq!(str_val.as_string().unwrap(), "hello");
273  }
274
275  #[cfg(feature = "expr")]
276  #[test]
277  #[serial]
278  fn test_make_list() {
279    let ctx = Arc::new(Context::new().expect("Failed to create context"));
280    let store =
281      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
282    let state = EvalStateBuilder::new(&store)
283      .expect("Failed to create builder")
284      .build()
285      .expect("Failed to build state");
286
287    let a = state.make_int(1).unwrap();
288    let b = state.make_int(2).unwrap();
289    let c = state.make_int(3).unwrap();
290
291    let list = state.make_list(&[&a, &b, &c]).expect("Failed to make list");
292    assert_eq!(list.value_type(), ValueType::List);
293    assert_eq!(list.list_len().unwrap(), 3);
294  }
295
296  #[cfg(feature = "expr")]
297  #[test]
298  #[serial]
299  fn test_make_attrs() {
300    let ctx = Arc::new(Context::new().expect("Failed to create context"));
301    let store =
302      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
303    let state = EvalStateBuilder::new(&store)
304      .expect("Failed to create builder")
305      .build()
306      .expect("Failed to build state");
307
308    let a = state.make_int(42).unwrap();
309    let b = state.make_string("hello").unwrap();
310
311    let attrs = state
312      .make_attrs(&[("answer", &a), ("greeting", &b)])
313      .expect("Failed to make attrs");
314    assert_eq!(attrs.value_type(), ValueType::Attrs);
315
316    let answer = attrs.get_attr("answer").unwrap();
317    // as_int auto-forces lazy thunks.
318    assert_eq!(answer.as_int().unwrap(), 42);
319  }
320
321  #[cfg(feature = "expr")]
322  #[test]
323  #[serial]
324  fn test_value_call() {
325    let ctx = Arc::new(Context::new().expect("Failed to create context"));
326    let store =
327      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
328    let state = EvalStateBuilder::new(&store)
329      .expect("Failed to create builder")
330      .build()
331      .expect("Failed to build state");
332
333    let f = state
334      .eval_from_string("x: x + 1", "<eval>")
335      .expect("Failed to evaluate function");
336    let arg = state.make_int(41).unwrap();
337    let result = f.call(&arg).expect("Failed to call function");
338    assert_eq!(result.as_int().unwrap(), 42);
339  }
340
341  #[cfg(feature = "expr")]
342  #[test]
343  #[serial]
344  fn test_value_copy() {
345    let ctx = Arc::new(Context::new().expect("Failed to create context"));
346    let store =
347      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
348    let state = EvalStateBuilder::new(&store)
349      .expect("Failed to create builder")
350      .build()
351      .expect("Failed to build state");
352
353    let orig = state.make_int(7).unwrap();
354    let copy = orig.copy().expect("Failed to copy value");
355    assert_eq!(copy.as_int().unwrap(), 7);
356  }
357
358  #[cfg(feature = "expr")]
359  #[test]
360  #[serial]
361  fn test_as_string_with_context_plain() {
362    let ctx = Arc::new(Context::new().expect("Failed to create context"));
363    let store =
364      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
365    let state = EvalStateBuilder::new(&store)
366      .expect("Failed to create builder")
367      .build()
368      .expect("Failed to build state");
369
370    let val = state
371      .eval_from_string("\"hello\"", "<eval>")
372      .expect("Failed to evaluate string");
373    let (s, ctx_paths) = val
374      .as_string_with_context()
375      .expect("as_string_with_context failed");
376    assert_eq!(s, "hello");
377    assert!(
378      ctx_paths.is_empty(),
379      "Plain string should have no context paths"
380    );
381  }
382
383  #[cfg(feature = "expr")]
384  #[test]
385  #[serial]
386  fn test_eval_from_file() {
387    use std::io::Write as _;
388    let ctx = Arc::new(Context::new().expect("Failed to create context"));
389    let store =
390      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
391    let state = EvalStateBuilder::new(&store)
392      .expect("Failed to create builder")
393      .build()
394      .expect("Failed to build state");
395
396    let mut tmp =
397      tempfile::NamedTempFile::new().expect("Failed to create temp file");
398    write!(tmp, "1 + 1").expect("Failed to write temp file");
399    let result = state
400      .eval_from_file(tmp.path())
401      .expect("eval_from_file failed");
402    assert_eq!(result.as_int().unwrap(), 2);
403  }
404
405  #[cfg(feature = "expr")]
406  #[test]
407  #[serial]
408  fn test_no_load_config() {
409    let ctx = Arc::new(Context::new().expect("Failed to create context"));
410    let store =
411      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
412    let state = EvalStateBuilder::new(&store)
413      .expect("Failed to create builder")
414      .no_load_config()
415      .build()
416      .expect("Failed to build state with no_load_config");
417    let val = state
418      .eval_from_string("1 + 1", "<eval>")
419      .expect("Evaluation failed");
420    assert_eq!(val.as_int().unwrap(), 2);
421  }
422}