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
118use std::fmt;
119#[cfg(any(
120  feature = "store",
121  feature = "expr",
122  feature = "flake",
123  feature = "external",
124  feature = "primop"
125))]
126use std::{
127  ffi::{CStr, CString},
128  ptr::NonNull,
129};
130#[cfg(any(
131  feature = "expr",
132  feature = "flake",
133  feature = "external",
134  feature = "primop"
135))]
136use std::{path::Path, sync::Arc};
137
138#[cfg(feature = "expr")] mod attrs;
139#[cfg(feature = "expr")] mod lists;
140
141#[cfg(feature = "external")] pub mod external;
142#[cfg(feature = "flake")] pub mod flake;
143#[cfg(feature = "primop")] pub mod primop;
144
145#[cfg(all(test, any(feature = "store", feature = "expr")))]
146use serial_test::serial;
147
148/// Raw, unsafe FFI bindings to the Nix C API.
149///
150/// # Warning
151///
152/// This module exposes the low-level, unsafe C bindings. Prefer using the safe,
153/// high-level APIs provided by this crate. Use at your own risk.
154#[doc(hidden)]
155pub mod sys {
156  pub use nix_bindings_sys::*;
157}
158
159/// Result type for Nix operations.
160pub type Result<T> = std::result::Result<T, Error>;
161
162/// Error types for Nix operations.
163#[derive(Debug)]
164pub enum Error {
165  /// Unknown error from Nix C API.
166  Unknown(String),
167
168  /// Overflow error.
169  Overflow,
170
171  /// Key not found error.
172  KeyNotFound(String),
173
174  /// List index out of bounds.
175  IndexOutOfBounds {
176    /// The index that was requested.
177    index:  usize,
178    /// The actual length of the list.
179    length: usize,
180  },
181
182  /// Nix evaluation error.
183  EvalError(String),
184
185  /// Invalid value type conversion.
186  InvalidType {
187    /// Expected type.
188    expected: &'static str,
189    /// Actual type.
190    actual:   String,
191  },
192  /// Null pointer error.
193  NullPointer,
194
195  /// String conversion error.
196  StringConversion(std::ffi::NulError),
197}
198
199impl fmt::Display for Error {
200  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201    match self {
202      Error::Unknown(msg) => write!(f, "Unknown error: {msg}"),
203      Error::Overflow => write!(f, "Overflow error"),
204      Error::KeyNotFound(key) => write!(f, "Key not found: {key}"),
205      Error::IndexOutOfBounds { index, length } => {
206        write!(f, "Index out of bounds: index {index}, length {length}")
207      },
208      Error::EvalError(msg) => write!(f, "Evaluation error: {msg}"),
209      Error::InvalidType { expected, actual } => {
210        write!(f, "Invalid type: expected {expected}, got {actual}")
211      },
212      Error::NullPointer => write!(f, "Null pointer error"),
213      Error::StringConversion(e) => write!(f, "String conversion error: {e}"),
214    }
215  }
216}
217
218impl std::error::Error for Error {}
219
220impl From<std::ffi::NulError> for Error {
221  fn from(e: std::ffi::NulError) -> Self {
222    Error::StringConversion(e)
223  }
224}
225
226#[cfg(feature = "store")] mod store;
227#[cfg(feature = "store")]
228pub use store::{Derivation, Store, StorePath};
229
230/// Extract a string from a Nix context using a callback-based API.
231///
232/// Many Nix C API functions return strings via callbacks. This helper
233/// makes that pattern ergonomic.
234///
235/// # Safety
236///
237/// `call` must invoke `callback` with a valid string pointer and length.
238#[cfg(feature = "store")]
239unsafe fn string_from_callback<F>(call: F) -> Option<String>
240where
241  F: FnOnce(sys::nix_get_string_callback, *mut std::os::raw::c_void),
242{
243  unsafe extern "C" fn collect(
244    start: *const std::os::raw::c_char,
245    n: std::os::raw::c_uint,
246    user_data: *mut std::os::raw::c_void,
247  ) {
248    let result = unsafe { &mut *(user_data as *mut Option<String>) };
249    if !start.is_null() {
250      let bytes =
251        unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
252      *result = std::str::from_utf8(bytes).ok().map(|s| s.to_owned());
253    }
254  }
255
256  let mut result: Option<String> = None;
257  let user_data = &mut result as *mut _ as *mut std::os::raw::c_void;
258  call(Some(collect), user_data);
259  result
260}
261
262/// Returns `true` when Nix is running in pure evaluation mode (`--pure-eval`).
263///
264/// Reads the global `pure-eval` Nix setting.  The result reflects the setting
265/// at call time and is stable for the lifetime of a single Nix evaluation.
266///
267/// # Use in primops
268///
269/// [`primop::PrimOpRet::set_path`] and [`primop::PrimOpRet::make_path`] call
270/// `nix_init_path_string` internally, which the Nix evaluator rejects for
271/// absolute paths in pure mode.  Check this function first so your primop can
272/// make the right call: return an explicit error, use a string, or refuse to
273/// run at all.
274#[cfg(feature = "store")]
275pub fn is_pure_eval() -> bool {
276  // SAFETY: nix_setting_get is safe with a null context; errors are silently
277  // ignored, and a missing key means the default (false).
278  unsafe {
279    let val = string_from_callback(|cb, ud| {
280      sys::nix_setting_get(std::ptr::null_mut(), c"pure-eval".as_ptr(), cb, ud);
281    });
282    val.as_deref() == Some("true")
283  }
284}
285
286/// Check a Nix error code and convert to `Result`, extracting the real
287/// error message from the context.
288#[cfg(feature = "store")]
289fn check_err(ctx: *mut sys::nix_c_context, err: sys::nix_err) -> Result<()> {
290  if err == sys::nix_err_NIX_OK {
291    return Ok(());
292  }
293
294  // Extract the real error message from the context.
295  // nix_err_msg returns a borrowed pointer valid until the next Nix call.
296  // We must copy it to a String immediately.
297  let msg = unsafe {
298    let ptr = sys::nix_err_msg(std::ptr::null_mut(), ctx, std::ptr::null_mut());
299    if ptr.is_null() {
300      None
301    } else {
302      Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
303    }
304  };
305
306  // For NIX_ERR_NIX_ERROR, also try to get the richer info message.
307  let detail = if err == sys::nix_err_NIX_ERR_NIX_ERROR {
308    unsafe {
309      string_from_callback(|cb, ud| {
310        sys::nix_err_info_msg(std::ptr::null_mut(), ctx, cb, ud);
311      })
312    }
313  } else {
314    None
315  };
316
317  let message = detail
318    .or(msg)
319    .unwrap_or_else(|| format!("Nix error code: {err}"));
320
321  match err {
322    sys::nix_err_NIX_ERR_UNKNOWN => Err(Error::Unknown(message)),
323    sys::nix_err_NIX_ERR_OVERFLOW => Err(Error::Overflow),
324    sys::nix_err_NIX_ERR_KEY => Err(Error::KeyNotFound(message)),
325    sys::nix_err_NIX_ERR_NIX_ERROR => Err(Error::EvalError(message)),
326    _ => Err(Error::Unknown(message)),
327  }
328}
329
330/// Return the version of the Nix library being used.
331///
332/// This is a free function that does not require a context.
333#[cfg(feature = "store")]
334#[must_use]
335pub fn nix_version() -> &'static str {
336  // SAFETY: nix_version_get returns a pointer to a static string literal
337  unsafe {
338    let ptr = sys::nix_version_get();
339    if ptr.is_null() {
340      "<unknown>"
341    } else {
342      CStr::from_ptr(ptr).to_str().unwrap_or("<unknown>")
343    }
344  }
345}
346
347/// Verbosity level for Nix log output.
348#[cfg(feature = "store")]
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub enum Verbosity {
351  /// Only errors.
352  Error,
353  /// Warnings and errors.
354  Warn,
355  /// Notices, warnings, and errors.
356  Notice,
357  /// Info messages.
358  Info,
359  /// Talkative output.
360  Talkative,
361  /// Chatty output.
362  Chatty,
363  /// Debug output.
364  Debug,
365  /// Maximum verbosity (vomit).
366  Vomit,
367}
368
369#[cfg(feature = "store")]
370impl Verbosity {
371  fn to_c(self) -> sys::nix_verbosity {
372    match self {
373      Verbosity::Error => sys::nix_verbosity_NIX_LVL_ERROR,
374      Verbosity::Warn => sys::nix_verbosity_NIX_LVL_WARN,
375      Verbosity::Notice => sys::nix_verbosity_NIX_LVL_NOTICE,
376      Verbosity::Info => sys::nix_verbosity_NIX_LVL_INFO,
377      Verbosity::Talkative => sys::nix_verbosity_NIX_LVL_TALKATIVE,
378      Verbosity::Chatty => sys::nix_verbosity_NIX_LVL_CHATTY,
379      Verbosity::Debug => sys::nix_verbosity_NIX_LVL_DEBUG,
380      Verbosity::Vomit => sys::nix_verbosity_NIX_LVL_VOMIT,
381    }
382  }
383}
384
385/// Nix context for managing library state.
386///
387/// This is the root object for all Nix operations. It manages the lifetime
388/// of the Nix C API context and provides automatic cleanup.
389#[cfg(feature = "store")]
390pub struct Context {
391  inner: NonNull<sys::nix_c_context>,
392}
393
394#[cfg(feature = "store")]
395impl Context {
396  /// Create a new Nix context.
397  ///
398  /// This initializes the Nix C API context and the required libraries.
399  ///
400  /// # Errors
401  ///
402  /// Returns an error if context creation or library initialization fails.
403  pub fn new() -> Result<Self> {
404    // SAFETY: nix_c_context_create is safe to call
405    let ctx_ptr = unsafe { sys::nix_c_context_create() };
406    let inner = NonNull::new(ctx_ptr).ok_or(Error::NullPointer)?;
407
408    let ctx = Context { inner };
409
410    // The nix_lib*_init functions are documented as one-shot. Reinvoking
411    // them across Contexts is a waste at best and a race at worst; the
412    // Once gate ensures exactly one initialization per process.
413    static INIT: std::sync::Once = std::sync::Once::new();
414    let mut init_err: Result<()> = Ok(());
415    INIT.call_once(|| {
416      // SAFETY: nix_lib*_init are safe with a fresh context.
417      let r = (|| unsafe {
418        check_err(
419          ctx.inner.as_ptr(),
420          sys::nix_libutil_init(ctx.inner.as_ptr()),
421        )?;
422        check_err(
423          ctx.inner.as_ptr(),
424          sys::nix_libstore_init(ctx.inner.as_ptr()),
425        )?;
426        check_err(
427          ctx.inner.as_ptr(),
428          sys::nix_libexpr_init(ctx.inner.as_ptr()),
429        )
430      })();
431      init_err = r;
432    });
433    init_err?;
434
435    Ok(ctx)
436  }
437
438  /// Set a global Nix configuration setting.
439  ///
440  /// Settings take effect for new [`EvalState`] instances. Use
441  /// `"extra-<name>"` to append to an existing setting's value.
442  ///
443  /// # Errors
444  ///
445  /// Returns [`Error::KeyNotFound`] if the setting key is unknown.
446  pub fn set_setting(&self, key: &str, value: &str) -> Result<()> {
447    let key_c = CString::new(key)?;
448    let value_c = CString::new(value)?;
449    // SAFETY: context and strings are valid
450    unsafe {
451      check_err(
452        self.inner.as_ptr(),
453        sys::nix_setting_set(
454          self.inner.as_ptr(),
455          key_c.as_ptr(),
456          value_c.as_ptr(),
457        ),
458      )
459    }
460  }
461
462  /// Get the value of a global Nix configuration setting.
463  ///
464  /// # Errors
465  ///
466  /// Returns [`Error::KeyNotFound`] if the setting key is unknown.
467  pub fn get_setting(&self, key: &str) -> Result<String> {
468    let key_c = CString::new(key)?;
469    let mut err_code = sys::nix_err_NIX_OK;
470    // SAFETY: context and key are valid
471    let result = unsafe {
472      string_from_callback(|cb, ud| {
473        err_code =
474          sys::nix_setting_get(self.inner.as_ptr(), key_c.as_ptr(), cb, ud);
475      })
476    };
477    check_err(self.inner.as_ptr(), err_code)?;
478    result.ok_or_else(|| Error::KeyNotFound(key.to_string()))
479  }
480
481  /// Set the verbosity level for Nix log output.
482  ///
483  /// # Errors
484  ///
485  /// Returns an error if the verbosity level cannot be set.
486  pub fn set_verbosity(&self, level: Verbosity) -> Result<()> {
487    // SAFETY: context is valid
488    unsafe {
489      check_err(
490        self.inner.as_ptr(),
491        sys::nix_set_verbosity(self.inner.as_ptr(), level.to_c()),
492      )
493    }
494  }
495
496  /// Get the raw context pointer.
497  ///
498  /// # Safety
499  ///
500  /// The caller must ensure the pointer is used safely.
501  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_c_context {
502    self.inner.as_ptr()
503  }
504}
505
506#[cfg(feature = "store")]
507impl Drop for Context {
508  fn drop(&mut self) {
509    // SAFETY: We own the context and it's valid until drop
510    unsafe {
511      sys::nix_c_context_free(self.inner.as_ptr());
512    }
513  }
514}
515
516// SAFETY: `nix_c_context` is exclusively-owned mutable state. Moving the
517// pointer to another thread is sound provided the source thread releases
518// ownership; the Box-like value semantics of `Context` already enforce
519// that at the type level. `Sync` is NOT implemented: every C entry point
520// rewrites the per-context error buffer, so two threads holding
521// `&Context` would race on that buffer with no synchronization.
522#[cfg(feature = "store")]
523unsafe impl Send for Context {}
524
525/// Builder for Nix evaluation state.
526///
527/// This allows configuring the evaluation environment before creating
528/// the evaluation state.
529#[cfg(feature = "expr")]
530pub struct EvalStateBuilder {
531  inner:     NonNull<sys::nix_eval_state_builder>,
532  store:     Arc<Store>,
533  context:   Arc<Context>,
534  skip_load: bool,
535}
536
537#[cfg(feature = "expr")]
538impl EvalStateBuilder {
539  /// Create a new evaluation state builder.
540  ///
541  /// # Arguments
542  ///
543  /// * `store` - The Nix store to use for evaluation
544  ///
545  /// # Errors
546  ///
547  /// Returns an error if the builder cannot be created.
548  pub fn new(store: &Arc<Store>) -> Result<Self> {
549    // SAFETY: store context and store are valid
550    let builder_ptr = unsafe {
551      sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr())
552    };
553
554    let inner = NonNull::new(builder_ptr).ok_or(Error::NullPointer)?;
555
556    Ok(EvalStateBuilder {
557      inner,
558      store: Arc::clone(store),
559      context: Arc::clone(&store._context),
560      skip_load: false,
561    })
562  }
563
564  /// Set the lookup path (`NIX_PATH`) for `<...>` expressions.
565  ///
566  /// Each entry should be in the form `"name=path"` or just `"path"`,
567  /// matching the format of `NIX_PATH` entries.
568  ///
569  /// # Errors
570  ///
571  /// Returns an error if the lookup path cannot be set.
572  pub fn set_lookup_path(self, paths: &[impl AsRef<str>]) -> Result<Self> {
573    // Build null-terminated array of C strings.
574    let c_strings: Vec<CString> = paths
575      .iter()
576      .map(|s| CString::new(s.as_ref()))
577      .collect::<std::result::Result<_, _>>()?;
578
579    let mut ptrs: Vec<*const std::os::raw::c_char> =
580      c_strings.iter().map(|cs| cs.as_ptr()).collect();
581    ptrs.push(std::ptr::null()); // null terminator
582
583    // SAFETY: context and builder are valid, ptrs is null-terminated
584    unsafe {
585      check_err(
586        self.context.as_ptr(),
587        sys::nix_eval_state_builder_set_lookup_path(
588          self.context.as_ptr(),
589          self.inner.as_ptr(),
590          ptrs.as_mut_ptr(),
591        ),
592      )?;
593    }
594
595    Ok(self)
596  }
597
598  /// Apply flake settings to the evaluation state builder.
599  ///
600  /// This enables `builtins.getFlake` and related flake functionality
601  /// in the resulting [`EvalState`].
602  ///
603  /// # Errors
604  ///
605  /// Returns an error if the flake settings cannot be applied.
606  #[cfg(feature = "flake")]
607  pub fn with_flake_settings(
608    self,
609    settings: &flake::FlakeSettings,
610  ) -> Result<Self> {
611    // SAFETY: context, settings, and builder are valid
612    unsafe {
613      check_err(
614        self.context.as_ptr(),
615        sys::nix_flake_settings_add_to_eval_state_builder(
616          self.context.as_ptr(),
617          settings.as_ptr(),
618          self.inner.as_ptr(),
619        ),
620      )?;
621    }
622
623    Ok(self)
624  }
625
626  /// Skip loading Nix configuration from the environment.
627  ///
628  /// By default [`build`](Self::build) calls `nix_eval_state_builder_load` to
629  /// read configuration from environment variables and config files. Call
630  /// this method to skip that step, which is useful in tests or sandboxed
631  /// environments.
632  #[must_use]
633  pub fn no_load_config(mut self) -> Self {
634    self.skip_load = true;
635    self
636  }
637
638  /// Build the evaluation state.
639  ///
640  /// # Errors
641  ///
642  /// Returns an error if the evaluation state cannot be built.
643  pub fn build(self) -> Result<EvalState> {
644    // Load configuration from environment first (unless suppressed).
645    // SAFETY: context and builder are valid
646    if !self.skip_load {
647      unsafe {
648        check_err(
649          self.context.as_ptr(),
650          sys::nix_eval_state_builder_load(
651            self.context.as_ptr(),
652            self.inner.as_ptr(),
653          ),
654        )?;
655      }
656    }
657
658    // Build the state
659    // SAFETY: context and builder are valid
660    let state_ptr = unsafe {
661      sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr())
662    };
663
664    let inner = NonNull::new(state_ptr).ok_or(Error::NullPointer)?;
665
666    Ok(EvalState {
667      inner,
668      store: self.store.clone(),
669      context: self.context.clone(),
670    })
671  }
672}
673
674#[cfg(feature = "expr")]
675impl Drop for EvalStateBuilder {
676  fn drop(&mut self) {
677    // SAFETY: We own the builder and it's valid until drop
678    unsafe {
679      sys::nix_eval_state_builder_free(self.inner.as_ptr());
680    }
681  }
682}
683
684/// Nix evaluation state for evaluating expressions.
685///
686/// This provides the main interface for evaluating Nix expressions
687/// and creating values.
688#[cfg(feature = "expr")]
689pub struct EvalState {
690  pub(crate) inner:   NonNull<sys::EvalState>,
691  #[expect(dead_code, reason = "keeps the Arc<Store> alive Drop side-effects")]
692  store:              Arc<Store>,
693  pub(crate) context: Arc<Context>,
694}
695
696#[cfg(feature = "expr")]
697impl EvalState {
698  /// Evaluate a Nix expression from a string.
699  ///
700  /// # Arguments
701  ///
702  /// * `expr` - The Nix expression to evaluate
703  /// * `path` - The path to use for error reporting (e.g., `"<eval>"`)
704  ///
705  /// # Errors
706  ///
707  /// Returns an error if evaluation fails.
708  pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>> {
709    let expr_c = CString::new(expr)?;
710    let path_c = CString::new(path)?;
711
712    // Allocate value for result
713    // SAFETY: context and state are valid
714    let value_ptr = unsafe {
715      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
716    };
717    if value_ptr.is_null() {
718      return Err(Error::NullPointer);
719    }
720
721    // Evaluate expression
722    // SAFETY: all pointers are valid
723    unsafe {
724      check_err(
725        self.context.as_ptr(),
726        sys::nix_expr_eval_from_string(
727          self.context.as_ptr(),
728          self.inner.as_ptr(),
729          expr_c.as_ptr(),
730          path_c.as_ptr(),
731          value_ptr,
732        ),
733      )?;
734    }
735
736    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
737
738    Ok(Value { inner, state: self })
739  }
740
741  /// Evaluate a Nix expression from a file.
742  ///
743  /// Reads the file at `path`, then evaluates its contents using the parent
744  /// directory as the base path for relative imports.
745  ///
746  /// # Errors
747  ///
748  /// Returns an error if the file cannot be read or if evaluation fails.
749  pub fn eval_from_file(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
750    let path = path.as_ref();
751    let expr = std::fs::read_to_string(path).map_err(|e| {
752      Error::Unknown(format!("Failed to read file {}: {e}", path.display()))
753    })?;
754    let base_path = path.parent().unwrap_or_else(|| Path::new("."));
755    // The C API takes a NUL-terminated path string. On Unix paths can carry
756    // non-UTF-8 bytes; preserve them by going through OsStr/bytes.
757    #[cfg(unix)]
758    let base_cstring = {
759      use std::os::unix::ffi::OsStrExt as _;
760      CString::new(base_path.as_os_str().as_bytes())?
761    };
762    #[cfg(not(unix))]
763    let base_cstring = {
764      let base = base_path.to_str().ok_or_else(|| {
765        Error::Unknown("File path is not valid UTF-8".to_string())
766      })?;
767      CString::new(base)?
768    };
769    // eval_from_string also wants a UTF-8 base path because it takes &str,
770    // so on non-UTF-8 paths convert via lossy fallback only for the error
771    // reporting label.
772    let base_str = base_cstring.to_string_lossy();
773    self.eval_from_string(&expr, &base_str)
774  }
775
776  /// Allocate a new uninitialized value.
777  ///
778  /// # Errors
779  ///
780  /// Returns an error if value allocation fails.
781  pub fn alloc_value(&self) -> Result<Value<'_>> {
782    // SAFETY: context and state are valid
783    let value_ptr = unsafe {
784      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
785    };
786    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
787
788    Ok(Value { inner, state: self })
789  }
790
791  /// Create a Nix integer value.
792  ///
793  /// # Errors
794  ///
795  /// Returns an error if value allocation or initialization fails.
796  pub fn make_int(&self, i: i64) -> Result<Value<'_>> {
797    let v = self.alloc_value()?;
798    // SAFETY: context and value are valid
799    unsafe {
800      check_err(
801        self.context.as_ptr(),
802        sys::nix_init_int(self.context.as_ptr(), v.inner.as_ptr(), i),
803      )?;
804    }
805    Ok(v)
806  }
807
808  /// Create a Nix float value.
809  ///
810  /// # Errors
811  ///
812  /// Returns an error if value allocation or initialization fails.
813  pub fn make_float(&self, f: f64) -> Result<Value<'_>> {
814    let v = self.alloc_value()?;
815    // SAFETY: context and value are valid
816    unsafe {
817      check_err(
818        self.context.as_ptr(),
819        sys::nix_init_float(self.context.as_ptr(), v.inner.as_ptr(), f),
820      )?;
821    }
822    Ok(v)
823  }
824
825  /// Create a Nix boolean value.
826  ///
827  /// # Errors
828  ///
829  /// Returns an error if value allocation or initialization fails.
830  pub fn make_bool(&self, b: bool) -> Result<Value<'_>> {
831    let v = self.alloc_value()?;
832    // SAFETY: context and value are valid
833    unsafe {
834      check_err(
835        self.context.as_ptr(),
836        sys::nix_init_bool(self.context.as_ptr(), v.inner.as_ptr(), b),
837      )?;
838    }
839    Ok(v)
840  }
841
842  /// Create a Nix null value.
843  ///
844  /// # Errors
845  ///
846  /// Returns an error if value allocation or initialization fails.
847  pub fn make_null(&self) -> Result<Value<'_>> {
848    let v = self.alloc_value()?;
849    // SAFETY: context and value are valid
850    unsafe {
851      check_err(
852        self.context.as_ptr(),
853        sys::nix_init_null(self.context.as_ptr(), v.inner.as_ptr()),
854      )?;
855    }
856    Ok(v)
857  }
858
859  /// Create a Nix string value.
860  ///
861  /// # Errors
862  ///
863  /// Returns an error if value allocation, string conversion, or
864  /// initialization fails.
865  pub fn make_string(&self, s: &str) -> Result<Value<'_>> {
866    let v = self.alloc_value()?;
867    let s_c = CString::new(s)?;
868    // SAFETY: context and value are valid
869    unsafe {
870      check_err(
871        self.context.as_ptr(),
872        sys::nix_init_string(
873          self.context.as_ptr(),
874          v.inner.as_ptr(),
875          s_c.as_ptr(),
876        ),
877      )?;
878    }
879    Ok(v)
880  }
881
882  /// Create a Nix path value.
883  ///
884  /// # Pure Evaluation
885  ///
886  /// In pure-eval mode (`--pure-eval`) the Nix evaluator wraps the
887  /// filesystem in an `AllowListSourceAccessor` that rejects any
888  /// unregistered absolute path. When the `shim` feature is enabled this
889  /// method automatically registers absolute paths via the shim's
890  /// `nix_eval_state_allow_path` before constructing the value, mirroring
891  /// what Nix's own fetch builtins do. Without `shim` you must arrange
892  /// for allowPath yourself, or [`is_pure_eval`] returns true.
893  ///
894  /// # Errors
895  ///
896  /// Returns an error if value allocation, path conversion, or
897  /// initialization fails.
898  pub fn make_path(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
899    let v = self.alloc_value()?;
900    let path_str = path
901      .as_ref()
902      .to_str()
903      .ok_or_else(|| Error::Unknown("Path is not valid UTF-8".to_string()))?;
904    let path_c = CString::new(path_str)?;
905
906    // In pure-eval mode the evaluator wraps the filesystem in an
907    // AllowListSourceAccessor and rejects unregistered absolute paths
908    // that match the store directory. We auto-register only paths whose
909    // string starts with "/nix/store/" because the C++ allowPath call
910    // requires a parseable store path; anything else would throw a C++
911    // exception that crosses the FFI as undefined behaviour.
912    #[cfg(feature = "shim")]
913    if path.as_ref().is_absolute()
914      && path_str.starts_with("/nix/store/")
915      && is_pure_eval()
916    {
917      // SAFETY: context, state, and path are valid.
918      unsafe {
919        check_err(
920          self.context.as_ptr(),
921          sys::nix_eval_state_allow_path(
922            self.context.as_ptr(),
923            self.inner.as_ptr(),
924            path_c.as_ptr(),
925          ),
926        )?;
927      }
928    }
929
930    // SAFETY: context, state, and value are valid
931    unsafe {
932      check_err(
933        self.context.as_ptr(),
934        sys::nix_init_path_string(
935          self.context.as_ptr(),
936          self.inner.as_ptr(),
937          v.inner.as_ptr(),
938          path_c.as_ptr(),
939        ),
940      )?;
941    }
942    Ok(v)
943  }
944
945  /// Create a Nix list value from a slice of values.
946  ///
947  /// # Errors
948  ///
949  /// Returns an error if value allocation or list construction fails.
950  pub fn make_list(&self, items: &[&Value<'_>]) -> Result<Value<'_>> {
951    // SAFETY: context and state are valid
952    let builder = unsafe {
953      sys::nix_make_list_builder(
954        self.context.as_ptr(),
955        self.inner.as_ptr(),
956        items.len(),
957      )
958    };
959    if builder.is_null() {
960      return Err(Error::NullPointer);
961    }
962
963    // Free the builder on all paths, including early returns on error.
964    struct ListBuilderGuard(*mut sys::ListBuilder);
965    impl Drop for ListBuilderGuard {
966      fn drop(&mut self) {
967        unsafe { sys::nix_list_builder_free(self.0) };
968      }
969    }
970    let _guard = ListBuilderGuard(builder);
971
972    // Insert each item
973    for (i, item) in items.iter().enumerate() {
974      // SAFETY: context, builder, and value are valid; index is in bounds
975      unsafe {
976        check_err(
977          self.context.as_ptr(),
978          sys::nix_list_builder_insert(
979            self.context.as_ptr(),
980            builder,
981            i as std::os::raw::c_uint,
982            item.inner.as_ptr(),
983          ),
984        )?;
985      }
986    }
987
988    let result = self.alloc_value()?;
989    // SAFETY: context, builder, and result value are valid
990    unsafe {
991      check_err(
992        self.context.as_ptr(),
993        sys::nix_make_list(
994          self.context.as_ptr(),
995          builder,
996          result.inner.as_ptr(),
997        ),
998      )?;
999    }
1000
1001    Ok(result)
1002  }
1003
1004  /// Create a Nix attribute set from key-value pairs.
1005  ///
1006  /// # Errors
1007  ///
1008  /// Returns an error if value allocation or attribute set construction fails.
1009  pub fn make_attrs<'s>(
1010    &'s self,
1011    pairs: &[(&str, &Value<'_>)],
1012  ) -> Result<Value<'s>> {
1013    // SAFETY: context and state are valid
1014    let builder = unsafe {
1015      sys::nix_make_bindings_builder(
1016        self.context.as_ptr(),
1017        self.inner.as_ptr(),
1018        pairs.len(),
1019      )
1020    };
1021    if builder.is_null() {
1022      return Err(Error::NullPointer);
1023    }
1024
1025    // Free the builder on all paths, including early returns on error.
1026    struct BindingsBuilderGuard(*mut sys::BindingsBuilder);
1027    impl Drop for BindingsBuilderGuard {
1028      fn drop(&mut self) {
1029        unsafe { sys::nix_bindings_builder_free(self.0) };
1030      }
1031    }
1032    let _guard = BindingsBuilderGuard(builder);
1033
1034    // Insert each key-value pair
1035    for (key, value) in pairs {
1036      let key_c = CString::new(*key)?;
1037      // SAFETY: context, builder, key, and value are valid
1038      unsafe {
1039        check_err(
1040          self.context.as_ptr(),
1041          sys::nix_bindings_builder_insert(
1042            self.context.as_ptr(),
1043            builder,
1044            key_c.as_ptr(),
1045            value.inner.as_ptr(),
1046          ),
1047        )?;
1048      }
1049    }
1050
1051    let result = self.alloc_value()?;
1052    // SAFETY: context, builder, and result value are valid
1053    unsafe {
1054      check_err(
1055        self.context.as_ptr(),
1056        sys::nix_make_attrs(
1057          self.context.as_ptr(),
1058          result.inner.as_ptr(),
1059          builder,
1060        ),
1061      )?;
1062    }
1063
1064    Ok(result)
1065  }
1066
1067  /// Get the raw state pointer.
1068  ///
1069  /// # Safety
1070  ///
1071  /// The caller must ensure the pointer is used safely.
1072  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::EvalState {
1073    self.inner.as_ptr()
1074  }
1075}
1076
1077#[cfg(feature = "expr")]
1078impl Drop for EvalState {
1079  fn drop(&mut self) {
1080    // SAFETY: We own the state and it's valid until drop
1081    unsafe {
1082      sys::nix_state_free(self.inner.as_ptr());
1083    }
1084  }
1085}
1086
1087// SAFETY: `EvalState` owns its `*mut sys::EvalState` outright and only
1088// references the C context through `Arc<Context>`. Moving the wrapper
1089// hands sole ownership of the evaluator handle to the destination
1090// thread; the C++ evaluator does not look at thread-local state, so the
1091// move is sound. We rely on the caller not to retain a clone of the
1092// same `Arc<Context>` and call into it concurrently from another
1093// thread; see the `# Thread Safety` section in the crate root.
1094//
1095// `Sync` is NOT implemented: every C entry point rewrites the
1096// per-context error buffer that this evaluator shares with `Context`,
1097// so concurrent `&EvalState` use across threads would race.
1098#[cfg(feature = "expr")]
1099unsafe impl Send for EvalState {}
1100
1101/// Nix value types.
1102#[cfg(feature = "expr")]
1103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1104pub enum ValueType {
1105  /// Thunk (unevaluated expression).
1106  Thunk,
1107  /// Integer value.
1108  Int,
1109  /// Float value.
1110  Float,
1111  /// Boolean value.
1112  Bool,
1113  /// String value.
1114  String,
1115  /// Path value.
1116  Path,
1117  /// Null value.
1118  Null,
1119  /// Attribute set.
1120  Attrs,
1121  /// List.
1122  List,
1123  /// Function.
1124  Function,
1125  /// External value.
1126  External,
1127}
1128
1129#[cfg(feature = "expr")]
1130impl ValueType {
1131  fn from_c(value_type: sys::ValueType) -> Self {
1132    match value_type {
1133      sys::ValueType_NIX_TYPE_THUNK => ValueType::Thunk,
1134      sys::ValueType_NIX_TYPE_INT => ValueType::Int,
1135      sys::ValueType_NIX_TYPE_FLOAT => ValueType::Float,
1136      sys::ValueType_NIX_TYPE_BOOL => ValueType::Bool,
1137      sys::ValueType_NIX_TYPE_STRING => ValueType::String,
1138      sys::ValueType_NIX_TYPE_PATH => ValueType::Path,
1139      sys::ValueType_NIX_TYPE_NULL => ValueType::Null,
1140      sys::ValueType_NIX_TYPE_ATTRS => ValueType::Attrs,
1141      sys::ValueType_NIX_TYPE_LIST => ValueType::List,
1142      sys::ValueType_NIX_TYPE_FUNCTION => ValueType::Function,
1143      sys::ValueType_NIX_TYPE_EXTERNAL => ValueType::External,
1144      _ => ValueType::Thunk, // fallback
1145    }
1146  }
1147}
1148
1149#[cfg(feature = "expr")]
1150impl fmt::Display for ValueType {
1151  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1152    let name = match self {
1153      ValueType::Thunk => "thunk",
1154      ValueType::Int => "int",
1155      ValueType::Float => "float",
1156      ValueType::Bool => "bool",
1157      ValueType::String => "string",
1158      ValueType::Path => "path",
1159      ValueType::Null => "null",
1160      ValueType::Attrs => "attrs",
1161      ValueType::List => "list",
1162      ValueType::Function => "function",
1163      ValueType::External => "external",
1164    };
1165    write!(f, "{name}")
1166  }
1167}
1168
1169/// A Nix value.
1170///
1171/// This represents any value in the Nix language, including primitives,
1172/// collections, and functions. Values are GC-managed; this struct holds
1173/// a reference count that is released on drop.
1174#[cfg(feature = "expr")]
1175pub struct Value<'a> {
1176  pub(crate) inner: NonNull<sys::nix_value>,
1177  pub(crate) state: &'a EvalState,
1178}
1179
1180#[cfg(feature = "expr")]
1181impl Value<'_> {
1182  /// Force evaluation of this value.
1183  ///
1184  /// If the value is a thunk, this will evaluate it to its final form.
1185  ///
1186  /// # Errors
1187  ///
1188  /// Returns an error if evaluation fails.
1189  pub fn force(&mut self) -> Result<()> {
1190    // SAFETY: context, state, and value are valid
1191    unsafe {
1192      check_err(
1193        self.state.context.as_ptr(),
1194        sys::nix_value_force(
1195          self.state.context.as_ptr(),
1196          self.state.as_ptr(),
1197          self.inner.as_ptr(),
1198        ),
1199      )
1200    }
1201  }
1202
1203  /// Force deep evaluation of this value.
1204  ///
1205  /// This forces evaluation of the value and all its nested components.
1206  ///
1207  /// # Errors
1208  ///
1209  /// Returns an error if evaluation fails.
1210  pub fn force_deep(&mut self) -> Result<()> {
1211    // SAFETY: context, state, and value are valid
1212    unsafe {
1213      check_err(
1214        self.state.context.as_ptr(),
1215        sys::nix_value_force_deep(
1216          self.state.context.as_ptr(),
1217          self.state.as_ptr(),
1218          self.inner.as_ptr(),
1219        ),
1220      )
1221    }
1222  }
1223
1224  /// Get the type of this value.
1225  #[must_use]
1226  pub fn value_type(&self) -> ValueType {
1227    // SAFETY: context and value are valid
1228    let c_type = unsafe {
1229      sys::nix_get_type(self.state.context.as_ptr(), self.inner.as_ptr())
1230    };
1231    ValueType::from_c(c_type)
1232  }
1233
1234  /// Convert this value to an integer.
1235  ///
1236  /// # Errors
1237  ///
1238  /// Returns an error if the value is not an integer.
1239  pub fn as_int(&self) -> Result<i64> {
1240    if self.value_type() != ValueType::Int {
1241      return Err(Error::InvalidType {
1242        expected: "int",
1243        actual:   self.value_type().to_string(),
1244      });
1245    }
1246
1247    // SAFETY: context and value are valid, type is checked
1248    let result = unsafe {
1249      sys::nix_get_int(self.state.context.as_ptr(), self.inner.as_ptr())
1250    };
1251
1252    Ok(result)
1253  }
1254
1255  /// Convert this value to a float.
1256  ///
1257  /// # Errors
1258  ///
1259  /// Returns an error if the value is not a float.
1260  pub fn as_float(&self) -> Result<f64> {
1261    if self.value_type() != ValueType::Float {
1262      return Err(Error::InvalidType {
1263        expected: "float",
1264        actual:   self.value_type().to_string(),
1265      });
1266    }
1267
1268    // SAFETY: context and value are valid, type is checked
1269    let result = unsafe {
1270      sys::nix_get_float(self.state.context.as_ptr(), self.inner.as_ptr())
1271    };
1272
1273    Ok(result)
1274  }
1275
1276  /// Convert this value to a boolean.
1277  ///
1278  /// # Errors
1279  ///
1280  /// Returns an error if the value is not a boolean.
1281  pub fn as_bool(&self) -> Result<bool> {
1282    if self.value_type() != ValueType::Bool {
1283      return Err(Error::InvalidType {
1284        expected: "bool",
1285        actual:   self.value_type().to_string(),
1286      });
1287    }
1288
1289    // SAFETY: context and value are valid, type is checked
1290    let result = unsafe {
1291      sys::nix_get_bool(self.state.context.as_ptr(), self.inner.as_ptr())
1292    };
1293
1294    Ok(result)
1295  }
1296
1297  /// Convert this value to a string.
1298  ///
1299  /// This realises the string (resolves any context/store paths) and
1300  /// returns its content.
1301  ///
1302  /// # Errors
1303  ///
1304  /// Returns an error if the value is not a string.
1305  pub fn as_string(&self) -> Result<String> {
1306    if self.value_type() != ValueType::String {
1307      return Err(Error::InvalidType {
1308        expected: "string",
1309        actual:   self.value_type().to_string(),
1310      });
1311    }
1312
1313    // Use the realised string API to handle string contexts correctly.
1314    // SAFETY: context, state, and value are valid; type is checked
1315    let realised_str = unsafe {
1316      sys::nix_string_realise(
1317        self.state.context.as_ptr(),
1318        self.state.as_ptr(),
1319        self.inner.as_ptr(),
1320        false,
1321      )
1322    };
1323
1324    if realised_str.is_null() {
1325      return Err(Error::NullPointer);
1326    }
1327
1328    // SAFETY: realised_str is non-null
1329    let buffer_start =
1330      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
1331
1332    let buffer_size =
1333      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
1334
1335    if buffer_start.is_null() {
1336      unsafe { sys::nix_realised_string_free(realised_str) };
1337      return Err(Error::NullPointer);
1338    }
1339
1340    // SAFETY: buffer_start and buffer_size are valid
1341    let bytes = unsafe {
1342      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
1343    };
1344    let string = std::str::from_utf8(bytes)
1345      .map_err(|_| Error::Unknown("Invalid UTF-8 in string".to_string()))?
1346      .to_owned();
1347
1348    unsafe { sys::nix_realised_string_free(realised_str) };
1349
1350    Ok(string)
1351  }
1352
1353  /// Convert this value to a string and return its store-path context.
1354  ///
1355  /// This is the extended form of [`as_string`](Self::as_string): it returns
1356  /// the string content together with any store paths embedded in the string's
1357  /// context. For ordinary strings the context vector is empty.
1358  ///
1359  /// # Errors
1360  ///
1361  /// Returns an error if the value is not a string.
1362  pub fn as_string_with_context(
1363    &self,
1364  ) -> Result<(String, Vec<store::StorePath>)> {
1365    if self.value_type() != ValueType::String {
1366      return Err(Error::InvalidType {
1367        expected: "string",
1368        actual:   self.value_type().to_string(),
1369      });
1370    }
1371
1372    // SAFETY: context, state, and value are valid; type is checked
1373    let realised_str = unsafe {
1374      sys::nix_string_realise(
1375        self.state.context.as_ptr(),
1376        self.state.as_ptr(),
1377        self.inner.as_ptr(),
1378        false,
1379      )
1380    };
1381
1382    if realised_str.is_null() {
1383      return Err(Error::NullPointer);
1384    }
1385
1386    // Read the string content.
1387    let buffer_start =
1388      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
1389    let buffer_size =
1390      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
1391
1392    if buffer_start.is_null() {
1393      unsafe { sys::nix_realised_string_free(realised_str) };
1394      return Err(Error::NullPointer);
1395    }
1396
1397    let bytes = unsafe {
1398      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
1399    };
1400    let string = match std::str::from_utf8(bytes) {
1401      Ok(s) => s.to_owned(),
1402      Err(_) => {
1403        unsafe { sys::nix_realised_string_free(realised_str) };
1404        return Err(Error::Unknown("Invalid UTF-8 in string".to_string()));
1405      },
1406    };
1407
1408    // Collect store-path context.
1409    let count =
1410      unsafe { sys::nix_realised_string_get_store_path_count(realised_str) };
1411    let mut paths = Vec::with_capacity(count);
1412    for i in 0..count {
1413      // SAFETY: index is in bounds
1414      let raw =
1415        unsafe { sys::nix_realised_string_get_store_path(realised_str, i) };
1416      if raw.is_null() {
1417        continue;
1418      }
1419      // Clone the path so we own it independently of the realised_str buffer.
1420      let cloned =
1421        unsafe { sys::nix_store_path_clone(raw as *mut sys::StorePath) };
1422      if let Some(inner) = std::ptr::NonNull::new(cloned) {
1423        paths.push(store::StorePath {
1424          inner,
1425          _context: Arc::clone(&self.state.context),
1426        });
1427      }
1428    }
1429
1430    unsafe { sys::nix_realised_string_free(realised_str) };
1431
1432    Ok((string, paths))
1433  }
1434
1435  /// Convert this value to a filesystem path.
1436  ///
1437  /// # Errors
1438  ///
1439  /// Returns an error if the value is not a path.
1440  pub fn as_path(&self) -> Result<std::path::PathBuf> {
1441    if self.value_type() != ValueType::Path {
1442      return Err(Error::InvalidType {
1443        expected: "path",
1444        actual:   self.value_type().to_string(),
1445      });
1446    }
1447
1448    // SAFETY: context and value are valid, type is checked
1449    let path_ptr = unsafe {
1450      sys::nix_get_path_string(self.state.context.as_ptr(), self.inner.as_ptr())
1451    };
1452
1453    if path_ptr.is_null() {
1454      return Err(Error::NullPointer);
1455    }
1456
1457    // On Unix the kernel treats paths as arbitrary bytes, so we round-trip
1458    // through OsStr to preserve any non-UTF-8 bytes the path may contain.
1459    // On other targets fall back to the lossy UTF-8 conversion.
1460    #[cfg(unix)]
1461    {
1462      use std::os::unix::ffi::OsStrExt as _;
1463      let bytes = unsafe { CStr::from_ptr(path_ptr) }.to_bytes();
1464      Ok(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(bytes)))
1465    }
1466    #[cfg(not(unix))]
1467    {
1468      let path_str =
1469        unsafe { CStr::from_ptr(path_ptr).to_string_lossy().into_owned() };
1470      Ok(std::path::PathBuf::from(path_str))
1471    }
1472  }
1473
1474  /// Call this value as a function with a single argument.
1475  ///
1476  /// # Errors
1477  ///
1478  /// Returns an error if this value is not a function or the call fails.
1479  pub fn call(&self, arg: &Value<'_>) -> Result<Value<'_>> {
1480    let result = self.state.alloc_value()?;
1481    // SAFETY: context, state, function value, arg value, and result are valid
1482    unsafe {
1483      check_err(
1484        self.state.context.as_ptr(),
1485        sys::nix_value_call(
1486          self.state.context.as_ptr(),
1487          self.state.as_ptr(),
1488          self.inner.as_ptr(),
1489          arg.inner.as_ptr(),
1490          result.inner.as_ptr(),
1491        ),
1492      )?;
1493    }
1494    Ok(result)
1495  }
1496
1497  /// Call this value as a curried function with multiple arguments.
1498  ///
1499  /// # Errors
1500  ///
1501  /// Returns an error if this value is not a function or the call fails.
1502  pub fn call_multi(&self, args: &[&Value<'_>]) -> Result<Value<'_>> {
1503    let result = self.state.alloc_value()?;
1504    let mut arg_ptrs: Vec<*mut sys::nix_value> =
1505      args.iter().map(|a| a.inner.as_ptr()).collect();
1506    // SAFETY: context, state, fn, args array, and result are valid
1507    unsafe {
1508      check_err(
1509        self.state.context.as_ptr(),
1510        sys::nix_value_call_multi(
1511          self.state.context.as_ptr(),
1512          self.state.as_ptr(),
1513          self.inner.as_ptr(),
1514          arg_ptrs.len(),
1515          arg_ptrs.as_mut_ptr(),
1516          result.inner.as_ptr(),
1517        ),
1518      )?;
1519    }
1520    Ok(result)
1521  }
1522
1523  /// Create a lazy thunk that applies a function to an argument.
1524  ///
1525  /// Unlike [`call`](Self::call), this does not perform the call immediately;
1526  /// it stores it as a thunk to be evaluated lazily. This is useful for
1527  /// constructing lazy attribute sets and lists.
1528  ///
1529  /// # Errors
1530  ///
1531  /// Returns an error if the thunk cannot be created.
1532  pub fn make_thunk<'a>(
1533    fn_val: &'a Value<'a>,
1534    arg: &'a Value<'a>,
1535  ) -> Result<Value<'a>> {
1536    let result = fn_val.state.alloc_value()?;
1537    // SAFETY: context and all value pointers are valid
1538    unsafe {
1539      check_err(
1540        fn_val.state.context.as_ptr(),
1541        sys::nix_init_apply(
1542          fn_val.state.context.as_ptr(),
1543          result.inner.as_ptr(),
1544          fn_val.inner.as_ptr(),
1545          arg.inner.as_ptr(),
1546        ),
1547      )?;
1548    }
1549    Ok(result)
1550  }
1551
1552  /// Copy this value into a new owned value.
1553  ///
1554  /// # Errors
1555  ///
1556  /// Returns an error if the copy fails.
1557  pub fn copy(&self) -> Result<Value<'_>> {
1558    let result = self.state.alloc_value()?;
1559    // SAFETY: context and both value pointers are valid
1560    unsafe {
1561      check_err(
1562        self.state.context.as_ptr(),
1563        sys::nix_copy_value(
1564          self.state.context.as_ptr(),
1565          result.inner.as_ptr(),
1566          self.inner.as_ptr(),
1567        ),
1568      )?;
1569    }
1570    Ok(result)
1571  }
1572
1573  /// Get the raw value pointer.
1574  ///
1575  /// Format this value as Nix syntax.
1576  ///
1577  /// This provides a string representation that matches Nix's own syntax,
1578  /// making it useful for debugging and displaying values to users.
1579  ///
1580  /// # Errors
1581  ///
1582  /// Returns an error if the value cannot be converted to a string
1583  /// representation.
1584  pub fn to_nix_string(&self) -> Result<String> {
1585    match self.value_type() {
1586      ValueType::Int => Ok(self.as_int()?.to_string()),
1587      ValueType::Float => Ok(self.as_float()?.to_string()),
1588      ValueType::Bool => {
1589        Ok(if self.as_bool()? {
1590          "true".to_string()
1591        } else {
1592          "false".to_string()
1593        })
1594      },
1595      ValueType::String => {
1596        Ok(format!("\"{}\"", self.as_string()?.replace('"', "\\\"")))
1597      },
1598      ValueType::Null => Ok("null".to_string()),
1599      ValueType::Attrs => Ok("{ <attrs> }".to_string()),
1600      ValueType::List => Ok("[ <list> ]".to_string()),
1601      ValueType::Function => Ok("<function>".to_string()),
1602      ValueType::Path => {
1603        Ok(
1604          self
1605            .as_path()
1606            .map(|p| p.display().to_string())
1607            .unwrap_or_else(|_| "<path>".to_string()),
1608        )
1609      },
1610      ValueType::Thunk => Ok("<thunk>".to_string()),
1611      ValueType::External => Ok("<external>".to_string()),
1612    }
1613  }
1614}
1615
1616#[cfg(feature = "expr")]
1617impl Drop for Value<'_> {
1618  fn drop(&mut self) {
1619    // SAFETY: We hold a GC reference (automatically incremented for us by
1620    // the Nix C API when the value was returned). Release it here.
1621    unsafe {
1622      sys::nix_value_decref(self.state.context.as_ptr(), self.inner.as_ptr());
1623    }
1624  }
1625}
1626
1627#[cfg(feature = "expr")]
1628impl<'a> Clone for Value<'a> {
1629  /// Clone a [`Value`] by incrementing its GC reference count.
1630  ///
1631  /// Both clones reference the same underlying Nix value; they are not
1632  /// deep copies. Use [`copy`](Value::copy) when you need an independent
1633  /// value slot.
1634  ///
1635  /// Panics if the C API rejects the incref, which should not happen for a
1636  /// live value.
1637  fn clone(&self) -> Self {
1638    // SAFETY: context and value are valid; incref is idempotent.
1639    let err = unsafe {
1640      sys::nix_value_incref(self.state.context.as_ptr(), self.inner.as_ptr())
1641    };
1642    assert!(
1643      err == sys::nix_err_NIX_OK,
1644      "nix_value_incref failed with code {err}"
1645    );
1646    Value {
1647      inner: self.inner,
1648      state: self.state,
1649    }
1650  }
1651}
1652
1653#[cfg(feature = "expr")]
1654impl fmt::Display for Value<'_> {
1655  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1656    match self.value_type() {
1657      ValueType::Int => {
1658        if let Ok(val) = self.as_int() {
1659          write!(f, "{val}")
1660        } else {
1661          write!(f, "<int error>")
1662        }
1663      },
1664      ValueType::Float => {
1665        if let Ok(val) = self.as_float() {
1666          write!(f, "{val}")
1667        } else {
1668          write!(f, "<float error>")
1669        }
1670      },
1671      ValueType::Bool => {
1672        if let Ok(val) = self.as_bool() {
1673          write!(f, "{val}")
1674        } else {
1675          write!(f, "<bool error>")
1676        }
1677      },
1678      ValueType::String => {
1679        if let Ok(val) = self.as_string() {
1680          write!(f, "{val}")
1681        } else {
1682          write!(f, "<string error>")
1683        }
1684      },
1685      ValueType::Null => write!(f, "null"),
1686      ValueType::Attrs => write!(f, "{{ <attrs> }}"),
1687      ValueType::List => write!(f, "[ <list> ]"),
1688      ValueType::Function => write!(f, "<function>"),
1689      ValueType::Path => {
1690        if let Ok(p) = self.as_path() {
1691          write!(f, "{}", p.display())
1692        } else {
1693          write!(f, "<path>")
1694        }
1695      },
1696      ValueType::Thunk => write!(f, "<thunk>"),
1697      ValueType::External => write!(f, "<external>"),
1698    }
1699  }
1700}
1701
1702#[cfg(feature = "expr")]
1703impl fmt::Debug for Value<'_> {
1704  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1705    let value_type = self.value_type();
1706    match value_type {
1707      ValueType::Int => {
1708        if let Ok(val) = self.as_int() {
1709          write!(f, "Value::Int({val})")
1710        } else {
1711          write!(f, "Value::Int(<error>)")
1712        }
1713      },
1714      ValueType::Float => {
1715        if let Ok(val) = self.as_float() {
1716          write!(f, "Value::Float({val})")
1717        } else {
1718          write!(f, "Value::Float(<error>)")
1719        }
1720      },
1721      ValueType::Bool => {
1722        if let Ok(val) = self.as_bool() {
1723          write!(f, "Value::Bool({val})")
1724        } else {
1725          write!(f, "Value::Bool(<error>)")
1726        }
1727      },
1728      ValueType::String => {
1729        if let Ok(val) = self.as_string() {
1730          write!(f, "Value::String({val:?})")
1731        } else {
1732          write!(f, "Value::String(<error>)")
1733        }
1734      },
1735      ValueType::Null => write!(f, "Value::Null"),
1736      ValueType::Attrs => write!(f, "Value::Attrs({{ <attrs> }})"),
1737      ValueType::List => write!(f, "Value::List([ <list> ])"),
1738      ValueType::Function => write!(f, "Value::Function(<function>)"),
1739      ValueType::Path => {
1740        if let Ok(p) = self.as_path() {
1741          write!(f, "Value::Path({})", p.display())
1742        } else {
1743          write!(f, "Value::Path(<path>)")
1744        }
1745      },
1746      ValueType::Thunk => write!(f, "Value::Thunk(<thunk>)"),
1747      ValueType::External => write!(f, "Value::External(<external>)"),
1748    }
1749  }
1750}
1751
1752#[cfg(all(test, any(feature = "store", feature = "expr")))]
1753mod tests {
1754  use super::*;
1755
1756  #[cfg(feature = "store")]
1757  #[test]
1758  #[serial]
1759  fn test_context_creation() {
1760    let _ctx = Context::new().expect("Failed to create context");
1761    // Context should be dropped automatically
1762  }
1763
1764  #[cfg(feature = "store")]
1765  #[test]
1766  #[serial]
1767  fn test_nix_version() {
1768    let version = nix_version();
1769    assert!(!version.is_empty(), "Version should not be empty");
1770  }
1771
1772  #[cfg(feature = "expr")]
1773  #[test]
1774  #[serial]
1775  fn test_eval_state_builder() {
1776    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1777    let store =
1778      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1779    let _state = EvalStateBuilder::new(&store)
1780      .expect("Failed to create builder")
1781      .build()
1782      .expect("Failed to build state");
1783    // State should be dropped automatically
1784  }
1785
1786  #[cfg(feature = "expr")]
1787  #[test]
1788  #[serial]
1789  fn test_simple_evaluation() {
1790    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1791    let store =
1792      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1793    let state = EvalStateBuilder::new(&store)
1794      .expect("Failed to create builder")
1795      .build()
1796      .expect("Failed to build state");
1797
1798    let result = state
1799      .eval_from_string("1 + 2", "<eval>")
1800      .expect("Failed to evaluate expression");
1801
1802    assert_eq!(result.value_type(), ValueType::Int);
1803    assert_eq!(result.as_int().expect("Failed to get int value"), 3);
1804  }
1805
1806  #[cfg(feature = "expr")]
1807  #[test]
1808  #[serial]
1809  fn test_value_types() {
1810    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1811    let store =
1812      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1813    let state = EvalStateBuilder::new(&store)
1814      .expect("Failed to create builder")
1815      .build()
1816      .expect("Failed to build state");
1817
1818    // Test integer
1819    let int_val = state
1820      .eval_from_string("42", "<eval>")
1821      .expect("Failed to evaluate int");
1822    assert_eq!(int_val.value_type(), ValueType::Int);
1823    assert_eq!(int_val.as_int().expect("Failed to get int"), 42);
1824
1825    // Test boolean
1826    let bool_val = state
1827      .eval_from_string("true", "<eval>")
1828      .expect("Failed to evaluate bool");
1829    assert_eq!(bool_val.value_type(), ValueType::Bool);
1830    assert!(bool_val.as_bool().expect("Failed to get bool"));
1831
1832    // Test string
1833    let str_val = state
1834      .eval_from_string("\"hello\"", "<eval>")
1835      .expect("Failed to evaluate string");
1836    assert_eq!(str_val.value_type(), ValueType::String);
1837    assert_eq!(str_val.as_string().expect("Failed to get string"), "hello");
1838  }
1839
1840  #[cfg(feature = "expr")]
1841  #[test]
1842  #[serial]
1843  fn test_value_construction() {
1844    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1845    let store =
1846      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1847    let state = EvalStateBuilder::new(&store)
1848      .expect("Failed to create builder")
1849      .build()
1850      .expect("Failed to build state");
1851
1852    let int_val = state.make_int(99).expect("Failed to make int");
1853    assert_eq!(int_val.as_int().unwrap(), 99);
1854
1855    let float_val = state.make_float(2.5).expect("Failed to make float");
1856    assert!((float_val.as_float().unwrap() - 2.5).abs() < 1e-9);
1857
1858    let bool_val = state.make_bool(true).expect("Failed to make bool");
1859    assert!(bool_val.as_bool().unwrap());
1860
1861    let null_val = state.make_null().expect("Failed to make null");
1862    assert_eq!(null_val.value_type(), ValueType::Null);
1863
1864    let str_val = state.make_string("hello").expect("Failed to make string");
1865    assert_eq!(str_val.as_string().unwrap(), "hello");
1866  }
1867
1868  #[cfg(feature = "expr")]
1869  #[test]
1870  #[serial]
1871  fn test_make_list() {
1872    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1873    let store =
1874      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1875    let state = EvalStateBuilder::new(&store)
1876      .expect("Failed to create builder")
1877      .build()
1878      .expect("Failed to build state");
1879
1880    let a = state.make_int(1).unwrap();
1881    let b = state.make_int(2).unwrap();
1882    let c = state.make_int(3).unwrap();
1883
1884    let list = state.make_list(&[&a, &b, &c]).expect("Failed to make list");
1885    assert_eq!(list.value_type(), ValueType::List);
1886    assert_eq!(list.list_len().unwrap(), 3);
1887  }
1888
1889  #[cfg(feature = "expr")]
1890  #[test]
1891  #[serial]
1892  fn test_make_attrs() {
1893    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1894    let store =
1895      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1896    let state = EvalStateBuilder::new(&store)
1897      .expect("Failed to create builder")
1898      .build()
1899      .expect("Failed to build state");
1900
1901    let a = state.make_int(42).unwrap();
1902    let b = state.make_string("hello").unwrap();
1903
1904    let attrs = state
1905      .make_attrs(&[("answer", &a), ("greeting", &b)])
1906      .expect("Failed to make attrs");
1907    assert_eq!(attrs.value_type(), ValueType::Attrs);
1908
1909    let mut answer = attrs.get_attr("answer").unwrap();
1910    answer.force().unwrap();
1911    assert_eq!(answer.as_int().unwrap(), 42);
1912  }
1913
1914  #[cfg(feature = "expr")]
1915  #[test]
1916  #[serial]
1917  fn test_value_call() {
1918    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1919    let store =
1920      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1921    let state = EvalStateBuilder::new(&store)
1922      .expect("Failed to create builder")
1923      .build()
1924      .expect("Failed to build state");
1925
1926    let f = state
1927      .eval_from_string("x: x + 1", "<eval>")
1928      .expect("Failed to evaluate function");
1929    let arg = state.make_int(41).unwrap();
1930    let result = f.call(&arg).expect("Failed to call function");
1931    assert_eq!(result.as_int().unwrap(), 42);
1932  }
1933
1934  #[cfg(feature = "expr")]
1935  #[test]
1936  #[serial]
1937  fn test_value_copy() {
1938    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1939    let store =
1940      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1941    let state = EvalStateBuilder::new(&store)
1942      .expect("Failed to create builder")
1943      .build()
1944      .expect("Failed to build state");
1945
1946    let orig = state.make_int(7).unwrap();
1947    let copy = orig.copy().expect("Failed to copy value");
1948    assert_eq!(copy.as_int().unwrap(), 7);
1949  }
1950
1951  #[cfg(feature = "expr")]
1952  #[test]
1953  #[serial]
1954  fn test_as_string_with_context_plain() {
1955    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1956    let store =
1957      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1958    let state = EvalStateBuilder::new(&store)
1959      .expect("Failed to create builder")
1960      .build()
1961      .expect("Failed to build state");
1962
1963    let val = state
1964      .eval_from_string("\"hello\"", "<eval>")
1965      .expect("Failed to evaluate string");
1966    let (s, ctx_paths) = val
1967      .as_string_with_context()
1968      .expect("as_string_with_context failed");
1969    assert_eq!(s, "hello");
1970    assert!(
1971      ctx_paths.is_empty(),
1972      "Plain string should have no context paths"
1973    );
1974  }
1975
1976  #[cfg(feature = "expr")]
1977  #[test]
1978  #[serial]
1979  fn test_eval_from_file() {
1980    use std::io::Write as _;
1981    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1982    let store =
1983      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1984    let state = EvalStateBuilder::new(&store)
1985      .expect("Failed to create builder")
1986      .build()
1987      .expect("Failed to build state");
1988
1989    let mut tmp =
1990      tempfile::NamedTempFile::new().expect("Failed to create temp file");
1991    write!(tmp, "1 + 1").expect("Failed to write temp file");
1992    let result = state
1993      .eval_from_file(tmp.path())
1994      .expect("eval_from_file failed");
1995    assert_eq!(result.as_int().unwrap(), 2);
1996  }
1997
1998  #[cfg(feature = "expr")]
1999  #[test]
2000  #[serial]
2001  fn test_no_load_config() {
2002    let ctx = Arc::new(Context::new().expect("Failed to create context"));
2003    let store =
2004      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
2005    let state = EvalStateBuilder::new(&store)
2006      .expect("Failed to create builder")
2007      .no_load_config()
2008      .build()
2009      .expect("Failed to build state with no_load_config");
2010    let val = state
2011      .eval_from_string("1 + 1", "<eval>")
2012      .expect("Evaluation failed");
2013    assert_eq!(val.as_int().unwrap(), 2);
2014  }
2015}