Compare commits

...

2 Commits

Author SHA1 Message Date
Naxdy af04dd9f58 Add API for adding new builtins 2026-05-31 20:01:46 +02:00
Naxdy 42f8eef0a8 Refactor EvalState use within PrimOp 2026-05-31 19:55:23 +02:00
2 changed files with 53 additions and 67 deletions
+26 -37
View File
@@ -207,6 +207,12 @@ pub struct RealisedString {
#[clippy::has_significant_drop]
struct EvalStateRef {
eval_state: NonNull<raw::EvalState>,
/// Whether we should call [`raw::state_free`] once this `EvalStateRef` is [`Drop`]ped.
///
/// The reason this exists is because we might construct non-owning [`EvalState`]s by way of
/// using [`EvalState::from_raw`], in which case we do not want to call [`raw::state_free`] once
/// the constructed [`EvalState`] goes out of scope.
should_free: bool,
}
unsafe impl Send for EvalStateRef {}
@@ -224,8 +230,10 @@ impl EvalStateRef {
impl Drop for EvalStateRef {
fn drop(&mut self) {
unsafe {
raw::state_free(self.eval_state.as_ptr());
if self.should_free {
unsafe {
raw::state_free(self.eval_state.as_ptr());
}
}
}
}
@@ -349,8 +357,8 @@ impl EvalStateBuilder {
eval_state: NonNull::new(eval_state).unwrap_or_else(|| {
panic!("nix_state_create returned a null pointer without an error")
}),
should_free: true,
},
store: self.store.clone(),
})
}
/// Returns a raw pointer to the underlying eval state builder.
@@ -375,7 +383,6 @@ impl EvalStateBuilder {
#[clippy::has_significant_drop]
pub struct EvalState {
eval_state: EvalStateRef,
store: Store,
}
unsafe impl Send for EvalState {}
@@ -391,6 +398,16 @@ impl EvalState {
.build()
}
pub(crate) unsafe fn from_raw(ptr: *mut raw::EvalState) -> Self {
Self {
eval_state: EvalStateRef {
eval_state: NonNull::new(ptr)
.expect("tried to construct a new `EvalState` from null pointer"),
should_free: false,
},
}
}
/// Returns a raw pointer to the raw Nix C API EvalState.
///
/// # Safety
@@ -400,11 +417,6 @@ impl 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
}
/// Parses and evaluates a Nix expression `expr`.
///
/// Expressions can contain relative paths such as `./.` that are resolved relative to the given `path`.
@@ -885,7 +897,6 @@ impl EvalState {
err: e,
})?;
let primop = primop::PrimOp::new(
self,
primop::PrimOpMeta {
// name is observable in stack traces, ie if the thunk returns Err
name: name.as_c_str(),
@@ -897,7 +908,7 @@ impl EvalState {
Box::new(move |eval_state, _dummy: &[Value; 1]| f(eval_state)),
)?;
let p = primop.new_value()?;
let p = primop.new_value(self)?;
self.new_value_apply(&p, &p)
}
@@ -1106,24 +1117,6 @@ impl EvalState {
}
}
/// 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 this function
/// does not affect `builtins`.
///
/// # Deprecated
///
/// This function is deprecated and has been replaced by
/// [`PrimOp::new_value`](crate::primop::PrimOp::new_value).
#[doc(alias = "make_primop")]
#[doc(alias = "create_function")]
#[doc(alias = "builtin")]
#[deprecated = "use `PrimOp::new_value` instead"]
pub fn new_value_primop(primop: primop::PrimOp) -> Result<Value> {
primop.new_value()
}
/// Creates a new [attribute set][`ValueType::AttrSet`] Nix value from an iterator of name-value pairs.
///
/// Accepts any iterator that yields `(String, Value)` pairs and has an exact size.
@@ -2294,7 +2287,6 @@ mod tests {
let bias_control = bias.clone();
let primop = primop::PrimOp::new(
&mut es,
primop::PrimOpMeta {
name: cstr!("testFunction"),
args: [cstr!("a"), cstr!("b")],
@@ -2309,7 +2301,7 @@ mod tests {
)
.unwrap();
let f = primop.new_value().unwrap();
let f = primop.new_value(&mut es).unwrap();
{
*bias_control.lock().unwrap() = 10;
@@ -2335,7 +2327,6 @@ mod tests {
let f = {
let es: &mut EvalState = &mut es;
let prim = primop::PrimOp::new(
es,
primop::PrimOpMeta {
name: cstr!("throwingTestFunction"),
args: [cstr!("arg")],
@@ -2350,7 +2341,7 @@ mod tests {
)
.unwrap();
prim.new_value()
prim.new_value(es)
}
.unwrap();
let a = es.new_value_int(2).unwrap();
@@ -2434,7 +2425,6 @@ mod tests {
let store = Store::open(None, []).unwrap();
let mut es = EvalState::new(store, []).unwrap();
let primop = primop::PrimOp::new(
&mut es,
primop::PrimOpMeta {
name: cstr!("frobnicate"),
doc: cstr!("Frobnicates widgets"),
@@ -2447,7 +2437,7 @@ mod tests {
}),
)
.unwrap();
let f = primop.new_value().unwrap();
let f = primop.new_value(&mut es).unwrap();
let a = es.new_value_int(2).unwrap();
let b = es.new_value_int(3).unwrap();
let fa = es.call(f, a).unwrap();
@@ -2467,7 +2457,6 @@ mod tests {
let store = Store::open(None, []).unwrap();
let mut es = EvalState::new(store, []).unwrap();
let primop = primop::PrimOp::new(
&mut es,
primop::PrimOpMeta {
name: cstr!("frobnicate"),
doc: cstr!("Frobnicates widgets"),
@@ -2480,7 +2469,7 @@ mod tests {
}),
)
.unwrap();
let f = primop.new_value().unwrap();
let f = primop.new_value(&mut es).unwrap();
let a = es.new_value_int(0).unwrap();
match es.call(f, a) {
Ok(_) => panic!("expected an error"),
+27 -30
View File
@@ -28,12 +28,11 @@ pub struct PrimOpMeta<'a, const N: usize> {
pub args: [&'a CStr; N],
}
pub struct PrimOp<'a> {
pub struct PrimOp {
ptr: *mut raw::PrimOp,
eval_state: &'a mut EvalState,
}
impl Drop for PrimOp<'_> {
impl Drop for PrimOp {
fn drop(&mut self) {
unsafe {
raw::gc_decref(null_mut(), self.ptr as *mut c_void);
@@ -41,17 +40,16 @@ impl Drop for PrimOp<'_> {
}
}
impl<'a> PrimOp<'a> {
impl PrimOp {
/// Create a new primop with the given metadata and implementation.
///
/// When `f` returns an `Err`, the error is propagated to the Nix evaluator.
/// To return a [recoverable error](RecoverableError), include it in the
/// error chain (e.g. `Err(RecoverableError::new("...").into())`).
pub fn new<const N: usize>(
eval_state: &'a mut EvalState,
meta: PrimOpMeta<N>,
f: Box<dyn Fn(&mut EvalState, &[Value; N]) -> Result<Value, Box<dyn Error>>>,
) -> Result<PrimOp<'a>, EvalStateError> {
) -> Result<PrimOp, EvalStateError> {
assert!(N != 0);
let mut args = Vec::new();
@@ -68,7 +66,6 @@ impl<'a> PrimOp<'a> {
let user_data = Box::leak(Box::new(PrimOpContext {
arity: N,
function: Box::new(move |eval_state, args| f(eval_state, args.try_into().unwrap())),
eval_state,
}));
user_data as *const PrimOpContext as *mut c_void
};
@@ -85,10 +82,7 @@ impl<'a> PrimOp<'a> {
))?
};
Ok(PrimOp {
ptr: op,
eval_state,
})
Ok(PrimOp { ptr: op })
}
/// Creates a new [`function`](crate::value::ValueType::Function) Nix value implemented by a Rust function.
@@ -98,37 +92,40 @@ impl<'a> PrimOp<'a> {
/// does not affect `builtins`.
#[doc(alias = "make_primop")]
#[doc(alias = "create_function")]
#[doc(alias = "builtin")]
pub fn new_value(mut self) -> Result<Value, EvalStateError> {
self.with_state_and_ptr(|ptr, this| {
let value = this.new_value_uninitialized()?;
let mut ctx = Context::new();
unsafe {
check_call!(raw::init_primop(&mut ctx, value.raw_ptr(), ptr))?;
};
Ok(value)
})
pub fn new_value(self, eval_state: &mut EvalState) -> Result<Value, EvalStateError> {
let value = eval_state.new_value_uninitialized()?;
let mut ctx = Context::new();
unsafe {
check_call!(raw::init_primop(&mut ctx, value.raw_ptr(), self.ptr))?;
};
Ok(value)
}
pub(crate) fn with_state_and_ptr<F, T>(&mut self, f: F) -> T
where
F: Fn(*mut raw::PrimOp, &mut EvalState) -> T,
{
f(self.ptr, self.eval_state)
/// Creates a new [`function`](crate::value::ValueType::Function) Nix value implemented by a
/// Rust function, and adds it to the global `builtins` attribute set.
///
/// Note that this `PrimOp` will only be accessible by [`EvalState`]s that are created _after_
/// this is called.
#[doc(alias = "builtin")]
pub fn new_builtin(self) -> Result<(), EvalStateError> {
let mut ctx = Context::new();
unsafe {
check_call!(raw::register_primop(&mut ctx, self.ptr))?;
}
Ok(())
}
}
/// The user_data for our Nix primops
struct PrimOpContext<'a> {
struct PrimOpContext {
arity: usize,
function: Box<dyn Fn(&mut EvalState, &[Value]) -> Result<Value, Box<dyn Error>>>,
eval_state: &'a mut EvalState,
}
unsafe extern "C" fn function_adapter(
user_data: *mut ::std::os::raw::c_void,
context_out: *mut raw_util::c_context,
_state: *mut raw::EvalState,
state: *mut raw::EvalState,
args: *mut *mut raw::Value,
ret: *mut raw::Value,
) {
@@ -140,7 +137,7 @@ unsafe extern "C" fn function_adapter(
.collect();
let args_slice = args_vec.as_slice();
let r = primop_info.function.as_ref()(primop_info.eval_state, args_slice);
let r = primop_info.function.as_ref()(&mut unsafe { EvalState::from_raw(state) }, args_slice);
match r {
Ok(v) => unsafe {