Skip to main content

otter/types/
ops.rs

1//! Environment operation verbs not owned by a single term type.
2//!
3//! Two kinds of verb live here. The free functions [`serialize`]/[`deserialize`]
4//! (term ↔ external term format) and [`port_command`] act on *any* term, so they
5//! are functions taking an env rather than methods on one type. The rest are
6//! inherent methods on the env *kind* that has the context for them: the
7//! [`CallEnv`] verbs ([`cpu_time`](CallEnv::cpu_time),
8//! [`schedule_nif`](CallEnv::schedule_nif)) need a live NIF call, and the
9//! [`InitEnv`] `set_option_*` verbs are only valid from `load`/`upgrade`.
10
11use std::ffi::{c_int, c_void, CStr};
12
13use crate::types::{
14    AnyTerm, BinaryBuf, CallEnv, CallingEnv, Env, InitEnv, LocalPort, RawTerm, Raised,
15    Term, Tuple,
16};
17
18/// Serialize a term to the external term format (`enif_term_to_binary`),
19/// returning the bytes in a [`BinaryBuf`]. Type-agnostic. `None` on failure.
20pub fn serialize<'id>(env: impl Env<'id>, term: impl Term<'id>) -> Option<BinaryBuf> {
21    let mut bin: enif_ffi::Binary = unsafe { std::mem::zeroed() };
22    if unsafe { enif_ffi::term_to_binary(env.raw_env(), term.raw_term(), &mut bin) } != 0 {
23        Some(BinaryBuf::from_filled(bin))
24    } else {
25        None
26    }
27}
28
29/// Deserialize a term from external-term-format bytes (`enif_binary_to_term`).
30/// If `safe`, encoded atoms not already in the atom table are rejected. `None`
31/// on decode failure.
32pub fn deserialize<'id>(env: impl Env<'id>, data: &[u8], safe: bool) -> Option<AnyTerm<'id>> {
33    let opts = if safe { enif_ffi::BIN2TERM_SAFE } else { 0 };
34    let mut term: RawTerm = 0;
35    let consumed =
36        unsafe { enif_ffi::binary_to_term(env.raw_env(), data.as_ptr(), data.len(), &mut term, opts) };
37    (consumed != 0).then(|| AnyTerm::wrap(term, env))
38}
39
40/// Send a command to local `port` (`enif_port_command`, NULL msg_env — copied
41/// from the caller env). `true` if accepted.
42pub fn port_command<'id>(env: impl CallingEnv<'id>, port: &LocalPort, msg: impl Term<'id>) -> bool {
43    unsafe {
44        enif_ffi::port_command(env.raw_env(), &port.port, std::ptr::null_mut(), msg.raw_term()) != 0
45    }
46}
47
48impl<'id> CallEnv<'id> {
49    /// The current logical CPU's execution time in `erlang:timestamp/0` format
50    /// (`enif_cpu_time`). `Err(Raised)` (`badarg`) if the OS does not support it.
51    pub fn cpu_time(self) -> Result<Tuple<'id>, Raised<'id>> {
52        let raw = unsafe { enif_ffi::cpu_time(self.raw_env()) };
53        let term = self.check_raised(AnyTerm::wrap(raw, self))?;
54        Ok(Tuple::from_raw(term.raw_term()))
55    }
56
57    /// Reschedule the current NIF to run `fp` (`enif_schedule_nif`). The success
58    /// value must be returned directly from the NIF; a bad `fun_name` raises
59    /// `badarg`, surfaced as `Err(Raised)`.
60    ///
61    /// # Safety
62    /// `fp` must be a valid NIF function pointer; `argv` must point to `argc`
63    /// valid terms.
64    pub unsafe fn schedule_nif(
65        self,
66        fun_name: &CStr,
67        flags: i32,
68        fp: unsafe extern "C" fn(*mut enif_ffi::Env, c_int, *const enif_ffi::Term) -> enif_ffi::Term,
69        argc: i32,
70        argv: *const enif_ffi::Term,
71    ) -> Result<AnyTerm<'id>, Raised<'id>> {
72        let raw =
73            unsafe { enif_ffi::schedule_nif(self.raw_env(), fun_name.as_ptr(), flags, fp, argc, argv) };
74        self.check_raised(AnyTerm::wrap(raw, self))
75    }
76}
77
78impl<'id> InitEnv<'id> {
79    /// Enable delayed halt: the VM waits for running NIF calls before halting
80    /// (`enif_set_option(ERL_NIF_OPT_DELAY_HALT)`). `true` on success.
81    pub fn set_option_delay_halt(self) -> bool {
82        unsafe { enif_ffi::set_option_delay_halt(self.raw_env()) == 0 }
83    }
84
85    /// Set the on-halt callback (`enif_set_option(ERL_NIF_OPT_ON_HALT)`).
86    ///
87    /// # Safety
88    /// `callback` must remain valid for the lifetime of the VM.
89    pub unsafe fn set_option_on_halt(self, callback: unsafe extern "C" fn(*mut c_void)) -> bool {
90        unsafe { enif_ffi::set_option_on_halt(self.raw_env(), callback) == 0 }
91    }
92
93    /// Set the on-unload-thread callback
94    /// (`enif_set_option(ERL_NIF_OPT_ON_UNLOAD_THREAD)`).
95    ///
96    /// # Safety
97    /// `callback` must remain valid for the lifetime of the VM.
98    pub unsafe fn set_option_on_unload_thread(
99        self,
100        callback: unsafe extern "C" fn(*mut c_void),
101    ) -> bool {
102        unsafe { enif_ffi::set_option_on_unload_thread(self.raw_env(), callback) == 0 }
103    }
104}