Skip to main content

nix_bindings/
value.rs

1//! [`Value`]: Nix value wrapper with GC-managed lifetime. [`ValueType`]:
2//! the discriminator enum.
3
4#![cfg(feature = "expr")]
5
6use std::{ffi::CStr, fmt, ptr::NonNull, sync::Arc};
7
8use crate::{
9  Error,
10  EvalState,
11  Result,
12  error::check_err,
13  store,
14  sys,
15  value_ops,
16};
17
18/// Nix value types.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ValueType {
21  /// Thunk (unevaluated expression).
22  Thunk,
23  /// Integer value.
24  Int,
25  /// Float value.
26  Float,
27  /// Boolean value.
28  Bool,
29  /// String value.
30  String,
31  /// Path value.
32  Path,
33  /// Null value.
34  Null,
35  /// Attribute set.
36  Attrs,
37  /// List.
38  List,
39  /// Function.
40  Function,
41  /// External value.
42  External,
43}
44
45impl ValueType {
46  pub(crate) fn from_c(value_type: sys::ValueType) -> Self {
47    match value_type {
48      sys::ValueType_NIX_TYPE_THUNK => ValueType::Thunk,
49      sys::ValueType_NIX_TYPE_INT => ValueType::Int,
50      sys::ValueType_NIX_TYPE_FLOAT => ValueType::Float,
51      sys::ValueType_NIX_TYPE_BOOL => ValueType::Bool,
52      sys::ValueType_NIX_TYPE_STRING => ValueType::String,
53      sys::ValueType_NIX_TYPE_PATH => ValueType::Path,
54      sys::ValueType_NIX_TYPE_NULL => ValueType::Null,
55      sys::ValueType_NIX_TYPE_ATTRS => ValueType::Attrs,
56      sys::ValueType_NIX_TYPE_LIST => ValueType::List,
57      sys::ValueType_NIX_TYPE_FUNCTION => ValueType::Function,
58      sys::ValueType_NIX_TYPE_EXTERNAL => ValueType::External,
59      _ => ValueType::Thunk,
60    }
61  }
62}
63
64impl fmt::Display for ValueType {
65  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66    let name = match self {
67      ValueType::Thunk => "thunk",
68      ValueType::Int => "int",
69      ValueType::Float => "float",
70      ValueType::Bool => "bool",
71      ValueType::String => "string",
72      ValueType::Path => "path",
73      ValueType::Null => "null",
74      ValueType::Attrs => "attrs",
75      ValueType::List => "list",
76      ValueType::Function => "function",
77      ValueType::External => "external",
78    };
79    write!(f, "{name}")
80  }
81}
82
83/// A Nix value.
84///
85/// This represents any value in the Nix language, including primitives,
86/// collections, and functions. Values are GC-managed; this struct holds
87/// a reference count that is released on drop.
88pub struct Value<'a> {
89  pub(crate) inner: NonNull<sys::nix_value>,
90  pub(crate) state: &'a EvalState,
91}
92
93impl value_ops::NixValueRaw for Value<'_> {
94  fn raw_ctx(&self) -> *mut sys::nix_c_context {
95    // SAFETY: the wrapper holds the context alive via Arc.
96    unsafe { self.state.context.as_ptr() }
97  }
98
99  fn raw_state(&self) -> *mut sys::EvalState {
100    // SAFETY: the wrapper holds the state alive via &EvalState.
101    unsafe { self.state.as_ptr() }
102  }
103
104  fn raw_inner(&self) -> *mut sys::nix_value {
105    self.inner.as_ptr()
106  }
107}
108
109impl Value<'_> {
110  /// Force evaluation of this value.
111  ///
112  /// If the value is a thunk, this will evaluate it to its final form.
113  ///
114  /// # Errors
115  ///
116  /// Returns an error if evaluation fails.
117  pub fn force(&mut self) -> Result<()> {
118    self.force_shared()
119  }
120
121  /// Force deep evaluation of this value.
122  ///
123  /// Forces evaluation of the value and all its nested components.
124  ///
125  /// # Errors
126  ///
127  /// Returns an error if evaluation fails.
128  pub fn force_deep(&mut self) -> Result<()> {
129    // SAFETY: context, state, and value are valid
130    unsafe {
131      check_err(
132        self.state.context.as_ptr(),
133        sys::nix_value_force_deep(
134          self.state.context.as_ptr(),
135          self.state.as_ptr(),
136          self.inner.as_ptr(),
137        ),
138      )
139    }
140  }
141
142  /// Get the type of this value.
143  ///
144  /// Does **not** force; a lazy attribute or list element reports as
145  /// [`ValueType::Thunk`] until forced. Use [`force`](Self::force) or any
146  /// `as_*` accessor (which force implicitly) first.
147  #[must_use]
148  pub fn value_type(&self) -> ValueType {
149    // SAFETY: context and value are valid
150    let c_type = unsafe {
151      sys::nix_get_type(self.state.context.as_ptr(), self.inner.as_ptr())
152    };
153    ValueType::from_c(c_type)
154  }
155
156  /// Get the Nix type name of this value as reported by the C API.
157  ///
158  /// Wraps `nix_get_typename`. Returns Nix's source-of-truth string (e.g.
159  /// `"int"`, `"thunk"`, `"lambda"`). Falls back to the local
160  /// [`ValueType::to_string`] if the C call returns null.
161  #[must_use]
162  pub fn type_name(&self) -> String {
163    // SAFETY: context and value are valid.
164    let ptr = unsafe {
165      sys::nix_get_typename(self.state.context.as_ptr(), self.inner.as_ptr())
166    };
167    if ptr.is_null() {
168      return self.value_type().to_string();
169    }
170    // SAFETY: ptr is non-null and points to a NUL-terminated C string.
171    let s = unsafe { CStr::from_ptr(ptr) }
172      .to_string_lossy()
173      .into_owned();
174    // nix_get_typename returns a strdup'd string; the caller owns it and
175    // must free it.
176    unsafe extern "C" {
177      fn free(ptr: *mut std::os::raw::c_void);
178    }
179    // SAFETY: ptr came from strdup inside libnixexpr.
180    unsafe { free(ptr.cast_mut().cast()) };
181    s
182  }
183
184  /// Force this value via a shared reference.
185  ///
186  /// Used internally by the `as_*` accessors. The Nix C API mutates the
187  /// underlying thunk on first force, but the operation is idempotent and
188  /// the wrapper itself is not changed, so `&self` is sound.
189  pub(crate) fn force_shared(&self) -> Result<()> {
190    // SAFETY: context, state, and value are valid
191    unsafe {
192      check_err(
193        self.state.context.as_ptr(),
194        sys::nix_value_force(
195          self.state.context.as_ptr(),
196          self.state.as_ptr(),
197          self.inner.as_ptr(),
198        ),
199      )
200    }
201  }
202
203  /// Convert this value to an integer. Forces the value first.
204  ///
205  /// # Errors
206  ///
207  /// Returns an error if forcing fails or the resolved value is not an
208  /// integer.
209  pub fn as_int(&self) -> Result<i64> {
210    self.force_shared()?;
211    if self.value_type() != ValueType::Int {
212      return Err(Error::InvalidType {
213        expected: "int",
214        actual:   self.value_type().to_string(),
215      });
216    }
217    // SAFETY: context and value are valid, type is checked
218    Ok(unsafe {
219      sys::nix_get_int(self.state.context.as_ptr(), self.inner.as_ptr())
220    })
221  }
222
223  /// Convert this value to a float. Forces the value first.
224  ///
225  /// # Errors
226  ///
227  /// Returns an error if forcing fails or the resolved value is not a
228  /// float.
229  pub fn as_float(&self) -> Result<f64> {
230    self.force_shared()?;
231    if self.value_type() != ValueType::Float {
232      return Err(Error::InvalidType {
233        expected: "float",
234        actual:   self.value_type().to_string(),
235      });
236    }
237    // SAFETY: context and value are valid, type is checked
238    Ok(unsafe {
239      sys::nix_get_float(self.state.context.as_ptr(), self.inner.as_ptr())
240    })
241  }
242
243  /// Convert this value to a boolean. Forces the value first.
244  ///
245  /// # Errors
246  ///
247  /// Returns an error if forcing fails or the resolved value is not a
248  /// boolean.
249  pub fn as_bool(&self) -> Result<bool> {
250    self.force_shared()?;
251    if self.value_type() != ValueType::Bool {
252      return Err(Error::InvalidType {
253        expected: "bool",
254        actual:   self.value_type().to_string(),
255      });
256    }
257    // SAFETY: context and value are valid, type is checked
258    Ok(unsafe {
259      sys::nix_get_bool(self.state.context.as_ptr(), self.inner.as_ptr())
260    })
261  }
262
263  /// Convert this value to a string.
264  ///
265  /// Realises any string context. Forces the value first.
266  ///
267  /// # Errors
268  ///
269  /// Returns an error if forcing fails or the value is not a string.
270  pub fn as_string(&self) -> Result<String> {
271    self.force_shared()?;
272    if self.value_type() != ValueType::String {
273      return Err(Error::InvalidType {
274        expected: "string",
275        actual:   self.value_type().to_string(),
276      });
277    }
278
279    // SAFETY: context, state, and value are valid; type is checked
280    let realised_str = unsafe {
281      sys::nix_string_realise(
282        self.state.context.as_ptr(),
283        self.state.as_ptr(),
284        self.inner.as_ptr(),
285        false,
286      )
287    };
288
289    if realised_str.is_null() {
290      return Err(Error::NullPointer);
291    }
292
293    let buffer_start =
294      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
295    let buffer_size =
296      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
297
298    if buffer_start.is_null() {
299      unsafe { sys::nix_realised_string_free(realised_str) };
300      return Err(Error::NullPointer);
301    }
302
303    let bytes = unsafe {
304      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
305    };
306    let string = std::str::from_utf8(bytes)
307      .map_err(|_| Error::Unknown("Invalid UTF-8 in string".to_string()))?
308      .to_owned();
309
310    unsafe { sys::nix_realised_string_free(realised_str) };
311
312    Ok(string)
313  }
314
315  /// Convert this value to a string and return its store-path context.
316  ///
317  /// Extended form of [`as_string`](Self::as_string) returning both content
318  /// and any store paths embedded in the string's context. For ordinary
319  /// strings the context vector is empty.
320  ///
321  /// # Errors
322  ///
323  /// Returns an error if the value is not a string.
324  pub fn as_string_with_context(
325    &self,
326  ) -> Result<(String, Vec<store::StorePath>)> {
327    self.force_shared()?;
328    if self.value_type() != ValueType::String {
329      return Err(Error::InvalidType {
330        expected: "string",
331        actual:   self.value_type().to_string(),
332      });
333    }
334
335    // SAFETY: context, state, and value are valid; type is checked
336    let realised_str = unsafe {
337      sys::nix_string_realise(
338        self.state.context.as_ptr(),
339        self.state.as_ptr(),
340        self.inner.as_ptr(),
341        false,
342      )
343    };
344
345    if realised_str.is_null() {
346      return Err(Error::NullPointer);
347    }
348
349    let buffer_start =
350      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
351    let buffer_size =
352      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
353
354    if buffer_start.is_null() {
355      unsafe { sys::nix_realised_string_free(realised_str) };
356      return Err(Error::NullPointer);
357    }
358
359    let bytes = unsafe {
360      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
361    };
362    let string = match std::str::from_utf8(bytes) {
363      Ok(s) => s.to_owned(),
364      Err(_) => {
365        unsafe { sys::nix_realised_string_free(realised_str) };
366        return Err(Error::Unknown("Invalid UTF-8 in string".to_string()));
367      },
368    };
369
370    let count =
371      unsafe { sys::nix_realised_string_get_store_path_count(realised_str) };
372    let mut paths = Vec::with_capacity(count);
373    for i in 0..count {
374      // SAFETY: index is in bounds
375      let raw =
376        unsafe { sys::nix_realised_string_get_store_path(realised_str, i) };
377      if raw.is_null() {
378        continue;
379      }
380      let cloned =
381        unsafe { sys::nix_store_path_clone(raw as *mut sys::StorePath) };
382      if let Some(inner) = NonNull::new(cloned) {
383        paths.push(store::StorePath {
384          inner,
385          _context: Arc::clone(&self.state.context),
386        });
387      }
388    }
389
390    unsafe { sys::nix_realised_string_free(realised_str) };
391
392    Ok((string, paths))
393  }
394
395  /// Convert this value to a filesystem path. Forces the value first.
396  ///
397  /// # Errors
398  ///
399  /// Returns an error if forcing fails or the value is not a path.
400  pub fn as_path(&self) -> Result<std::path::PathBuf> {
401    self.force_shared()?;
402    if self.value_type() != ValueType::Path {
403      return Err(Error::InvalidType {
404        expected: "path",
405        actual:   self.value_type().to_string(),
406      });
407    }
408
409    // SAFETY: context and value are valid, type is checked
410    let path_ptr = unsafe {
411      sys::nix_get_path_string(self.state.context.as_ptr(), self.inner.as_ptr())
412    };
413
414    if path_ptr.is_null() {
415      return Err(Error::NullPointer);
416    }
417
418    // On Unix the kernel treats paths as arbitrary bytes; round-trip
419    // through OsStr to preserve any non-UTF-8 bytes.
420    #[cfg(unix)]
421    {
422      use std::os::unix::ffi::OsStrExt as _;
423      let bytes = unsafe { CStr::from_ptr(path_ptr) }.to_bytes();
424      Ok(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(bytes)))
425    }
426    #[cfg(not(unix))]
427    {
428      let path_str =
429        unsafe { CStr::from_ptr(path_ptr).to_string_lossy().into_owned() };
430      Ok(std::path::PathBuf::from(path_str))
431    }
432  }
433
434  /// Call this value as a function with a single argument.
435  ///
436  /// # Errors
437  ///
438  /// Returns an error if this value is not a function or the call fails.
439  pub fn call(&self, arg: &Value<'_>) -> Result<Value<'_>> {
440    let result = self.state.alloc_value()?;
441    // SAFETY: context, state, function value, arg value, and result are valid
442    unsafe {
443      check_err(
444        self.state.context.as_ptr(),
445        sys::nix_value_call(
446          self.state.context.as_ptr(),
447          self.state.as_ptr(),
448          self.inner.as_ptr(),
449          arg.inner.as_ptr(),
450          result.inner.as_ptr(),
451        ),
452      )?;
453    }
454    Ok(result)
455  }
456
457  /// Call this value as a curried function with multiple arguments.
458  ///
459  /// # Errors
460  ///
461  /// Returns an error if this value is not a function or the call fails.
462  pub fn call_multi(&self, args: &[&Value<'_>]) -> Result<Value<'_>> {
463    let result = self.state.alloc_value()?;
464    let mut arg_ptrs: Vec<*mut sys::nix_value> =
465      args.iter().map(|a| a.inner.as_ptr()).collect();
466    // SAFETY: context, state, fn, args array, and result are valid
467    unsafe {
468      check_err(
469        self.state.context.as_ptr(),
470        sys::nix_value_call_multi(
471          self.state.context.as_ptr(),
472          self.state.as_ptr(),
473          self.inner.as_ptr(),
474          arg_ptrs.len(),
475          arg_ptrs.as_mut_ptr(),
476          result.inner.as_ptr(),
477        ),
478      )?;
479    }
480    Ok(result)
481  }
482
483  /// Create a lazy thunk that applies a function to an argument.
484  ///
485  /// Unlike [`call`](Self::call), this does not perform the call
486  /// immediately; it stores it as a thunk. Useful for lazy attribute sets
487  /// and lists.
488  ///
489  /// # Errors
490  ///
491  /// Returns an error if the thunk cannot be created.
492  pub fn make_thunk<'a>(
493    fn_val: &'a Value<'a>,
494    arg: &'a Value<'a>,
495  ) -> Result<Value<'a>> {
496    let result = fn_val.state.alloc_value()?;
497    // SAFETY: context and all value pointers are valid
498    unsafe {
499      check_err(
500        fn_val.state.context.as_ptr(),
501        sys::nix_init_apply(
502          fn_val.state.context.as_ptr(),
503          result.inner.as_ptr(),
504          fn_val.inner.as_ptr(),
505          arg.inner.as_ptr(),
506        ),
507      )?;
508    }
509    Ok(result)
510  }
511
512  /// Copy this value into a new owned value slot.
513  ///
514  /// # Errors
515  ///
516  /// Returns an error if the copy fails.
517  pub fn copy(&self) -> Result<Value<'_>> {
518    let result = self.state.alloc_value()?;
519    // SAFETY: context and both value pointers are valid
520    unsafe {
521      check_err(
522        self.state.context.as_ptr(),
523        sys::nix_copy_value(
524          self.state.context.as_ptr(),
525          result.inner.as_ptr(),
526          self.inner.as_ptr(),
527        ),
528      )?;
529    }
530    Ok(result)
531  }
532
533  /// Format this value as Nix syntax.
534  ///
535  /// Forces the value first and recursively renders attribute sets and
536  /// lists. Functions, thunks, and external values render as opaque
537  /// placeholders (`<lambda>`, `<thunk>`, `<external>`) the same way
538  /// `nix-instantiate --eval` does.
539  ///
540  /// # Errors
541  ///
542  /// Returns an error if forcing fails or any nested value cannot be
543  /// rendered.
544  pub fn to_nix_string(&self) -> Result<String> {
545    self.force_shared()?;
546    match self.value_type() {
547      ValueType::Int => Ok(self.as_int()?.to_string()),
548      ValueType::Float => Ok(self.as_float()?.to_string()),
549      ValueType::Bool => {
550        Ok(if self.as_bool()? { "true" } else { "false" }.to_string())
551      },
552      ValueType::String => {
553        let s = self.as_string()?;
554        Ok(format!(
555          "\"{}\"",
556          s.replace('\\', "\\\\").replace('"', "\\\"")
557        ))
558      },
559      ValueType::Null => Ok("null".to_string()),
560      ValueType::Path => {
561        Ok(
562          self
563            .as_path()
564            .map_or_else(|_| "<path>".to_string(), |p| p.display().to_string()),
565        )
566      },
567      ValueType::Attrs => {
568        let mut parts = Vec::new();
569        for entry in self.attrs()? {
570          let (k, v) = entry?;
571          parts.push(format!("{k} = {};", v.to_nix_string()?));
572        }
573        if parts.is_empty() {
574          Ok("{ }".to_string())
575        } else {
576          Ok(format!("{{ {} }}", parts.join(" ")))
577        }
578      },
579      ValueType::List => {
580        let mut parts = Vec::new();
581        for entry in self.list_iter()? {
582          parts.push(entry?.to_nix_string()?);
583        }
584        if parts.is_empty() {
585          Ok("[ ]".to_string())
586        } else {
587          Ok(format!("[ {} ]", parts.join(" ")))
588        }
589      },
590      ValueType::Function => Ok("<lambda>".to_string()),
591      ValueType::Thunk => Ok("<thunk>".to_string()),
592      ValueType::External => Ok("<external>".to_string()),
593    }
594  }
595}
596
597impl Drop for Value<'_> {
598  fn drop(&mut self) {
599    // SAFETY: We hold a GC reference; release it.
600    unsafe {
601      sys::nix_value_decref(self.state.context.as_ptr(), self.inner.as_ptr());
602    }
603  }
604}
605
606impl<'a> Clone for Value<'a> {
607  /// Clone a [`Value`] by incrementing its GC reference count.
608  ///
609  /// Both clones reference the same underlying Nix value; they are not
610  /// deep copies. Use [`copy`](Value::copy) when you need an independent
611  /// value slot.
612  fn clone(&self) -> Self {
613    // SAFETY: context and value are valid; incref is idempotent.
614    let err = unsafe {
615      sys::nix_value_incref(self.state.context.as_ptr(), self.inner.as_ptr())
616    };
617    assert!(
618      err == sys::nix_err_NIX_OK,
619      "nix_value_incref failed with code {err}"
620    );
621    Value {
622      inner: self.inner,
623      state: self.state,
624    }
625  }
626}
627
628impl fmt::Display for Value<'_> {
629  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630    match self.value_type() {
631      ValueType::Int => {
632        match self.as_int() {
633          Ok(v) => write!(f, "{v}"),
634          Err(_) => write!(f, "<int error>"),
635        }
636      },
637      ValueType::Float => {
638        match self.as_float() {
639          Ok(v) => write!(f, "{v}"),
640          Err(_) => write!(f, "<float error>"),
641        }
642      },
643      ValueType::Bool => {
644        match self.as_bool() {
645          Ok(v) => write!(f, "{v}"),
646          Err(_) => write!(f, "<bool error>"),
647        }
648      },
649      ValueType::String => {
650        match self.as_string() {
651          Ok(v) => write!(f, "{v}"),
652          Err(_) => write!(f, "<string error>"),
653        }
654      },
655      ValueType::Null => write!(f, "null"),
656      ValueType::Attrs => write!(f, "{{ <attrs> }}"),
657      ValueType::List => write!(f, "[ <list> ]"),
658      ValueType::Function => write!(f, "<function>"),
659      ValueType::Path => {
660        match self.as_path() {
661          Ok(p) => write!(f, "{}", p.display()),
662          Err(_) => write!(f, "<path>"),
663        }
664      },
665      ValueType::Thunk => write!(f, "<thunk>"),
666      ValueType::External => write!(f, "<external>"),
667    }
668  }
669}
670
671impl fmt::Debug for Value<'_> {
672  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
673    let value_type = self.value_type();
674    match value_type {
675      ValueType::Int => {
676        match self.as_int() {
677          Ok(v) => write!(f, "Value::Int({v})"),
678          Err(_) => write!(f, "Value::Int(<error>)"),
679        }
680      },
681      ValueType::Float => {
682        match self.as_float() {
683          Ok(v) => write!(f, "Value::Float({v})"),
684          Err(_) => write!(f, "Value::Float(<error>)"),
685        }
686      },
687      ValueType::Bool => {
688        match self.as_bool() {
689          Ok(v) => write!(f, "Value::Bool({v})"),
690          Err(_) => write!(f, "Value::Bool(<error>)"),
691        }
692      },
693      ValueType::String => {
694        match self.as_string() {
695          Ok(v) => write!(f, "Value::String({v:?})"),
696          Err(_) => write!(f, "Value::String(<error>)"),
697        }
698      },
699      ValueType::Null => write!(f, "Value::Null"),
700      ValueType::Attrs => write!(f, "Value::Attrs({{ <attrs> }})"),
701      ValueType::List => write!(f, "Value::List([ <list> ])"),
702      ValueType::Function => write!(f, "Value::Function(<function>)"),
703      ValueType::Path => {
704        match self.as_path() {
705          Ok(p) => write!(f, "Value::Path({})", p.display()),
706          Err(_) => write!(f, "Value::Path(<path>)"),
707        }
708      },
709      ValueType::Thunk => write!(f, "Value::Thunk(<thunk>)"),
710      ValueType::External => write!(f, "Value::External(<external>)"),
711    }
712  }
713}