rustler/
term.rs

1use crate::sys::*;
2use crate::types::binary::OwnedBinary;
3use crate::wrapper::env::term_to_binary;
4use crate::wrapper::NIF_TERM;
5use crate::{Binary, Decoder, Env, NifResult};
6use std::cmp::Ordering;
7use std::fmt::{self, Debug};
8use std::hash::{Hash, Hasher};
9
10/// Term is used to represent all erlang terms. Terms are always lifetime limited by a Env.
11///
12/// Term is cloneable and copyable, but it can not exist outside of the lifetime of the Env
13/// that owns it.
14#[derive(Clone, Copy)]
15pub struct Term<'a> {
16    term: NIF_TERM,
17    env: Env<'a>,
18}
19
20impl Debug for Term<'_> {
21    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
22        crate::wrapper::term::fmt(self.as_c_arg(), f)
23    }
24}
25
26impl<'a> Term<'a> {
27    /// Create a `Term` from a raw `NIF_TERM`.
28    ///
29    /// # Unsafe
30    /// The caller must ensure that `env` is the environment that `inner` belongs to,
31    /// unless `inner` is an atom term.
32    #[inline]
33    pub unsafe fn new(env: Env<'a>, inner: NIF_TERM) -> Self {
34        Term { term: inner, env }
35    }
36    /// This extracts the raw term pointer. It is usually used in order to obtain a type that can
37    /// be passed to calls into the erlang vm.
38    #[inline]
39    pub fn as_c_arg(&self) -> NIF_TERM {
40        self.term
41    }
42
43    #[inline]
44    pub fn get_env(self) -> Env<'a> {
45        self.env
46    }
47
48    /// Returns a representation of self in the given Env.
49    ///
50    /// If the term is already is in the provided env, it will be directly returned. Otherwise
51    /// the term will be copied over.
52    #[inline]
53    pub fn in_env<'b>(&self, env: Env<'b>) -> Term<'b> {
54        if self.get_env() == env {
55            // It's safe to create a new Term<'b> without copying because we
56            // just proved that the same environment is associated with both 'a
57            // and 'b.  (They are either exactly the same lifetime, or the
58            // lifetimes of two .run() calls on the same OwnedEnv.)
59            unsafe { Term::new(env, self.as_c_arg()) }
60        } else {
61            unsafe { Term::new(env, enif_make_copy(env.as_c_arg(), self.as_c_arg())) }
62        }
63    }
64
65    /// Decodes the Term into type T.
66    ///
67    /// This should be used as the primary method of extracting the value from a Term.
68    ///
69    /// # Examples
70    ///
71    /// ```ignore
72    /// let term: Term = ...;
73    /// let number: i32 = term.decode()?;
74    /// ```
75    pub fn decode<T>(self) -> NifResult<T>
76    where
77        T: Decoder<'a>,
78    {
79        Decoder::decode(self)
80    }
81
82    /// Decodes the Term into Binary
83    ///
84    /// This could be used as a replacement for [`decode`] when decoding Binary from an iolist
85    /// is needed.
86    ///
87    /// [`decode`]: #method.decode
88    #[inline]
89    pub fn decode_as_binary(self) -> NifResult<Binary<'a>> {
90        if self.is_binary() {
91            return Binary::from_term(self);
92        }
93        Binary::from_iolist(self)
94    }
95
96    #[inline]
97    pub fn to_binary(self) -> OwnedBinary {
98        let raw_binary = unsafe { term_to_binary(self.env.as_c_arg(), self.as_c_arg()) }.unwrap();
99        unsafe { OwnedBinary::from_raw(raw_binary) }
100    }
101
102    /// Non-portable hash function that only guarantees the same hash for the same term within
103    /// one Erlang VM instance.
104    ///
105    /// It takes 32-bit salt values and generates hashes within 0..2^32-1.
106    pub fn hash_internal(&self, salt: u32) -> u32 {
107        unsafe {
108            enif_hash(
109                ErlNifHash::ERL_NIF_INTERNAL_HASH,
110                self.as_c_arg(),
111                salt as u64,
112            ) as u32
113        }
114    }
115
116    /// Portable hash function that gives the same hash for the same Erlang term regardless of
117    /// machine architecture and ERTS version.
118    ///
119    /// It generates hashes within 0..2^27-1.
120    pub fn hash_phash2(&self) -> u32 {
121        unsafe { enif_hash(ErlNifHash::ERL_NIF_PHASH2, self.as_c_arg(), 0) as u32 }
122    }
123
124    #[cfg(feature = "nif_version_2_15")]
125    pub fn get_erl_type(&self) -> ErlNifTermType {
126        unsafe { enif_term_type(self.env.as_c_arg(), self.as_c_arg()) }
127    }
128}
129
130impl PartialEq for Term<'_> {
131    fn eq(&self, other: &Term) -> bool {
132        unsafe { enif_is_identical(self.as_c_arg(), other.as_c_arg()) == 1 }
133    }
134}
135impl Eq for Term<'_> {}
136
137fn cmp(lhs: &Term, rhs: &Term) -> Ordering {
138    let ord = unsafe { enif_compare(lhs.as_c_arg(), rhs.as_c_arg()) };
139    match ord {
140        0 => Ordering::Equal,
141        n if n < 0 => Ordering::Less,
142        _ => Ordering::Greater,
143    }
144}
145
146impl Ord for Term<'_> {
147    fn cmp(&self, other: &Term) -> Ordering {
148        cmp(self, other)
149    }
150}
151impl<'a> PartialOrd for Term<'a> {
152    fn partial_cmp(&self, other: &Term<'a>) -> Option<Ordering> {
153        Some(self.cmp(other))
154    }
155}
156
157impl Hash for Term<'_> {
158    fn hash<H: Hasher>(&self, state: &mut H) {
159        // As far as I can see, there is really no way
160        // to get a seed from the hasher. This is definitely
161        // not optimal, but it's the best we can do for now.
162        state.write_u32(self.hash_internal(0));
163    }
164}
165
166unsafe impl Sync for Term<'_> {}
167unsafe impl Send for Term<'_> {}