diff --git a/rust/nix-c-raw/src/lib.rs b/rust/nix-c-raw/src/lib.rs index a38a13a..05c7e41 100644 --- a/rust/nix-c-raw/src/lib.rs +++ b/rust/nix-c-raw/src/lib.rs @@ -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 +#![allow(rustdoc::invalid_codeblock_attributes)] // C code examples may use unsupported attributes include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/nix-expr/src/eval_state.rs b/rust/nix-expr/src/eval_state.rs index c9249ad..5c9154a 100644 --- a/rust/nix-expr/src/eval_state.rs +++ b/rust/nix-expr/src/eval_state.rs @@ -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]", "")?; +//! +//! // 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, } -/// A [Weak] reference to an [EvalState] +/// A [Weak] reference to an [EvalState]. pub struct EvalStateWeak { inner: Weak, store: StoreWeak, @@ -65,9 +201,11 @@ struct EvalStateRef { eval_state: NonNull, } 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("", /* path display: */ "in-memory")?; +/// # drop(guard); +/// # Ok(()) +/// # } +/// ``` pub struct EvalStateBuilder { eval_state_builder: *mut raw::eval_state_builder, lookup_path: Vec, @@ -92,6 +255,7 @@ impl Drop for EvalStateBuilder { } } impl EvalStateBuilder { + /// Creates a new [`EvalStateBuilder`]. pub fn new(store: Store) -> Result { 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) -> Result { let lookup_path: Vec = 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 { // 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) -> Result { 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 { 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 { 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 { 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", "")?; + /// 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 { 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 { 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 = es.require_list_strict(&list_value)?; /// let deque: VecDeque = es.require_list_strict(&list_value)?; + /// let linked_list = es.require_list_strict::>(&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(&mut self, value: &Value) -> Result where C: FromIterator, @@ -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> { 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> { 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 { 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 { 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> { 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 { 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 { 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 { 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 { 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", "")?; + /// let arg1 = es.eval_from_string("5", "")?; + /// let arg2 = es.eval_from_string("3", "")?; + /// + /// // 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 { 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 { 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 { 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(&mut self, attrs: I) -> Result where I: IntoIterator, @@ -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 { 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(); diff --git a/rust/nix-expr/src/value.rs b/rust/nix-expr/src/value.rs index bfa7011..14cda88 100644 --- a/rust/nix-expr/src/value.rs +++ b/rust/nix-expr/src/value.rs @@ -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 { 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, } 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 ///