Skip to main content

nix_bindings/
eval.rs

1//! [`EvalState`] and [`EvalStateBuilder`]: the Nix evaluator handle and
2//! its configuration.
3
4#![cfg(feature = "expr")]
5
6use std::{ffi::CString, path::Path, ptr::NonNull, sync::Arc};
7
8use crate::{
9  Context,
10  Error,
11  Result,
12  Store,
13  StorePath,
14  Value,
15  context::is_pure_eval,
16  error::check_err,
17  sys,
18};
19
20/// Builder for Nix evaluation state.
21///
22/// This allows configuring the evaluation environment before creating
23/// the evaluation state.
24pub struct EvalStateBuilder {
25  inner:     NonNull<sys::nix_eval_state_builder>,
26  store:     Arc<Store>,
27  context:   Arc<Context>,
28  skip_load: bool,
29}
30
31impl EvalStateBuilder {
32  /// Create a new evaluation state builder.
33  ///
34  /// # Arguments
35  ///
36  /// * `store` - The Nix store to use for evaluation
37  ///
38  /// # Errors
39  ///
40  /// Returns an error if the builder cannot be created.
41  pub fn new(store: &Arc<Store>) -> Result<Self> {
42    // SAFETY: store context and store are valid
43    let builder_ptr = unsafe {
44      sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr())
45    };
46
47    let inner = NonNull::new(builder_ptr).ok_or(Error::NullPointer)?;
48
49    Ok(EvalStateBuilder {
50      inner,
51      store: Arc::clone(store),
52      context: Arc::clone(&store._context),
53      skip_load: false,
54    })
55  }
56
57  /// Set the lookup path (`NIX_PATH`) for `<...>` expressions.
58  ///
59  /// Each entry should be in the form `"name=path"` or just `"path"`,
60  /// matching the format of `NIX_PATH` entries.
61  ///
62  /// # Errors
63  ///
64  /// Returns an error if the lookup path cannot be set.
65  pub fn set_lookup_path(self, paths: &[impl AsRef<str>]) -> Result<Self> {
66    let c_strings: Vec<CString> = paths
67      .iter()
68      .map(|s| CString::new(s.as_ref()))
69      .collect::<std::result::Result<_, _>>()?;
70
71    let mut ptrs: Vec<*const std::os::raw::c_char> =
72      c_strings.iter().map(|cs| cs.as_ptr()).collect();
73    ptrs.push(std::ptr::null());
74
75    // SAFETY: context and builder are valid, ptrs is null-terminated
76    unsafe {
77      check_err(
78        self.context.as_ptr(),
79        sys::nix_eval_state_builder_set_lookup_path(
80          self.context.as_ptr(),
81          self.inner.as_ptr(),
82          ptrs.as_mut_ptr(),
83        ),
84      )?;
85    }
86
87    Ok(self)
88  }
89
90  /// Apply flake settings to the evaluation state builder.
91  ///
92  /// This enables `builtins.getFlake` and related flake functionality
93  /// in the resulting [`EvalState`].
94  ///
95  /// # Errors
96  ///
97  /// Returns an error if the flake settings cannot be applied.
98  #[cfg(feature = "flake")]
99  pub fn with_flake_settings(
100    self,
101    settings: &crate::flake::FlakeSettings,
102  ) -> Result<Self> {
103    // SAFETY: context, settings, and builder are valid
104    unsafe {
105      check_err(
106        self.context.as_ptr(),
107        sys::nix_flake_settings_add_to_eval_state_builder(
108          self.context.as_ptr(),
109          settings.as_ptr(),
110          self.inner.as_ptr(),
111        ),
112      )?;
113    }
114
115    Ok(self)
116  }
117
118  /// Skip loading Nix configuration from the environment.
119  ///
120  /// By default [`build`](Self::build) calls `nix_eval_state_builder_load` to
121  /// read configuration from environment variables and config files. Call
122  /// this method to skip that step, which is useful in tests or sandboxed
123  /// environments.
124  #[must_use]
125  pub fn no_load_config(mut self) -> Self {
126    self.skip_load = true;
127    self
128  }
129
130  /// Build the evaluation state.
131  ///
132  /// # Errors
133  ///
134  /// Returns an error if the evaluation state cannot be built.
135  pub fn build(self) -> Result<EvalState> {
136    if !self.skip_load {
137      // SAFETY: context and builder are valid
138      unsafe {
139        check_err(
140          self.context.as_ptr(),
141          sys::nix_eval_state_builder_load(
142            self.context.as_ptr(),
143            self.inner.as_ptr(),
144          ),
145        )?;
146      }
147    }
148
149    // SAFETY: context and builder are valid
150    let state_ptr = unsafe {
151      sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr())
152    };
153
154    let inner = NonNull::new(state_ptr).ok_or(Error::NullPointer)?;
155
156    Ok(EvalState {
157      inner,
158      store: self.store.clone(),
159      context: self.context.clone(),
160    })
161  }
162}
163
164impl Drop for EvalStateBuilder {
165  fn drop(&mut self) {
166    // SAFETY: We own the builder and it's valid until drop
167    unsafe {
168      sys::nix_eval_state_builder_free(self.inner.as_ptr());
169    }
170  }
171}
172
173/// Nix evaluation state for evaluating expressions.
174///
175/// This provides the main interface for evaluating Nix expressions
176/// and creating values.
177pub struct EvalState {
178  pub(crate) inner:   NonNull<sys::EvalState>,
179  #[expect(dead_code, reason = "keeps the Arc<Store> alive Drop side-effects")]
180  store:              Arc<Store>,
181  pub(crate) context: Arc<Context>,
182}
183
184impl EvalState {
185  /// Evaluate a Nix expression from a string.
186  ///
187  /// # Arguments
188  ///
189  /// * `expr` - The Nix expression to evaluate
190  /// * `path` - The path to use for error reporting (e.g., `"<eval>"`)
191  ///
192  /// # Errors
193  ///
194  /// Returns an error if evaluation fails.
195  pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>> {
196    let expr_c = CString::new(expr)?;
197    let path_c = CString::new(path)?;
198
199    // SAFETY: context and state are valid
200    let value_ptr = unsafe {
201      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
202    };
203    if value_ptr.is_null() {
204      return Err(Error::NullPointer);
205    }
206
207    // SAFETY: all pointers are valid
208    unsafe {
209      check_err(
210        self.context.as_ptr(),
211        sys::nix_expr_eval_from_string(
212          self.context.as_ptr(),
213          self.inner.as_ptr(),
214          expr_c.as_ptr(),
215          path_c.as_ptr(),
216          value_ptr,
217        ),
218      )?;
219    }
220
221    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
222
223    Ok(Value { inner, state: self })
224  }
225
226  /// Evaluate a Nix expression from a file.
227  ///
228  /// Reads the file at `path` as UTF-8, then evaluates its contents using the
229  /// parent directory as the base path for relative imports. The base path is
230  /// passed to Nix as a UTF-8 string; non-UTF-8 components are replaced
231  /// lossily for the error-reporting label only.
232  ///
233  /// # Errors
234  ///
235  /// Returns an error if the file cannot be read as UTF-8 or if evaluation
236  /// fails.
237  pub fn eval_from_file(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
238    let path = path.as_ref();
239    let expr = std::fs::read_to_string(path).map_err(|e| {
240      Error::Unknown(format!("Failed to read file {}: {e}", path.display()))
241    })?;
242    let base_path = path.parent().unwrap_or_else(|| Path::new("."));
243    let base_str = base_path.to_string_lossy();
244    self.eval_from_string(&expr, &base_str)
245  }
246
247  /// Allocate a new uninitialized value.
248  ///
249  /// # Errors
250  ///
251  /// Returns an error if value allocation fails.
252  pub fn alloc_value(&self) -> Result<Value<'_>> {
253    // SAFETY: context and state are valid
254    let value_ptr = unsafe {
255      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
256    };
257    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
258
259    Ok(Value { inner, state: self })
260  }
261
262  /// Create a Nix integer value.
263  ///
264  /// # Errors
265  ///
266  /// Returns an error if value allocation or initialization fails.
267  pub fn make_int(&self, i: i64) -> Result<Value<'_>> {
268    let v = self.alloc_value()?;
269    // SAFETY: context and value are valid
270    unsafe {
271      check_err(
272        self.context.as_ptr(),
273        sys::nix_init_int(self.context.as_ptr(), v.inner.as_ptr(), i),
274      )?;
275    }
276    Ok(v)
277  }
278
279  /// Create a Nix float value.
280  ///
281  /// # Errors
282  ///
283  /// Returns an error if value allocation or initialization fails.
284  pub fn make_float(&self, f: f64) -> Result<Value<'_>> {
285    let v = self.alloc_value()?;
286    // SAFETY: context and value are valid
287    unsafe {
288      check_err(
289        self.context.as_ptr(),
290        sys::nix_init_float(self.context.as_ptr(), v.inner.as_ptr(), f),
291      )?;
292    }
293    Ok(v)
294  }
295
296  /// Create a Nix boolean value.
297  ///
298  /// # Errors
299  ///
300  /// Returns an error if value allocation or initialization fails.
301  pub fn make_bool(&self, b: bool) -> Result<Value<'_>> {
302    let v = self.alloc_value()?;
303    // SAFETY: context and value are valid
304    unsafe {
305      check_err(
306        self.context.as_ptr(),
307        sys::nix_init_bool(self.context.as_ptr(), v.inner.as_ptr(), b),
308      )?;
309    }
310    Ok(v)
311  }
312
313  /// Create a Nix null value.
314  ///
315  /// # Errors
316  ///
317  /// Returns an error if value allocation or initialization fails.
318  pub fn make_null(&self) -> Result<Value<'_>> {
319    let v = self.alloc_value()?;
320    // SAFETY: context and value are valid
321    unsafe {
322      check_err(
323        self.context.as_ptr(),
324        sys::nix_init_null(self.context.as_ptr(), v.inner.as_ptr()),
325      )?;
326    }
327    Ok(v)
328  }
329
330  /// Create a Nix string value.
331  ///
332  /// # Errors
333  ///
334  /// Returns an error if value allocation, string conversion, or
335  /// initialization fails.
336  pub fn make_string(&self, s: &str) -> Result<Value<'_>> {
337    let v = self.alloc_value()?;
338    let s_c = CString::new(s)?;
339    // SAFETY: context and value are valid
340    unsafe {
341      check_err(
342        self.context.as_ptr(),
343        sys::nix_init_string(
344          self.context.as_ptr(),
345          v.inner.as_ptr(),
346          s_c.as_ptr(),
347        ),
348      )?;
349    }
350    Ok(v)
351  }
352
353  /// Create a Nix path value.
354  ///
355  /// # Pure Evaluation
356  ///
357  /// In pure-eval mode (`--pure-eval`) the Nix evaluator wraps the
358  /// filesystem in an `AllowListSourceAccessor` that rejects any
359  /// unregistered absolute path. When the `shim` feature is enabled this
360  /// method automatically registers absolute paths via the shim's
361  /// `nix_eval_state_allow_path` before constructing the value, mirroring
362  /// what Nix's own fetch builtins do. Without `shim` you must arrange
363  /// for allowPath yourself, or [`is_pure_eval`] returns true.
364  ///
365  /// # Errors
366  ///
367  /// Returns an error if value allocation, path conversion, or
368  /// initialization fails.
369  pub fn make_path(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
370    let v = self.alloc_value()?;
371    let path_str = path
372      .as_ref()
373      .to_str()
374      .ok_or_else(|| Error::Unknown("Path is not valid UTF-8".to_string()))?;
375    let path_c = CString::new(path_str)?;
376
377    // See make_path note in old lib.rs for why we restrict auto-allow
378    // to /nix/store/ paths.
379    #[cfg(feature = "shim")]
380    if path.as_ref().is_absolute()
381      && path_str.starts_with("/nix/store/")
382      && is_pure_eval()
383    {
384      self.allow_store_path(path_str)?;
385    }
386
387    // SAFETY: context, state, and value are valid
388    unsafe {
389      check_err(
390        self.context.as_ptr(),
391        sys::nix_init_path_string(
392          self.context.as_ptr(),
393          self.inner.as_ptr(),
394          v.inner.as_ptr(),
395          path_c.as_ptr(),
396        ),
397      )?;
398    }
399    Ok(v)
400  }
401
402  /// Allow a canonical Nix store path in this evaluation state.
403  ///
404  /// This restores access to a previously resolved store path in a fresh
405  /// evaluator running with `restrict-eval`. It does not permit arbitrary
406  /// filesystem paths: Nix parses and validates the supplied store path.
407  ///
408  /// # Errors
409  ///
410  /// Returns an error if `path` is not a valid store path or Nix cannot add it
411  /// to this evaluation state's allowlist.
412  #[cfg(feature = "shim")]
413  pub fn allow_store_path(&self, path: &str) -> Result<()> {
414    let path_c = CString::new(path)?;
415    // SAFETY: context, state, and path are valid.
416    unsafe {
417      check_err(
418        self.context.as_ptr(),
419        sys::nix_eval_state_allow_path(
420          self.context.as_ptr(),
421          self.inner.as_ptr(),
422          path_c.as_ptr(),
423        ),
424      )
425    }
426  }
427
428  /// Create a Nix list value from a slice of values.
429  ///
430  /// # Errors
431  ///
432  /// Returns an error if value allocation or list construction fails.
433  pub fn make_list(&self, items: &[&Value<'_>]) -> Result<Value<'_>> {
434    // SAFETY: context and state are valid
435    let builder = unsafe {
436      sys::nix_make_list_builder(
437        self.context.as_ptr(),
438        self.inner.as_ptr(),
439        items.len(),
440      )
441    };
442    if builder.is_null() {
443      return Err(Error::NullPointer);
444    }
445
446    struct ListBuilderGuard(*mut sys::ListBuilder);
447    impl Drop for ListBuilderGuard {
448      fn drop(&mut self) {
449        unsafe { sys::nix_list_builder_free(self.0) };
450      }
451    }
452    let _guard = ListBuilderGuard(builder);
453
454    for (i, item) in items.iter().enumerate() {
455      // SAFETY: context, builder, and value are valid; index in bounds
456      unsafe {
457        check_err(
458          self.context.as_ptr(),
459          sys::nix_list_builder_insert(
460            self.context.as_ptr(),
461            builder,
462            i as std::os::raw::c_uint,
463            item.inner.as_ptr(),
464          ),
465        )?;
466      }
467    }
468
469    let result = self.alloc_value()?;
470    // SAFETY: context, builder, and result value are valid
471    unsafe {
472      check_err(
473        self.context.as_ptr(),
474        sys::nix_make_list(
475          self.context.as_ptr(),
476          builder,
477          result.inner.as_ptr(),
478        ),
479      )?;
480    }
481
482    Ok(result)
483  }
484
485  /// Create a Nix attribute set from key-value pairs.
486  ///
487  /// # Errors
488  ///
489  /// Returns an error if value allocation or attribute set construction
490  /// fails.
491  pub fn make_attrs<'s>(
492    &'s self,
493    pairs: &[(&str, &Value<'_>)],
494  ) -> Result<Value<'s>> {
495    // SAFETY: context and state are valid
496    let builder = unsafe {
497      sys::nix_make_bindings_builder(
498        self.context.as_ptr(),
499        self.inner.as_ptr(),
500        pairs.len(),
501      )
502    };
503    if builder.is_null() {
504      return Err(Error::NullPointer);
505    }
506
507    struct BindingsBuilderGuard(*mut sys::BindingsBuilder);
508    impl Drop for BindingsBuilderGuard {
509      fn drop(&mut self) {
510        unsafe { sys::nix_bindings_builder_free(self.0) };
511      }
512    }
513    let _guard = BindingsBuilderGuard(builder);
514
515    for (key, value) in pairs {
516      let key_c = CString::new(*key)?;
517      // SAFETY: context, builder, key, and value are valid
518      unsafe {
519        check_err(
520          self.context.as_ptr(),
521          sys::nix_bindings_builder_insert(
522            self.context.as_ptr(),
523            builder,
524            key_c.as_ptr(),
525            value.inner.as_ptr(),
526          ),
527        )?;
528      }
529    }
530
531    let result = self.alloc_value()?;
532    // SAFETY: context, builder, and result value are valid
533    unsafe {
534      check_err(
535        self.context.as_ptr(),
536        sys::nix_make_attrs(
537          self.context.as_ptr(),
538          result.inner.as_ptr(),
539          builder,
540        ),
541      )?;
542    }
543
544    Ok(result)
545  }
546
547  /// Determine whether a value is a derivation and return its store path.
548  ///
549  /// Forces `value` and, if it is a derivation, returns a newly allocated
550  /// [`StorePath`] for its `.drvPath`. Returns `Ok(None)` when the value is
551  /// not a derivation. An exception raised during forcing propagates as
552  /// `Err(...)`.
553  ///
554  /// # Errors
555  ///
556  /// Returns an error if forcing the value raises a Nix exception.
557  #[cfg(feature = "shim")]
558  pub fn get_derivation(&self, value: &Value<'_>) -> Result<Option<StorePath>> {
559    // SAFETY: context, state, and value are valid for the call duration.
560    let path_ptr = unsafe {
561      sys::nix_get_derivation(
562        self.context.as_ptr(),
563        self.inner.as_ptr(),
564        value.inner.as_ptr(),
565        false,
566      )
567    };
568
569    if path_ptr.is_null() {
570      // NIXC_CATCH_ERRS_NULL sets last_err_code on exception. A plain null
571      // (no exception, maybePkg was empty) leaves last_err_code at NIX_OK.
572      // SAFETY: context is valid for the lifetime of self.
573      unsafe {
574        let ctx = self.context.as_ptr();
575        let code = sys::nix_err_code(ctx);
576        check_err(ctx, code)?;
577      }
578      return Ok(None);
579    }
580
581    let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
582    Ok(Some(StorePath {
583      inner,
584      _context: Arc::clone(&self.context),
585    }))
586  }
587
588  /// Call a function using an attribute set as its argument source.
589  ///
590  /// Forces `fn_val` and writes the result into a newly allocated value:
591  ///
592  /// - If `fn_val` is a function with named formals, each formal is looked up
593  ///   in `auto_args`; formals with defaults that are absent from `auto_args`
594  ///   use their defaults.
595  /// - If `auto_args` is `None`, empty bindings are supplied (every formal must
596  ///   then have a default).
597  /// - If `fn_val` is not a function, the value is copied to the result
598  ///   unchanged.
599  ///
600  /// # Errors
601  ///
602  /// Returns an error if the call fails.
603  #[cfg(feature = "shim")]
604  pub fn auto_call_function<'s>(
605    &'s self,
606    auto_args: Option<&Value<'_>>,
607    fn_val: &Value<'_>,
608  ) -> Result<Value<'s>> {
609    let result = self.alloc_value()?;
610    let auto_args_ptr =
611      auto_args.map_or(std::ptr::null_mut(), |v| v.inner.as_ptr());
612
613    // SAFETY: context, state, auto_args_ptr (null or valid), fn_val, and
614    // result are all valid for the call duration.
615    unsafe {
616      check_err(
617        self.context.as_ptr(),
618        sys::nix_value_auto_call_function(
619          self.context.as_ptr(),
620          self.inner.as_ptr(),
621          auto_args_ptr,
622          fn_val.inner.as_ptr(),
623          result.inner.as_ptr(),
624        ),
625      )?;
626    }
627
628    Ok(result)
629  }
630
631  /// Get the raw state pointer.
632  ///
633  /// # Safety
634  ///
635  /// The caller must ensure the pointer is used safely.
636  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::EvalState {
637    self.inner.as_ptr()
638  }
639}
640
641impl Drop for EvalState {
642  fn drop(&mut self) {
643    // SAFETY: We own the state and it's valid until drop
644    unsafe {
645      sys::nix_state_free(self.inner.as_ptr());
646    }
647  }
648}
649
650// SAFETY: see crate-level "# Thread Safety" docs and the comment in
651// the original lib.rs Send impl.
652unsafe impl Send for EvalState {}