mod binarybuf;
mod ops;
pub mod terms;
mod typed;
pub use binarybuf::BinaryBuf;
pub use ops::{deserialize, port_command, serialize};
pub use terms::*;
pub use typed::TypedTerm;
pub use enif_ffi::{Hash, TermType, UniqueInteger};
#[cfg(feature = "bigint")]
#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
pub use num_bigint::BigInt;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicU64, Ordering};
mod sealed {
pub trait Sealed {}
}
pub type Invariant<'id> = PhantomData<*mut &'id ()>;
pub(crate) type RawEnv = *mut enif_ffi::Env;
pub trait Env<'id>: Copy + sealed::Sealed {
fn raw_env(&self) -> RawEnv;
fn as_any_env(self) -> AnyEnv<'id> {
AnyEnv { raw_env: self.raw_env(), _id: PhantomData }
}
fn term_type(self, term: impl Term<'id>) -> Option<enif_ffi::TermType> {
let code = unsafe { enif_ffi::term_type(self.raw_env(), term.raw_term()) };
enif_ffi::TermType::from_raw(code)
}
fn hash(self, algorithm: enif_ffi::Hash, term: impl Term<'id>, salt: u64) -> u64 {
unsafe { enif_ffi::hash(algorithm, term.raw_term(), salt) }
}
fn consume_timeslice(self, percent: i32) -> bool {
unsafe { enif_ffi::consume_timeslice(self.raw_env(), percent) != 0 }
}
fn is_current_process_alive(self) -> bool {
unsafe { enif_ffi::is_current_process_alive(self.raw_env()) != 0 }
}
fn make_unique_integer(self, properties: enif_ffi::UniqueInteger) -> Integer<'id> {
let raw = unsafe { enif_ffi::make_unique_integer(self.raw_env(), properties) };
Integer::from_raw(raw)
}
}
#[derive(Clone, Copy)]
pub struct AnyEnv<'id> {
raw_env: RawEnv,
_id: Invariant<'id>,
}
impl<'id> sealed::Sealed for AnyEnv<'id> {}
impl<'id> Env<'id> for AnyEnv<'id> {
fn raw_env(&self) -> RawEnv {
self.raw_env
}
}
#[derive(Clone, Copy)]
pub struct CallEnv<'id> {
raw_env: RawEnv,
_id: Invariant<'id>,
}
impl<'id> sealed::Sealed for CallEnv<'id> {}
impl<'id> Env<'id> for CallEnv<'id> {
fn raw_env(&self) -> RawEnv {
self.raw_env
}
}
impl CallEnv<'_> {
pub unsafe fn with_raw<R>(raw: RawEnv, f: impl for<'id> FnOnce(CallEnv<'id>) -> R) -> R {
f(CallEnv { raw_env: raw, _id: PhantomData })
}
}
#[derive(Clone, Copy)]
pub struct InitEnv<'id> {
raw_env: RawEnv,
_id: Invariant<'id>,
}
impl<'id> sealed::Sealed for InitEnv<'id> {}
impl<'id> Env<'id> for InitEnv<'id> {
fn raw_env(&self) -> RawEnv {
self.raw_env
}
}
impl InitEnv<'_> {
pub unsafe fn with_raw<R>(raw: RawEnv, f: impl for<'id> FnOnce(InitEnv<'id>) -> R) -> R {
f(InitEnv { raw_env: raw, _id: PhantomData })
}
}
#[derive(Clone, Copy)]
pub struct CallbackEnv<'id> {
raw_env: RawEnv,
_id: Invariant<'id>,
}
impl<'id> sealed::Sealed for CallbackEnv<'id> {}
impl<'id> Env<'id> for CallbackEnv<'id> {
fn raw_env(&self) -> RawEnv {
self.raw_env
}
}
impl CallbackEnv<'_> {
pub unsafe fn with_raw<R>(raw: RawEnv, f: impl for<'id> FnOnce(CallbackEnv<'id>) -> R) -> R {
f(CallbackEnv { raw_env: raw, _id: PhantomData })
}
}
#[derive(Clone, Copy)]
pub struct DeinitEnv<'id> {
raw_env: RawEnv,
_id: Invariant<'id>,
}
impl<'id> sealed::Sealed for DeinitEnv<'id> {}
impl<'id> Env<'id> for DeinitEnv<'id> {
fn raw_env(&self) -> RawEnv {
self.raw_env
}
}
impl DeinitEnv<'_> {
pub unsafe fn with_raw<R>(raw: RawEnv, f: impl for<'id> FnOnce(DeinitEnv<'id>) -> R) -> R {
f(DeinitEnv { raw_env: raw, _id: PhantomData })
}
}
fn send_move_(env: RawEnv, pid: &LocalPid, msg_env: &mut OwnedEnvArena, msg: OwnedEnvTerm) -> bool {
assert!(!msg_env.is_dirty);
let msg = msg_env.unwrap_term(msg);
let ok = unsafe { enif_ffi::send(env, &pid.pid, msg_env.env, msg) != 0 };
msg_env.is_dirty = ok;
ok
}
fn send_copy_<'a>(env: RawEnv, pid: &LocalPid, msg: impl Term<'a>) -> bool {
unsafe { enif_ffi::send(env, &pid.pid, std::ptr::null_mut(), msg.raw_term()) != 0 }
}
pub fn send_move(pid: &LocalPid, msg_env: &mut OwnedEnvArena, msg: OwnedEnvTerm) -> bool {
send_move_(std::ptr::null_mut(), pid, msg_env, msg)
}
pub fn send_copy<'a>(pid: &LocalPid, msg: impl Term<'a>) -> bool {
send_copy_(std::ptr::null_mut(), pid, msg)
}
pub fn send_move_from<'id>(calling_env: impl CallingEnv<'id>, pid: &LocalPid, msg_env: &mut OwnedEnvArena, msg: OwnedEnvTerm) -> bool {
send_move_(calling_env.raw_env(), pid, msg_env, msg)
}
pub fn send_copy_from<'id, 'a>(calling_env: impl CallingEnv<'id>, pid: &LocalPid, msg: impl Term<'a>) -> bool {
send_copy_(calling_env.raw_env(), pid, msg)
}
pub trait CallingEnv<'id>: Env<'id> {}
impl<'id> CallingEnv<'id> for CallEnv<'id> {}
impl<'id> CallingEnv<'id> for CallbackEnv<'id> {}
pub struct Raised<'id> {
_id: Invariant<'id>,
}
impl<'id> CallEnv<'id> {
pub fn raise<T>(self, reason: impl Term<'id>) -> Result<T, Raised<'id>> {
unsafe { enif_ffi::raise_exception(self.raw_env(), reason.raw_term()) };
Err(Raised { _id: PhantomData })
}
pub fn badarg<T>(self) -> Result<T, Raised<'id>> {
unsafe { enif_ffi::make_badarg(self.raw_env()) };
Err(Raised { _id: PhantomData })
}
pub fn check_raised(self, term: AnyTerm<'id>) -> Result<AnyTerm<'id>, Raised<'id>> {
if unsafe { enif_ffi::has_pending_exception(self.raw_env(), std::ptr::null_mut()) } != 0 {
Err(Raised { _id: PhantomData })
} else {
Ok(term)
}
}
}
pub(crate) type RawTerm = enif_ffi::Term;
pub(crate) const THE_NON_VALUE: RawTerm = 0;
pub trait Term<'id>: sealed::Sealed {
fn raw_term(self) -> RawTerm;
fn copy_to<'dst>(self, env: impl Env<'dst>) -> AnyTerm<'dst>
where
Self: Sized,
{
let raw = unsafe { enif_ffi::make_copy(env.raw_env(), self.raw_term()) };
AnyTerm::wrap(raw, env)
}
}
pub trait FreeTerm: for<'id> Term<'id> {}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct AnyTerm<'id> {
raw_term: RawTerm,
_id: Invariant<'id>,
}
impl<'id> AnyTerm<'id> {
#[crate::raw]
pub(crate) fn wrap(raw_term: RawTerm, _env: impl Env<'id>) -> Self {
Self { raw_term, _id: PhantomData }
}
}
impl sealed::Sealed for AnyTerm<'_> {}
impl<'id> Term<'id> for AnyTerm<'id> {
fn raw_term(self) -> RawTerm {
self.raw_term
}
}
static GENERATION: AtomicU64 = AtomicU64::new(1);
fn next_generation() -> u64 {
GENERATION.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, Copy)]
pub struct OwnedEnvTerm {
version: u64,
term: RawTerm,
}
pub struct OwnedEnvArena {
env: RawEnv,
version: u64,
is_dirty: bool,
}
impl Default for OwnedEnvArena {
fn default() -> Self {
Self::new()
}
}
impl OwnedEnvArena {
pub fn new() -> Self {
let env = unsafe { enif_ffi::alloc_env() };
assert!(!env.is_null(), "enif_alloc_env returned null");
OwnedEnvArena { env, version: next_generation(), is_dirty: false }
}
pub fn clear(&mut self) {
unsafe { enif_ffi::clear_env(self.env) };
self.version = next_generation();
self.is_dirty = false;
}
pub fn copy_in<'a>(&mut self, term: impl Term<'a>) -> OwnedEnvTerm {
assert!(!self.is_dirty);
self.wrap_term(unsafe { enif_ffi::make_copy(self.env, term.raw_term()) })
}
pub fn copy_out<'a>(&self, oterm: OwnedEnvTerm, env: impl Env<'a>) -> AnyTerm<'a> {
assert!(!self.is_dirty);
let remote_term = unsafe { enif_ffi::make_copy(env.raw_env(), self.unwrap_term(oterm)) };
AnyTerm { raw_term: remote_term, _id: PhantomData }
}
fn wrap_term(&self, raw_term: RawTerm) -> OwnedEnvTerm {
assert!(!self.is_dirty);
OwnedEnvTerm { version: self.version, term: raw_term }
}
fn unwrap_term(&self, oterm: OwnedEnvTerm) -> RawTerm {
assert!(!self.is_dirty);
assert!(self.version == oterm.version, "OwnedEnvTerm used with a different arena or after clear");
oterm.term
}
pub fn run<R>(&mut self, f: impl for<'id> FnOnce(OwnedEnv<'_, 'id>) -> R) -> R {
assert!(!self.is_dirty);
f(OwnedEnv { owner: self, _id: PhantomData })
}
}
impl Drop for OwnedEnvArena {
fn drop(&mut self) {
unsafe { enif_ffi::free_env(self.env) };
}
}
#[derive(Clone, Copy)]
pub struct OwnedEnv<'a, 'id> {
owner: &'a OwnedEnvArena,
_id: Invariant<'id>,
}
impl<'a, 'id> OwnedEnv<'a, 'id> {
pub fn export(self, term: impl Term<'id>) -> OwnedEnvTerm {
self.owner.wrap_term(term.raw_term())
}
pub fn import(self, oterm: OwnedEnvTerm) -> AnyTerm<'id> {
AnyTerm { raw_term: self.owner.unwrap_term(oterm), _id: PhantomData }
}
}
impl<'a, 'id> sealed::Sealed for OwnedEnv<'a, 'id> {}
impl<'a, 'id> Env<'id> for OwnedEnv<'a, 'id> {
fn raw_env(&self) -> RawEnv {
self.owner.env
}
}
#[cfg(test)]
mod brand_tests {
use super::*;
fn use_in<'id>(_env: impl Env<'id>, t: AnyTerm<'id>) -> RawTerm {
t.raw_term()
}
#[allow(dead_code)]
fn brands_are_distinct() {
unsafe {
CallEnv::with_raw(std::ptr::null_mut(), |e1| {
CallEnv::with_raw(std::ptr::null_mut(), |e2| {
let t1 = AnyTerm::wrap(0, e1);
let _ = use_in(e1, t1); let _ = e2;
});
});
}
}
}
#[cfg(test)]
mod owned_env_tests {
use super::*;
#[allow(dead_code)]
fn positive(b: &mut OwnedEnvArena) -> OwnedEnvTerm {
b.run(|env| {
let t = AnyTerm::wrap(0, env);
env.export(t)
})
}
}