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}