nix-expr: Improve docs

... and fix an error message

(cherry picked from commit ec3b5cfb911545ecf756336e792b59c5dd1be9b5)
This commit is contained in:
Robert Hensing 2025-09-30 15:50:42 +02:00
parent e68ec02d05
commit 65c9d937cd
3 changed files with 514 additions and 51 deletions

View file

@ -1,5 +1,22 @@
//! Raw bindings to Nix C API
//!
//! This crate contains automatically generated bindings from the Nix C headers.
//! The bindings are generated by bindgen and include C-style naming conventions
//! and documentation comments that don't always conform to Rust standards.
//!
//! Normally you don't have to use this crate directly.
//! Instead use `nix-store` and `nix-expr`.
// Standard bindgen suppressions for C naming conventions
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
// Rustdoc suppressions for generated C documentation
// The C headers contain Doxygen-style documentation that doesn't translate
// well to Rust's rustdoc format, causing various warnings:
#![allow(rustdoc::broken_intra_doc_links)] // @param[in]/[out] references don't resolve
#![allow(rustdoc::bare_urls)] // C docs may contain unescaped URLs
#![allow(rustdoc::invalid_html_tags)] // Doxygen HTML tags like <setting>
#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View file

@ -1,3 +1,133 @@
//! # Nix Expression Evaluation
//!
//! This module provides the core [`EvalState`] type for evaluating Nix expressions
//! and extracting typed values from the results.
//!
//! ## Overview
//!
//! The [`EvalState`] manages the evaluation context for Nix expressions, including:
//! - Expression parsing and evaluation with [`eval_from_string`](EvalState::eval_from_string)
//! - Type-safe value extraction with [`require_*`](EvalState#implementations) methods
//! - Memory management and garbage collection integration
//! - Store integration for derivations and store paths
//! - Custom function creation with [`new_value_primop`](EvalState::new_value_primop) and [`new_value_thunk`](EvalState::new_value_thunk)
//!
//! ### Construction
//!
//! Create an [`EvalState`] using [`EvalState::new`] or [`EvalStateBuilder`] for advanced configuration:
//!
//! ```rust
//! # use nix_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
//! # use nix_store::store::Store;
//! # use std::collections::HashMap;
//! # fn example() -> anyhow::Result<()> {
//! # test_init(); let guard = gc_register_my_thread()?;
//! let store = Store::open(None, HashMap::new())?;
//!
//! // Simple creation
//! let mut es = EvalState::new(store.clone(), [])?;
//!
//! // With custom lookup paths
//! let mut es = EvalStateBuilder::new(store)?
//! .lookup_path(["nixpkgs=/path/to/nixpkgs"])?
//! .build()?;
//! # drop(guard);
//! # Ok(())
//! # }
//! ```
//!
//! ## Value Extraction
//!
//! All `require_*` methods perform these steps:
//! 1. **Evaluation**: Force evaluation of thunks as needed
//! 2. **Type checking**: Verify the value matches the expected type
//! 3. **Extraction**: Return the typed Rust value or an error
//!
//! Methods with `_strict` in their name also evaluate their return values before returning them.
//!
//! ### Evaluation Strictness
//!
//! - **Lazy methods** (e.g., [`require_list_size`](EvalState::require_list_size)):
//! Evaluate only the structure needed
//! - **Strict methods** (e.g., [`require_list_strict`](EvalState::require_list_strict)):
//! Force full evaluation of all contained values
//! - **Selective methods** (e.g., [`require_list_select_idx_strict`](EvalState::require_list_select_idx_strict)):
//! Evaluate only the accessed elements
//!
//! ## Laziness and Strictness
//!
//! The terms "lazy" and "strict" in this API refer to Nix's [Weak Head Normal Form (WHNF)](https://nix.dev/manual/nix/latest/language/evaluation.html#values)
//! evaluation model, not the kind of deep strictness that is exercised by functions such as `builtins.toJSON` or `builtins.deepSeq`.
//!
//! - **WHNF evaluation**: Values are evaluated just enough to determine their type and basic structure
//! - **Deep evaluation**: All nested values are recursively forced (like `builtins.deepSeq`)
//!
//! For example, a list in WHNF has its length determined but individual elements may remain unevaluated thunks.
//! Methods marked as "strict" in this API force WHNF evaluation of their results, but do not perform deep evaluation
//! of arbitrarily nested structures unless explicitly documented otherwise.
//!
//! ### Thread Safety and Memory Management
//!
//! Before using [`EvalState`] in a thread, register it with the (process memory) garbage collector:
//!
//! ```rust,no_run
//! # use nix_expr::eval_state::{init, gc_register_my_thread, test_init};
//! # fn example() -> anyhow::Result<()> {
//! # test_init(); // Use test_init() in tests
//! init()?; // Initialize Nix library
//! let guard = gc_register_my_thread()?; // Register thread with GC
//! // Now safe to use EvalState in this thread
//! drop(guard);
//! # Ok(())
//! # }
//! ```
//!
//! ## Error Handling
//!
//! Evaluation methods return [`Result`] types. Common error scenarios include:
//! - **Type mismatches**: Expected type doesn't match actual value type
//! - **Evaluation errors**: Nix expressions that throw or have undefined behavior
//! - **Bounds errors**: Out-of-range access for indexed operations
//!
//! ## Examples
//!
//! ```rust
//! use nix_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
//! use nix_store::store::Store;
//! use std::collections::HashMap;
//!
//! # fn main() -> anyhow::Result<()> {
//! test_init(); // init() in non-test code
//! let guard = gc_register_my_thread()?;
//!
//! let store = Store::open(None, HashMap::new())?;
//! let mut es = EvalState::new(store, [])?;
//!
//! // Evaluate a list expression
//! let list_value = es.eval_from_string("[1 2 3]", "<example>")?;
//!
//! // Check the size (lazy - doesn't evaluate elements)
//! let size = es.require_list_size(&list_value)?;
//! println!("List has {} elements", size);
//!
//! // Access specific elements (evaluates only accessed elements)
//! if let Some(first) = es.require_list_select_idx_strict(&list_value, 0)? {
//! let value = es.require_int(&first)?;
//! println!("First element: {}", value);
//! }
//!
//! // Process all elements (evaluates all elements)
//! let all_elements: Vec<_> = es.require_list_strict(&list_value)?;
//! for element in all_elements {
//! let value = es.require_int(&element)?;
//! println!("Element: {}", value);
//! }
//!
//! drop(guard);
//! # Ok(())
//! # }
//! ```
use crate::primop;
use crate::value::{Int, Value, ValueType};
use anyhow::Context as _;
@ -25,23 +155,29 @@ lazy_static! {
}
};
}
pub fn init() -> Result<()> {
let x = INIT.as_ref();
match x {
Ok(_) => Ok(()),
Err(e) => {
// Couldn't just clone the error, so we have to print it here.
Err(anyhow::format_err!("nix_libstore_init error: {}", e))
Err(anyhow::format_err!("nix_expr::init error: {}", e))
}
}
}
/// A string value with its associated [store paths](https://nix.dev/manual/nix/stable/store/store-path.html).
///
/// Represents a Nix string with references to store paths.
pub struct RealisedString {
/// The string content.
pub s: String,
/// Store paths referenced by the string.
pub paths: Vec<StorePath>,
}
/// A [Weak] reference to an [EvalState]
/// A [Weak] reference to an [EvalState].
pub struct EvalStateWeak {
inner: Weak<EvalStateRef>,
store: StoreWeak,
@ -65,9 +201,11 @@ struct EvalStateRef {
eval_state: NonNull<raw::EvalState>,
}
impl EvalStateRef {
/// Returns a raw pointer to the underlying EvalState.
///
/// # Safety
///
/// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
/// The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
unsafe fn as_ptr(&self) -> *mut raw::EvalState {
self.eval_state.as_ptr()
}
@ -79,6 +217,31 @@ impl Drop for EvalStateRef {
}
}
}
/// Builder for configuring and creating an [`EvalState`].
///
/// Provides advanced configuration options for evaluation context setup.
/// Use [`EvalState::new`] for simple cases or this builder for custom configuration.
///
/// # Examples
///
/// ```rust
/// # use nix_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
/// # use nix_store::store::Store;
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
///
/// let mut es: EvalState = EvalStateBuilder::new(store)?
/// .lookup_path(["nixpkgs=/path/to/nixpkgs", "home-manager=/path/to/hm"])?
/// .build()?;
///
/// let value = es.eval_from_string("<nixpkgs>", /* path display: */ "in-memory")?;
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
pub struct EvalStateBuilder {
eval_state_builder: *mut raw::eval_state_builder,
lookup_path: Vec<CString>,
@ -92,6 +255,7 @@ impl Drop for EvalStateBuilder {
}
}
impl EvalStateBuilder {
/// Creates a new [`EvalStateBuilder`].
pub fn new(store: Store) -> Result<EvalStateBuilder> {
let mut context = Context::new();
let eval_state_builder =
@ -102,6 +266,7 @@ impl EvalStateBuilder {
lookup_path: Vec::new(),
})
}
/// Sets the [lookup path](https://nix.dev/manual/nix/latest/language/constructs/lookup-path.html) for Nix expression evaluation.
pub fn lookup_path<'a>(mut self, path: impl IntoIterator<Item = &'a str>) -> Result<Self> {
let lookup_path: Vec<CString> = path
.into_iter()
@ -114,6 +279,7 @@ impl EvalStateBuilder {
self.lookup_path = lookup_path;
Ok(self)
}
/// Builds the configured [`EvalState`].
pub fn build(&self) -> Result<EvalState> {
// Make sure the library is initialized
init()?;
@ -148,6 +314,12 @@ impl EvalStateBuilder {
context,
})
}
/// Returns a raw pointer to the underlying eval state builder.
///
/// # Safety
///
/// The caller must ensure that the pointer is not used beyond the lifetime of this builder.
// TODO: This function should be marked `unsafe`.
pub fn raw_ptr(&self) -> *mut raw::eval_state_builder {
self.eval_state_builder
}
@ -159,6 +331,8 @@ pub struct EvalState {
pub(crate) context: Context,
}
impl EvalState {
/// Creates a new EvalState with basic configuration.
///
/// For more options, use [EvalStateBuilder].
pub fn new<'a>(store: Store, lookup_path: impl IntoIterator<Item = &'a str>) -> Result<Self> {
EvalStateBuilder::new(store)?
@ -166,15 +340,21 @@ impl EvalState {
.build()
}
/// Returns a raw pointer to the raw Nix C API EvalState.
///
/// # Safety
///
/// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
/// The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
pub unsafe fn raw_ptr(&self) -> *mut raw::EvalState {
self.eval_state.as_ptr()
}
/// Returns a reference to the Store that's used for instantiation, import from derivation, etc.
pub fn store(&self) -> &Store {
&self.store
}
/// Creates a weak reference to this EvalState.
pub fn weak_ref(&self) -> EvalStateWeak {
EvalStateWeak {
inner: Arc::downgrade(&self.eval_state),
@ -189,18 +369,25 @@ impl EvalState {
/// # Examples
///
/// ```
/// # use nix_expr::eval_state::EvalState;
/// # use nix_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// use nix_store::store::Store;
/// use nix_expr::value::Value;
/// use std::collections::HashMap;
///
/// # fn main() -> anyhow::Result<()> {
/// # let mut es = EvalState::new(Store::open(None, [])?, [])?;
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// # let mut es = EvalState::new(Store::open(None, HashMap::new())?, [])?;
/// let v: Value = es.eval_from_string("42", ".")?;
/// assert_eq!(es.require_int(&v)?, 42);
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[doc(alias = "nix_expr_eval_from_string")]
#[doc(alias = "parse")]
#[doc(alias = "eval")]
#[doc(alias = "evaluate")]
pub fn eval_from_string(&mut self, expr: &str, path: &str) -> Result<Value> {
let expr_ptr =
CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?;
@ -218,7 +405,14 @@ impl EvalState {
Ok(value)
}
}
/// Try turn any Value into a Value that isn't a Thunk.
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of a value to [weak head normal form](https://nix.dev/manual/nix/latest/language/evaluation.html?highlight=WHNF#values).
///
/// Converts [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) to their evaluated form. Does not modify already-evaluated values.
///
/// Does not perform deep evaluation of nested structures.
#[doc(alias = "evaluate")]
#[doc(alias = "strict")]
pub fn force(&mut self, v: &Value) -> Result<()> {
unsafe {
check_call!(raw::value_force(
@ -229,11 +423,34 @@ impl EvalState {
}?;
Ok(())
}
/// Returns the type of a value without forcing [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html).
///
/// Returns [`None`] if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
///
/// Returns [`Some`] if the value is already evaluated.
#[doc(alias = "type_of")]
#[doc(alias = "value_type_lazy")]
#[doc(alias = "nix_get_type")]
#[doc(alias = "get_type")]
#[doc(alias = "nix_value_type")]
pub fn value_type_unforced(&mut self, value: &Value) -> Option<ValueType> {
let r = unsafe { check_call!(raw::get_type(&mut self.context, value.raw_ptr())) };
// .unwrap(): no reason for this to fail, as it does not evaluate
ValueType::from_raw(r.unwrap())
}
/// Returns the [type][`ValueType`] of a value, [forcing][`EvalState::force`] [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) if necessary.
///
/// Forces evaluation if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
///
/// Evaluation may fail, producing an [`Err`].
///
/// Guarantees a definitive result if [`Ok`], thanks to the language being [pure](https://nix.dev/manual/nix/latest/language/index.html?highlight=pure#nix-language) and [lazy](https://nix.dev/manual/nix/latest/language/index.html?highlight=lazy#nix-language).
#[doc(alias = "type_of")]
#[doc(alias = "value_type_strict")]
#[doc(alias = "nix_get_type")]
#[doc(alias = "get_type")]
#[doc(alias = "nix_value_type_strict")]
pub fn value_type(&mut self, value: &Value) -> Result<ValueType> {
match self.value_type_unforced(value) {
Some(a) => Ok(a),
@ -248,6 +465,35 @@ impl EvalState {
}
}
}
/// Extracts the value from an [integer][`ValueType::Int`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an integer.
///
/// Returns the integer value if successful, or an [`Err`] if evaluation failed or the value is not an integer.
///
/// # Examples
///
/// ```rust
/// # use nix_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_store::store::Store;
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
///
/// let value = es.eval_from_string("42", "<example>")?;
/// let int_val = es.require_int(&value)?;
/// assert_eq!(int_val, 42);
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[doc(alias = "integer")]
#[doc(alias = "number")]
#[doc(alias = "nix_get_int")]
#[doc(alias = "get_int")]
pub fn require_int(&mut self, v: &Value) -> Result<Int> {
let t = self.value_type(v)?;
if t != ValueType::Int {
@ -256,6 +502,14 @@ impl EvalState {
unsafe { check_call!(raw::get_int(&mut self.context, v.raw_ptr())) }
}
/// Extracts the value from a [boolean][`ValueType::Bool`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a boolean.
///
/// Returns the boolean value if successful, or an [`Err`] if evaluation failed or the value is not a boolean.
#[doc(alias = "boolean")]
#[doc(alias = "nix_get_bool")]
#[doc(alias = "get_bool")]
pub fn require_bool(&mut self, v: &Value) -> Result<bool> {
let t = self.value_type(v)?;
if t != ValueType::Bool {
@ -264,21 +518,31 @@ impl EvalState {
unsafe { check_call!(raw::get_bool(&mut self.context, v.raw_ptr())) }
}
/// Evaluate and require that the passed value is a list.
/// Returns the contained values in the specified container type.
/// This is strict - all list elements will be evaluated.
/// Extracts all elements from a [list][`ValueType::List`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
///
/// Returns the contained values in the specified container type (e.g., [`Vec`], [`VecDeque`][`std::collections::VecDeque`], etc.).
///
/// This is [strict](https://nix.dev/manual/nix/latest/language/evaluation.html#strictness) - all list elements will be evaluated.
///
/// # Examples
///
/// ```rust,no_run
/// # use nix_expr::value::Value;
/// # use std::collections::VecDeque;
/// # use std::collections::{VecDeque, LinkedList};
/// # fn example(es: &mut nix_expr::eval_state::EvalState, list_value: &Value) -> anyhow::Result<()> {
/// let vec: Vec<Value> = es.require_list_strict(&list_value)?;
/// let deque: VecDeque<Value> = es.require_list_strict(&list_value)?;
/// let linked_list = es.require_list_strict::<LinkedList<Value>>(&list_value)?;
/// # Ok(())
/// # }
/// ```
#[doc(alias = "collect")]
#[doc(alias = "to_vec")]
#[doc(alias = "all")]
#[doc(alias = "nix_get_list_size")]
#[doc(alias = "nix_get_list_byidx")]
pub fn require_list_strict<C>(&mut self, value: &Value) -> Result<C>
where
C: FromIterator<Value>,
@ -304,10 +568,14 @@ impl EvalState {
.collect()
}
/// Evaluate, and require that the value is an attrset.
/// Evaluate, and require that the [`Value`] is a Nix [`ValueType::AttrSet`].
///
/// Returns a list of the keys in the attrset.
///
/// NOTE: this currently implements its own sorting, which probably matches Nix's implementation, but is not guaranteed.
#[doc(alias = "keys")]
#[doc(alias = "attributes")]
#[doc(alias = "fields")]
pub fn require_attrs_names(&mut self, v: &Value) -> Result<Vec<String>> {
self.require_attrs_names_unsorted(v).map(|mut v| {
v.sort();
@ -315,8 +583,11 @@ impl EvalState {
})
}
/// For when [require_attrs_names] isn't fast enough.
/// For when [`EvalState::require_attrs_names`] isn't fast enough.
///
/// Only use when it's ok that the keys are returned in an arbitrary order.
#[doc(alias = "keys_unsorted")]
#[doc(alias = "attributes_unsorted")]
pub fn require_attrs_names_unsorted(&mut self, v: &Value) -> Result<Vec<String>> {
let t = self.value_type(v)?;
if t != ValueType::AttrSet {
@ -342,7 +613,14 @@ impl EvalState {
Ok(attrs)
}
/// Evaluate, require that the value is an attrset, and select an attribute by name.
/// Extracts an attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
///
/// Returns the attribute value if found, or an [`Err`] if evaluation failed, the attribute doesn't exist, or the value is not an attribute set.
#[doc(alias = "get_attr")]
#[doc(alias = "attribute")]
#[doc(alias = "field")]
pub fn require_attrs_select(&mut self, v: &Value, attr_name: &str) -> Result<Value> {
let t = self.value_type(v)?;
if t != ValueType::AttrSet {
@ -374,13 +652,20 @@ impl EvalState {
}
}
/// Evaluate, require that the value is an attrset, and select an attribute by name.
/// Extracts an optional attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
///
/// Return `Err(...)` if `v` is not an attrset, or if some other error occurred.
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
///
/// Return `Ok(None)` if the attribute is not present.
/// Returns [`Err`] if evaluation failed or the value is not an attribute set.
///
/// Return `Ok(Some(value))` if the attribute is present.
/// Returns [`Ok(None)`] if the attribute is not present.
///
/// Returns [`Ok(Some(value))`] if the attribute is present.
#[doc(alias = "nix_get_attr_byname")]
#[doc(alias = "get_attr_byname")]
#[doc(alias = "get_attr_opt")]
#[doc(alias = "try_get")]
#[doc(alias = "maybe_get")]
pub fn require_attrs_select_opt(
&mut self,
v: &Value,
@ -403,11 +688,16 @@ impl EvalState {
Ok(v2.map(|x| unsafe { Value::new(x) }))
}
/// Evaluates, require that the value is a list.
/// Returns the number of elements in the list.
/// Returns the number of elements in a [list][`ValueType::List`] Nix value.
///
/// This function only forces evaluation of the list structure itself,
/// not the individual elements. Elements remain as lazy thunks.
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the list structure and verifies the value is a list.
///
/// Individual elements remain as lazy [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) and are not evaluated.
#[doc(alias = "length")]
#[doc(alias = "count")]
#[doc(alias = "len")]
#[doc(alias = "nix_get_list_size")]
#[doc(alias = "get_list_size")]
pub fn require_list_size(&mut self, v: &Value) -> Result<u32> {
let t = self.value_type(v)?;
if t != ValueType::List {
@ -417,15 +707,21 @@ impl EvalState {
Ok(ret)
}
/// Evaluates, require that the value is a list, and select an element by index.
/// Extracts an element from a [list][`ValueType::List`] Nix value by index.
///
/// Returns `None` if the index is out of bounds.
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
/// Forces evaluation of the selected element, similar to [`require_attrs_select`].
///
/// # Strictness
/// Returns [`Ok(Some(value))`] if the element is found.
///
/// This function forces evaluation of the selected element, similar to
/// `require_attrs_select`. If the element contains an error (e.g., `throw`),
/// this function will return that error rather than a lazy thunk.
/// Returns [`Ok(None)`] if the index is out of bounds.
///
/// Returns [`Err`] if evaluation failed, the element contains an error (e.g., `throw`), or the value is not a list.
#[doc(alias = "get")]
#[doc(alias = "index")]
#[doc(alias = "at")]
#[doc(alias = "nix_get_list_byidx")]
#[doc(alias = "get_list_byidx")]
pub fn require_list_select_idx_strict(&mut self, v: &Value, idx: u32) -> Result<Option<Value>> {
let t = self.value_type(v)?;
if t != ValueType::List {
@ -452,8 +748,12 @@ impl EvalState {
Ok(v2.map(|x| unsafe { Value::new(x) }))
}
/// Create a new value containing the passed string.
/// Returns a string value without any string context.
/// Creates a new [string][`ValueType::String`] Nix value.
///
/// Returns a string value without any [string context](https://nix.dev/manual/nix/latest/language/string-context.html).
#[doc(alias = "make_string")]
#[doc(alias = "create_string")]
#[doc(alias = "string_value")]
pub fn new_value_str(&mut self, s: &str) -> Result<Value> {
let s = CString::new(s).with_context(|| "new_value_str: contains null byte")?;
let v = unsafe {
@ -468,6 +768,11 @@ impl EvalState {
Ok(v)
}
/// Creates a new [integer][`ValueType::Int`] Nix value.
#[doc(alias = "make_int")]
#[doc(alias = "create_int")]
#[doc(alias = "int_value")]
#[doc(alias = "integer_value")]
pub fn new_value_int(&mut self, i: Int) -> Result<Value> {
let v = unsafe {
let value = self.new_value_uninitialized()?;
@ -477,11 +782,15 @@ impl EvalState {
Ok(v)
}
/// Create a new thunk that will evaluate to the result of the given function.
/// The function will be called with the current EvalState.
/// The function must not return a thunk.
/// Creates a new [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) Nix value.
///
/// The [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) will lazily evaluate to the result of the given Rust function when forced.
/// The Rust function will be called with the current [`EvalState`] and must not return a thunk.
///
/// The name is shown in stack traces.
#[doc(alias = "make_thunk")]
#[doc(alias = "create_thunk")]
#[doc(alias = "lazy_value")]
pub fn new_value_thunk(
&mut self,
name: &str,
@ -520,7 +829,16 @@ impl EvalState {
};
r
}
/// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty
/// Extracts a string value from a [string][`ValueType::String`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a string.
/// Returns the string value if successful, or an [`Err`] if evaluation failed or the value is not a string.
///
/// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty.
#[doc(alias = "str")]
#[doc(alias = "text")]
#[doc(alias = "nix_get_string")]
#[doc(alias = "get_string")]
pub fn require_string(&mut self, value: &Value) -> Result<String> {
let t = self.value_type(value)?;
if t != ValueType::String {
@ -528,6 +846,13 @@ impl EvalState {
}
self.get_string(value)
}
/// Realises a [string][`ValueType::String`] Nix value with context information.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html), verifies the value is a string, and builds any derivations
/// referenced in the [string context](https://nix.dev/manual/nix/latest/language/string-context.html) if required.
#[doc(alias = "realize_string")]
#[doc(alias = "string_with_context")]
#[doc(alias = "build_string")]
pub fn realise_string(
&mut self,
value: &Value,
@ -578,9 +903,15 @@ impl EvalState {
Ok(RealisedString { s, paths })
}
/// Eagerly apply a function to an argument.
/// Applies a function to an argument and returns the result.
///
/// For a lazy version, see [`new_value_apply`][`EvalState::new_value_apply`].
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
/// For a lazy version, see [`new_value_apply`].
#[doc(alias = "nix_value_call")]
#[doc(alias = "value_call")]
#[doc(alias = "apply")]
#[doc(alias = "invoke")]
#[doc(alias = "execute")]
pub fn call(&mut self, f: Value, a: Value) -> Result<Value> {
let value = self.new_value_uninitialized()?;
unsafe {
@ -595,8 +926,49 @@ impl EvalState {
Ok(value)
}
/// Eagerly apply a function with multiple curried arguments.
/// Apply a sequence of [function applications](https://nix.dev/manual/nix/latest/language/operators.html#function-application).
///
/// When argument `f` is a curried function, this applies each argument in sequence.
/// Equivalent to the Nix expression `f arg1 arg2 arg3`.
///
/// Returns a [`Value`] in at least weak head normal form if successful.
///
/// Returns an [`Err`]
/// - if `f` did not evaluate to a function
/// - if `f arg1` had any problems
/// - if `f arg1` did not evaluate to a function (for `(f arg1) arg2`)
/// - etc
///
/// # Examples
///
/// ```rust
/// # use nix_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_store::store::Store;
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
///
/// // Create a curried function: x: y: x + y
/// let f = es.eval_from_string("x: y: x + y", "<example>")?;
/// let arg1 = es.eval_from_string("5", "<example>")?;
/// let arg2 = es.eval_from_string("3", "<example>")?;
///
/// // Equivalent to: (x: y: x + y) 5 3
/// let result = es.call_multi(&f, &[arg1, arg2])?;
/// let value = es.require_int(&result)?;
/// assert_eq!(value, 8);
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[doc(alias = "nix_value_call_multi")]
#[doc(alias = "value_call_multi")]
#[doc(alias = "apply_multi")]
#[doc(alias = "curry")]
#[doc(alias = "call_with_args")]
pub fn call_multi(&mut self, f: &Value, args: &[Value]) -> Result<Value> {
let value = self.new_value_uninitialized()?;
unsafe {
@ -613,9 +985,13 @@ impl EvalState {
Ok(value)
}
/// Apply a function to an argument, but don't evaluate the result just yet.
/// Applies a function to an argument lazily, creating a [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
///
/// For an eager version, see [`call`][`EvalState::call`].
/// Does not force [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
/// For an eager version, see [`call`].
#[doc(alias = "lazy_apply")]
#[doc(alias = "thunk_apply")]
#[doc(alias = "defer_call")]
pub fn new_value_apply(&mut self, f: &Value, a: &Value) -> Result<Value> {
let value = self.new_value_uninitialized()?;
unsafe {
@ -639,10 +1015,14 @@ impl EvalState {
}
}
/// Create a new Nix function that is implemented by a Rust function.
/// Creates a new [function][`ValueType::Function`] Nix value implemented by a Rust function.
///
/// This is also known as a "primop" in Nix, short for primitive operation.
/// Most of the `builtins.*` values are examples of primops, but
/// `new_value_primop` does not affect `builtins`.
/// Most of the `builtins.*` values are examples of primops, but this function
/// does not affect `builtins`.
#[doc(alias = "make_primop")]
#[doc(alias = "create_function")]
#[doc(alias = "builtin")]
pub fn new_value_primop(&mut self, primop: primop::PrimOp) -> Result<Value> {
let value = self.new_value_uninitialized()?;
unsafe {
@ -655,6 +1035,46 @@ impl EvalState {
Ok(value)
}
/// Creates a new [attribute set][`ValueType::Attrs`] Nix value from an iterator of name-value pairs.
///
/// Accepts any iterator that yields `(String, Value)` pairs and has an exact size.
/// Common usage includes [`Vec`], [`HashMap`], and array literals.
///
/// # Examples
///
/// ```rust
/// # use nix_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_store::store::Store;
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
/// let a = es.new_value_int(1)?;
/// let b = es.new_value_int(2)?;
/// let c = es.new_value_int(3)?;
/// let d = es.new_value_int(4)?;
///
/// // From array
/// let attrs1 = es.new_value_attrs([
/// ("x".to_string(), a),
/// ("y".to_string(), b)
/// ])?;
///
/// // From HashMap
/// let mut map = HashMap::new();
/// map.insert("foo".to_string(), c);
/// map.insert("bar".to_string(), d);
/// let attrs2 = es.new_value_attrs(map)?;
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[doc(alias = "make_attrs")]
#[doc(alias = "create_attrset")]
#[doc(alias = "object")]
#[doc(alias = "record")]
pub fn new_value_attrs<I>(&mut self, attrs: I) -> Result<Value>
where
I: IntoIterator<Item = (String, Value)>,
@ -687,6 +1107,7 @@ impl EvalState {
}
}
// Internal RAII helper; could be refactored and made pub
struct BindingsBuilder {
ptr: *mut raw::BindingsBuilder,
}
@ -710,12 +1131,19 @@ impl BindingsBuilder {
}
}
/// Triggers garbage collection immediately.
#[doc(alias = "garbage_collect")]
#[doc(alias = "collect")]
#[doc(alias = "gc")]
pub fn gc_now() {
unsafe {
raw::gc_now();
}
}
/// RAII guard for thread registration with the garbage collector.
///
/// Automatically unregisters the thread when dropped.
pub struct ThreadRegistrationGuard {
must_unregister: bool,
}
@ -743,6 +1171,9 @@ fn gc_register_my_thread_do_it() -> Result<()> {
}
}
#[doc(alias = "register_thread")]
#[doc(alias = "thread_setup")]
#[doc(alias = "gc_register")]
pub fn gc_register_my_thread() -> Result<ThreadRegistrationGuard> {
init()?;
unsafe {
@ -771,6 +1202,8 @@ impl Clone for EvalState {
/// Initialize the Nix library for testing. This includes some modifications to the Nix settings, that must not be used in production.
/// Use at your own peril, in rust test suites.
#[doc(alias = "test_initialize")]
#[doc(alias = "test_setup")]
pub fn test_init() {
init().unwrap();

View file

@ -8,28 +8,41 @@ use std::ptr::{null_mut, NonNull};
pub type Int = i64;
/// The type of a value (or thunk)
/// The type discriminator of a [`Value`] that has successfully evaluated to at least [weak head normal form](https://nix.dev/manual/nix/latest/language/evaluation.html?highlight=WHNF#values).
///
/// Typically acquired with [`EvalState::value_type`][`crate::eval_state::EvalState::value_type`]
#[derive(Eq, PartialEq, Debug)]
pub enum ValueType {
/// A Nix [attribute set](https://nix.dev/manual/nix/stable/language/types.html#type-attrs)
AttrSet,
/// A Nix [boolean](https://nix.dev/manual/nix/stable/language/types.html#type-bool)
Bool,
/// A Nix external value (mostly-opaque value for plugins, linked applications)
External,
/// A Nix [float](https://nix.dev/manual/nix/stable/language/types.html#type-float)
Float,
/// A Nix [function](https://nix.dev/manual/nix/stable/language/types.html#type-function)
Function,
/// A Nix [integer](https://nix.dev/manual/nix/stable/language/types.html#type-int)
Int,
/// A Nix [list](https://nix.dev/manual/nix/stable/language/types.html#type-list)
List,
/// A Nix [`null`](https://nix.dev/manual/nix/stable/language/types.html#type-null)
Null,
/// A Nix [path value](https://nix.dev/manual/nix/stable/language/types.html#type-path)
Path,
/// A Nix [string](https://nix.dev/manual/nix/stable/language/types.html#type-string)
String,
/// An unknown value, presumably from a new, partially unsupported version of Nix
Unknown,
}
impl ValueType {
/// Convert a raw value type to a `ValueType`.
/// Convert a raw value type to a [`ValueType`].
///
/// Return `None` if the Value is still a thunk (i.e. not yet evaluated).
/// Return [`None`] if the Value is still a thunk (i.e. not yet evaluated).
///
/// Return `Some(ValueType::Unknown)` if the value type is not recognized.
/// Return `Some(`[`ValueType::Unknown`]`)` if the value type is not recognized.
pub(crate) fn from_raw(raw: raw::ValueType) -> Option<ValueType> {
match raw {
raw::ValueType_NIX_TYPE_ATTRS => Some(ValueType::AttrSet),
@ -51,14 +64,14 @@ impl ValueType {
}
}
/* A pointer to a value or thunk, to be used with EvalState methods. */
/// A pointer to a [value](https://nix.dev/manual/nix/latest/language/types.html) or [thunk](https://nix.dev/manual/nix/2.31/language/evaluation.html?highlight=thunk#laziness), to be used with [`EvalState`][`crate::eval_state::EvalState`] methods.
pub struct Value {
inner: NonNull<raw::Value>,
}
impl Value {
/// Take ownership of a new Value.
/// Take ownership of a new [`Value`].
///
/// This does not call `nix_gc_incref`, but does call `nix_gc_decref` when dropped.
/// This does not call [`nix_c_raw::gc_incref`], but does call [`nix_c_raw::nix_gc_decref`] when [dropped][`Drop`].
///
/// # Safety
///
@ -69,9 +82,9 @@ impl Value {
}
}
/// Borrow a reference to a Value.
/// Borrow a reference to a [`Value`].
///
/// This calls `nix_gc_incref`, and the returned Value will call `nix_gc_decref` when dropped.
/// This calls [`nix_c_raw::value_incref`], and the returned Value will call [`nix_c_raw::value_decref`] when dropped.
///
/// # Safety
///