diff --git a/rust/nix-c-raw/src/lib.rs b/rust/nix-c-raw/src/lib.rs index a38a13a8..05c7e41e 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 89d696b5..4c39d79c 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 _; @@ -11,6 +141,7 @@ use nix_util::context::Context; use nix_util::string_return::{callback_get_result_string, callback_get_result_string_data}; use nix_util::{check_call, check_call_opt_key, result_string_init}; use std::ffi::{c_char, CString}; +use std::iter::FromIterator; use std::os::raw::c_uint; use std::ptr::{null, null_mut, NonNull}; use std::sync::{Arc, Weak}; @@ -24,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, @@ -64,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() } @@ -78,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, @@ -91,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 = @@ -101,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() @@ -113,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()?; @@ -147,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 } @@ -158,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)? @@ -165,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), @@ -188,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")?; @@ -217,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( @@ -228,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), @@ -247,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 { @@ -255,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 { @@ -263,10 +518,64 @@ impl EvalState { unsafe { check_call!(raw::get_bool(&mut self.context, v.raw_ptr())) } } - /// Evaluate, and require that the value is an attrset. + /// 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, 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, + { + let t = self.value_type(value)?; + if t != ValueType::List { + bail!("expected a list, but got a {:?}", t); + } + let size = unsafe { check_call!(raw::get_list_size(&mut self.context, value.raw_ptr())) }?; + + (0..size) + .map(|i| { + let element_ptr = unsafe { + check_call!(raw::get_list_byidx( + &mut self.context, + value.raw_ptr(), + self.eval_state.as_ptr(), + i + )) + }?; + Ok(unsafe { Value::new(element_ptr) }) + }) + .collect() + } + + /// 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(); @@ -274,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 { @@ -284,7 +596,7 @@ impl EvalState { let n = unsafe { check_call!(raw::get_attrs_size(&mut self.context, v.raw_ptr())) }?; let mut attrs = Vec::with_capacity(n as usize); for i in 0..n { - let cstr_ptr: *const i8 = unsafe { + let cstr_ptr: *const c_char = unsafe { check_call!(raw::get_attr_name_byidx( &mut self.context, v.raw_ptr(), @@ -301,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 { @@ -333,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. + /// + /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set. /// - /// Return `Err(...)` if `v` is not an attrset, or if some other error occurred. + /// Returns [`Err`] if evaluation failed or the value is not an attribute set. /// - /// Return `Ok(None)` if the attribute is not present. + /// Returns [`Ok(None)`] if the attribute is not present. /// - /// Return `Ok(Some(value))` if the attribute is 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, @@ -362,8 +688,72 @@ 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. + /// Returns the number of elements in a [list][`ValueType::List`] Nix value. + /// + /// 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 { + bail!("expected a list, but got a {:?}", t); + } + let ret = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?; + Ok(ret) + } + + /// Extracts an element from a [list][`ValueType::List`] Nix value by index. + /// + /// 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`]. + /// + /// Returns [`Ok(Some(value))`] if the element is found. + /// + /// 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 { + bail!("expected a list, but got a {:?}", t); + } + + // TODO: Remove this bounds checking once https://github.com/NixOS/nix/pull/14030 + // is merged, which will add proper bounds checking to the underlying C API. + // Currently we perform bounds checking in Rust to avoid undefined behavior. + let size = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?; + + if idx >= size { + return Ok(None); + } + + let v2 = unsafe { + check_call_opt_key!(raw::get_list_byidx( + &mut self.context, + v.raw_ptr(), + self.eval_state.as_ptr(), + idx + )) + }?; + Ok(v2.map(|x| unsafe { Value::new(x) })) + } + + /// 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 { @@ -378,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()?; @@ -387,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, @@ -430,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 { @@ -438,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, @@ -488,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 { @@ -505,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 { @@ -523,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 { @@ -549,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 { @@ -565,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, @@ -597,6 +1107,7 @@ impl EvalState { } } +// Internal RAII helper; could be refactored and made pub struct BindingsBuilder { ptr: *mut raw::BindingsBuilder, } @@ -620,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, } @@ -653,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 { @@ -681,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(); @@ -1269,6 +1792,91 @@ mod tests { .unwrap(); } + #[test] + fn eval_state_value_list_strict_empty() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[]", "").unwrap(); + es.force(&v).unwrap(); + let list: Vec = es.require_list_strict(&v).unwrap(); + assert_eq!(list.len(), 0); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_list_strict_int() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[42]", "").unwrap(); + es.force(&v).unwrap(); + let list: Vec = es.require_list_strict(&v).unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(es.require_int(&list[0]).unwrap(), 42); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_list_strict_int_bool() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[42 true]", "").unwrap(); + es.force(&v).unwrap(); + let list: Vec = es.require_list_strict(&v).unwrap(); + assert_eq!(list.len(), 2); + assert_eq!(es.require_int(&list[0]).unwrap(), 42); + assert_eq!(es.require_bool(&list[1]).unwrap(), true); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_list_strict_error() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string(r#"[(throw "_evaluated_item_")]"#, "").unwrap(); + es.force(&v).unwrap(); + // This should fail because require_list_strict evaluates all elements + let result: Result, _> = es.require_list_strict(&v); + assert!(result.is_err()); + match result { + Err(error_msg) => { + let error_str = error_msg.to_string(); + assert!(error_str.contains("_evaluated_item_")); + } + Ok(_) => panic!("unexpected success. The item should have been evaluated and its error propagated.") + } + }) + .unwrap(); + } + + #[test] + fn eval_state_value_list_strict_generic_container() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[1 2 3]", "").unwrap(); + + // Test with Vec + let vec: Vec = es.require_list_strict(&v).unwrap(); + assert_eq!(vec.len(), 3); + + // Test with VecDeque + let deque: std::collections::VecDeque = es.require_list_strict(&v).unwrap(); + assert_eq!(deque.len(), 3); + + // Verify contents are the same + assert_eq!(es.require_int(&vec[0]).unwrap(), 1); + assert_eq!(es.require_int(&deque[0]).unwrap(), 1); + }) + .unwrap(); + } + #[test] fn eval_state_realise_string() { gc_registering_current_thread(|| { @@ -1853,4 +2461,184 @@ mod tests { }) .unwrap(); } + + #[test] + fn eval_state_require_list_select_idx_strict_basic() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[ 10 20 30 ]", "").unwrap(); + + let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap(); + let elem1 = es.require_list_select_idx_strict(&v, 1).unwrap().unwrap(); + let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap(); + + assert_eq!(es.require_int(&elem0).unwrap(), 10); + assert_eq!(es.require_int(&elem1).unwrap(), 20); + assert_eq!(es.require_int(&elem2).unwrap(), 30); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_select_idx_strict_out_of_bounds() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[ 1 2 3 ]", "").unwrap(); + + let out_of_bounds = es.require_list_select_idx_strict(&v, 3).unwrap(); + assert!(out_of_bounds.is_none()); + + // Test boundary case - the last valid index + let last_elem = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap(); + assert_eq!(es.require_int(&last_elem).unwrap(), 3); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_select_idx_strict_empty_list() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("[ ]", "").unwrap(); + + // Test that the safe version properly handles empty list access + let elem = es.require_list_select_idx_strict(&v, 0).unwrap(); + assert!(elem.is_none()); + + // Verify we can get the size of an empty list + let size = es.require_list_size(&v).unwrap(); + assert_eq!(size, 0); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_select_idx_strict_forces_thunk() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = make_thunk(&mut es, "[ 42 ]"); + assert!(es.value_type_unforced(&v).is_none()); + + let elem = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap(); + assert_eq!(es.require_int(&elem).unwrap(), 42); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_select_idx_strict_error_element() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + + let v = es + .eval_from_string("[ (1 + 1) (throw \"error\") (3 + 3) ]", "") + .unwrap(); + + let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap(); + assert_eq!(es.require_int(&elem0).unwrap(), 2); + + let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap(); + assert_eq!(es.require_int(&elem2).unwrap(), 6); + + let elem1_result = es.require_list_select_idx_strict(&v, 1); + match elem1_result { + Ok(_) => panic!("expected an error from throw during selection"), + Err(e) => { + assert!(e.to_string().contains("error")); + } + } + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_select_idx_strict_wrong_type() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("42", "").unwrap(); + + let r = es.require_list_select_idx_strict(&v, 0); + match r { + Ok(_) => panic!("expected an error"), + Err(e) => { + let err_msg = e.to_string(); + assert!(err_msg.contains("expected a list, but got a")); + } + } + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_size_basic() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + + let empty = es.eval_from_string("[ ]", "").unwrap(); + assert_eq!(es.require_list_size(&empty).unwrap(), 0); + + let three_elem = es.eval_from_string("[ 1 2 3 ]", "").unwrap(); + assert_eq!(es.require_list_size(&three_elem).unwrap(), 3); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_size_forces_thunk() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = make_thunk(&mut es, "[ 1 2 3 4 5 ]"); + assert!(es.value_type_unforced(&v).is_none()); + + let size = es.require_list_size(&v).unwrap(); + assert_eq!(size, 5); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_size_lazy_elements() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + + let v = es + .eval_from_string( + "[ (throw \"error1\") (throw \"error2\") (throw \"error3\") ]", + "", + ) + .unwrap(); + + let size = es.require_list_size(&v).unwrap(); + assert_eq!(size, 3); + }) + .unwrap(); + } + + #[test] + fn eval_state_require_list_size_wrong_type() { + gc_registering_current_thread(|| { + let store = Store::open(None, HashMap::new()).unwrap(); + let mut es = EvalState::new(store, []).unwrap(); + let v = es.eval_from_string("\"not a list\"", "").unwrap(); + + let r = es.require_list_size(&v); + match r { + Ok(_) => panic!("expected an error"), + Err(e) => { + let err_msg = e.to_string(); + assert!(err_msg.contains("expected a list, but got a")); + } + } + }) + .unwrap(); + } } diff --git a/rust/nix-expr/src/value.rs b/rust/nix-expr/src/value.rs index bfa70116..14cda88c 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 /// diff --git a/rust/nix-flake/src/lib.rs b/rust/nix-flake/src/lib.rs index e4c45e52..acd5fa4e 100644 --- a/rust/nix-flake/src/lib.rs +++ b/rust/nix-flake/src/lib.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, ptr::NonNull}; +use std::{ffi::CString, os::raw::c_char, ptr::NonNull}; use anyhow::{Context as _, Result}; use nix_c_raw as raw; @@ -87,7 +87,7 @@ impl FlakeReferenceParseFlags { context::check_call!(raw::flake_reference_parse_flags_set_base_directory( &mut ctx, self.ptr.as_ptr(), - base_directory.as_ptr() as *const i8, + base_directory.as_ptr() as *const c_char, base_directory.len() )) }?; @@ -125,7 +125,7 @@ impl FlakeReference { fetch_settings.raw_ptr(), flake_settings.ptr, flags.ptr.as_ptr(), - reference.as_ptr() as *const i8, + reference.as_ptr() as *const c_char, reference.len(), // pointer to ptr &mut ptr, diff --git a/rust/nix-store/src/store.rs b/rust/nix-store/src/store.rs index 9ac67eda..0a6b2ae5 100644 --- a/rust/nix-store/src/store.rs +++ b/rust/nix-store/src/store.rs @@ -217,6 +217,21 @@ impl Store { } } + #[doc(alias = "nix_store_real_path")] + pub fn real_path(&mut self, path: &StorePath) -> Result { + let mut r = result_string_init!(); + unsafe { + check_call!(raw::store_real_path( + &mut self.context, + self.inner.ptr(), + path.as_ptr(), + Some(callback_get_result_string), + callback_get_result_string_data(&mut r) + )) + }?; + r + } + pub fn weak_ref(&self) -> StoreWeak { StoreWeak { inner: Arc::downgrade(&self.inner), @@ -286,7 +301,9 @@ mod tests { let store_path_string = format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26"); let store_path = store.parse_store_path(store_path_string.as_str()).unwrap(); + let real_store_path = store.real_path(&store_path).unwrap(); assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26"); + assert_eq!(real_store_path, store_path_string); } #[test] diff --git a/rust/nix-util/src/context.rs b/rust/nix-util/src/context.rs index 8e12a8b0..55d8332c 100644 --- a/rust/nix-util/src/context.rs +++ b/rust/nix-util/src/context.rs @@ -1,5 +1,6 @@ use anyhow::{bail, Result}; use nix_c_raw as raw; +use std::os::raw::c_char; use std::ptr::null_mut; use std::ptr::NonNull; @@ -50,7 +51,7 @@ impl Context { raw::set_err_msg( self.inner.as_ptr(), raw::err_NIX_OK, - b"\0".as_ptr() as *const i8, + b"\0".as_ptr() as *const c_char, ); } }