diff --git a/rustler/src/lib.rs b/rustler/src/lib.rs index 159e7dc7..fc3e7047 100644 --- a/rustler/src/lib.rs +++ b/rustler/src/lib.rs @@ -35,10 +35,14 @@ mod alloc; pub mod types; mod term; +mod wrapped_types; +pub use crate::wrapped_types::{ + ListIterator, Map +}; pub use crate::term::Term; pub use crate::types::{ - Atom, Binary, Decoder, Encoder, ErlOption, ListIterator, LocalPid, MapIterator, NewBinary, + Atom, Binary, Decoder, Encoder, ErlOption, LocalPid, MapIterator, NewBinary, OwnedBinary, Reference, }; diff --git a/rustler/src/types/elixir_struct.rs b/rustler/src/types/elixir_struct.rs index d0e60dc3..7bde54a0 100644 --- a/rustler/src/types/elixir_struct.rs +++ b/rustler/src/types/elixir_struct.rs @@ -8,19 +8,22 @@ //! `#[module = "Elixir.TheStructModule"]`. use super::atom::{self, Atom}; -use super::map::map_new; -use crate::{Env, NifResult, Term}; +use super::map::Map; +use crate::{Env, Error, NifResult, Term}; pub fn get_ex_struct_name(map: Term) -> NifResult { // In an Elixir struct the value in the __struct__ field is always an atom. - map.map_get(atom::__struct__()).and_then(Atom::from_term) + let map: Map<'_> = map.try_into()?; + map.get(atom::__struct__()) + .ok_or(Error::BadArg) + .and_then(Atom::from_term) } -pub fn make_ex_struct<'a>(env: Env<'a>, struct_module: &str) -> NifResult> { - let map = map_new(env); +pub fn make_ex_struct<'a>(env: Env<'a>, struct_module: &str) -> NifResult> { + let map = env.new_map(); let struct_atom = atom::__struct__(); let module_atom = Atom::from_str(env, struct_module)?; - map.map_put(struct_atom, module_atom) + map.put(struct_atom, module_atom) } diff --git a/rustler/src/types/list.rs b/rustler/src/types/list.rs deleted file mode 100644 index 22048d4a..00000000 --- a/rustler/src/types/list.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Utilities used for working with erlang linked lists. -//! -//! Right now the only supported way to read lists are through the ListIterator. - -use crate::wrapper::{list, NIF_TERM}; -use crate::{Decoder, Encoder, Env, Error, NifResult, Term}; - -/// Enables iteration over the items in the list. -/// -/// Although this behaves like a standard Rust iterator -/// ([book](https://doc.rust-lang.org/book/iterators.html) / -/// [docs](https://doc.rust-lang.org/std/iter/trait.Iterator.html)), there are a couple of tricky -/// parts to using it. -/// -/// Because the iterator is an iterator over `Term`s, you need to decode the terms before you -/// can do anything with them. -/// -/// ## Example -/// An easy way to decode all terms in a list, is to use the `.map()` function of the iterator, and -/// decode every entry in the list. This will produce an iterator of `Result`s, and will therefore -/// not be directly usable in the way you might immediately expect. -/// -/// For this case, the the `.collect()` function of rust iterators is useful, as it can lift -/// the `Result`s out of the list. (Contains extra type annotations for clarity) -/// -/// ``` -/// # use rustler::{Term, NifResult}; -/// # use rustler::types::list::ListIterator; -/// # fn list_iterator_example(list_term: Term) -> NifResult> { -/// let list_iterator: ListIterator = list_term.decode()?; -/// -/// let result: NifResult> = list_iterator -/// // Produces an iterator of NifResult -/// .map(|x| x.decode::()) -/// // Lifts each value out of the result. Returns Ok(Vec) if successful, the first error -/// // Error(Error) on failure. -/// .collect::>>(); -/// # result -/// # } -/// ``` -pub struct ListIterator<'a> { - term: Term<'a>, -} - -impl<'a> ListIterator<'a> { - fn new(term: Term<'a>) -> Option { - if term.is_list() { - let iter = ListIterator { term }; - Some(iter) - } else { - None - } - } -} - -impl<'a> Iterator for ListIterator<'a> { - type Item = Term<'a>; - - fn next(&mut self) -> Option> { - let env = self.term.get_env(); - let cell = unsafe { list::get_list_cell(env.as_c_arg(), self.term.as_c_arg()) }; - - match cell { - Some((head, tail)) => unsafe { - self.term = Term::new(self.term.get_env(), tail); - Some(Term::new(self.term.get_env(), head)) - }, - None => { - if self.term.is_empty_list() { - // We reached the end of the list, finish the iterator. - None - } else { - panic!("list iterator found improper list") - } - } - } - } -} - -impl<'a> Decoder<'a> for ListIterator<'a> { - fn decode(term: Term<'a>) -> NifResult { - match ListIterator::new(term) { - Some(iter) => Ok(iter), - None => Err(Error::BadArg), - } - } -} - -//impl<'a, T> Encoder for Iterator where T: Encoder { -// fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { -// let term_arr: Vec = -// self.map(|x| x.encode(env).as_c_arg()).collect(); -// } -//} -impl Encoder for Vec -where - T: Encoder, -{ - fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { - self.as_slice().encode(env) - } -} - -impl<'a, T> Decoder<'a> for Vec -where - T: Decoder<'a>, -{ - fn decode(term: Term<'a>) -> NifResult { - let iter: ListIterator = term.decode()?; - let res: NifResult = iter.map(|x| x.decode::()).collect(); - res - } -} - -impl Encoder for [T] -where - T: Encoder, -{ - fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { - let term_array: Vec = self.iter().map(|x| x.encode(env).as_c_arg()).collect(); - unsafe { Term::new(env, list::make_list(env.as_c_arg(), &term_array)) } - } -} - -impl Encoder for &[T] -where - T: Encoder, -{ - fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { - let term_array: Vec = self.iter().map(|x| x.encode(env).as_c_arg()).collect(); - unsafe { Term::new(env, list::make_list(env.as_c_arg(), &term_array)) } - } -} - -/// ## List terms -impl<'a> Term<'a> { - /// Returns a new empty list. - pub fn list_new_empty(env: Env<'a>) -> Term<'a> { - let list: &[u8] = &[]; - list.encode(env) - } - - /// Returns an iterator over a list term. - /// See documentation for ListIterator for more information. - /// - /// Returns None if the term is not a list. - pub fn into_list_iterator(self) -> NifResult> { - ListIterator::new(self).ok_or(Error::BadArg) - } - - /// Returns the length of a list term. - /// - /// Returns None if the term is not a list. - /// - /// ### Elixir equivalent - /// ```elixir - /// length(self_term) - /// ``` - pub fn list_length(self) -> NifResult { - unsafe { list::get_list_length(self.get_env().as_c_arg(), self.as_c_arg()) } - .ok_or(Error::BadArg) - } - - /// Unpacks a single cell at the head of a list term, - /// and returns the result as a tuple of (head, tail). - /// - /// Returns None if the term is not a list. - /// - /// ### Elixir equivalent - /// ```elixir - /// [head, tail] = self_term - /// {head, tail} - /// ``` - pub fn list_get_cell(self) -> NifResult<(Term<'a>, Term<'a>)> { - let env = self.get_env(); - unsafe { - list::get_list_cell(env.as_c_arg(), self.as_c_arg()) - .map(|(t1, t2)| (Term::new(env, t1), Term::new(env, t2))) - .ok_or(Error::BadArg) - } - } - - /// Makes a copy of the self list term and reverses it. - /// - /// Returns Err(Error::BadArg) if the term is not a list. - pub fn list_reverse(self) -> NifResult> { - let env = self.get_env(); - unsafe { - list::make_reverse_list(env.as_c_arg(), self.as_c_arg()) - .map(|t| Term::new(env, t)) - .ok_or(Error::BadArg) - } - } - - /// Adds `head` in a list cell with `self` as tail. - pub fn list_prepend(self, head: impl Encoder) -> Term<'a> { - let env = self.get_env(); - let head = head.encode(env); - unsafe { - let term = list::make_list_cell(env.as_c_arg(), head.as_c_arg(), self.as_c_arg()); - Term::new(env, term) - } - } -} diff --git a/rustler/src/types/mod.rs b/rustler/src/types/mod.rs index 01947b79..829cded8 100644 --- a/rustler/src/types/mod.rs +++ b/rustler/src/types/mod.rs @@ -2,7 +2,6 @@ use crate::{Env, Error, NifResult, Term}; #[macro_use] mod wrapper; -pub(crate) use self::wrapper::wrapper; #[macro_use] pub mod atom; diff --git a/rustler/src/types/reference.rs b/rustler/src/types/reference.rs index ce48260e..4d4873e0 100644 --- a/rustler/src/types/reference.rs +++ b/rustler/src/types/reference.rs @@ -1,66 +1,15 @@ -use std::ops::Deref; - -use crate::{Decoder, Encoder, Env, Error, NifResult, Term, TermType}; +use crate::{Env, Term, TermType}; use crate::sys::enif_make_ref; -wrapper!(Reference, TermType::Ref); - -/// Wrapper for BEAM reference terms. -// #[derive(PartialEq, Eq, Clone, Copy)] -// pub struct Reference<'a>(Term<'a>); -// -// impl<'a> Reference<'a> { -// /// Returns a representation of self in the given Env. -// /// -// /// If the term is already is in the provided env, it will be directly returned. Otherwise -// /// the term will be copied over. -// pub fn in_env<'b>(&self, env: Env<'b>) -> Reference<'b> { -// Reference(self.0.in_env(env)) -// } -// } - -impl<'a> Deref for Reference<'a> { - type Target = Term<'a>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> From> for Term<'a> { - fn from(term: Reference<'a>) -> Self { - term.0 - } -} - -impl<'a> TryFrom> for Reference<'a> { - type Error = Error; - - fn try_from(term: Term<'a>) -> Result { - if term.is_ref() { - Ok(Reference(term)) - } else { - Err(Error::BadArg) - } - } -} - -impl<'a> Decoder<'a> for Reference<'a> { - fn decode(term: Term<'a>) -> NifResult { - term.try_into() - } -} - -impl Encoder for Reference<'_> { - fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { - self.0.encode(env) - } +wrapper!{ + struct Reference(TermType::Ref) } impl<'a> Env<'a> { /// Create a new reference in this environment pub fn make_ref(self) -> Reference<'a> { - unsafe { Reference(Term::new(self, enif_make_ref(self.as_c_arg()))) } + let term = unsafe { Term::new(self, enif_make_ref(self.as_c_arg())) }; + unsafe { Reference::wrap_unchecked(term) } } } diff --git a/rustler/src/types/wrapper.rs b/rustler/src/types/wrapper.rs deleted file mode 100644 index ff843563..00000000 --- a/rustler/src/types/wrapper.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::TermType; - -struct WrapperError; - -pub trait Wrapper<'a>: Sized { - const WRAPPED_TYPE: TermType; - - fn wrap<'b>(term: Term<'a>) -> Result { - if term.get_type() == Self::WRAPPED_TYPE { - unsafe { Ok(Self::wrap_unsafe(term)) } - } else { - Err(WrapperError) - } - } - - fn unwrap(&self) -> Term<'a>; - - unsafe fn wrap_unsafe(term: Term<'a>) -> Self; - - /// Returns a representation of self in the given Env. - /// - /// If the term is already is in the provided env, it will be directly returned. Otherwise - /// the term will be copied over. - fn in_env<'b>(&self, env: Env<'b>) -> impl Wrapper<'b, WRAPPED_TYPE = Self::WrappedType> { - self.unwrap().in_env(env) - } -} - -macro_rules! wrapper { - ($name:ident, $term_type:path) => { - #[derive(PartialEq, Eq, Clone, Copy)] - pub struct $name<'a>(Term<'a>); - - impl<'a> $crate::types::wrapper::Wrapper<'a> for $name<'a> { - const WrappedType: TermType = $term_type; - - unsafe fn wrap_unsafe(term: Term<'a>) -> Self { - $name(term) - } - - fn unwrap(&self) -> Term<'a> { - self.0 - } - } - }; -} - -pub(crate) use wrapper; - -use crate::{Env, Term}; diff --git a/rustler/src/wrapped_types/list.rs b/rustler/src/wrapped_types/list.rs new file mode 100644 index 00000000..d92e7611 --- /dev/null +++ b/rustler/src/wrapped_types/list.rs @@ -0,0 +1,247 @@ +use super::wrapper; + +use std::mem::MaybeUninit; + +use crate::sys::{ + enif_get_list_cell, enif_get_list_length, enif_make_list_cell, enif_make_reverse_list, + get_enif_make_list, +}; +use crate::wrapper::{list, NIF_TERM}; +use crate::{Decoder, Encoder, Env, Error, NifResult, Term, TermType}; + +wrapper!( + /// Enables iteration over the items in the list. + /// + /// Although this behaves like a standard Rust iterator + /// ([book](https://doc.rust-lang.org/book/iterators.html) / + /// [docs](https://doc.rust-lang.org/std/iter/trait.Iterator.html)), there are a couple of tricky + /// parts to using it. + /// + /// Because the iterator is an iterator over `Term`s, you need to decode the terms before you + /// can do anything with them. + /// + /// ## Example + /// An easy way to decode all terms in a list, is to use the `.map()` function of the iterator, and + /// decode every entry in the list. This will produce an iterator of `Result`s, and will therefore + /// not be directly usable in the way you might immediately expect. + /// + /// For this case, the the `.collect()` function of rust iterators is useful, as it can lift + /// the `Result`s out of the list. (Contains extra type annotations for clarity) + /// + /// ``` + /// # use rustler::{Term, NifResult}; + /// # use rustler::types::list::ListIterator; + /// # fn list_iterator_example(list_term: Term) -> NifResult> { + /// let list_iterator: ListIterator = list_term.decode()?; + /// + /// let result: NifResult> = list_iterator + /// // Produces an iterator of NifResult + /// .map(|x| x.decode::()) + /// // Lifts each value out of the result. Returns Ok(Vec) if successful, the first error + /// // Error(Error) on failure. + /// .collect::>>(); + /// # result + /// # } + /// ``` + struct ListIterator(TermType::List) +); + +impl<'a> Iterator for ListIterator<'a> { + type Item = Term<'a>; + + fn next(&mut self) -> Option> { + match self.get_cell() { + Some((head, tail)) => { + // TODO: This is unsafe as tail might not be a list. + self.0 = tail; + Some(head) + }, + None => { + if self.is_empty_list() { + // We reached the end of the list, finish the iterator. + None + } else { + panic!("list iterator found improper list") + } + } + } + } +} + +//impl<'a, T> Encoder for Iterator where T: Encoder { +// fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { +// let term_arr: Vec = +// self.map(|x| x.encode(env).as_c_arg()).collect(); +// } +//} +impl Encoder for Vec +where + T: Encoder, +{ + fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { + self.as_slice().encode(env) + } +} + +impl<'a, T> Decoder<'a> for Vec +where + T: Decoder<'a>, +{ + fn decode(term: Term<'a>) -> NifResult { + let iter: ListIterator = term.decode()?; + let res: NifResult = iter.map(|x| x.decode::()).collect(); + res + } +} + +impl Encoder for [T] +where + T: Encoder, +{ + fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { + let term_array: Vec = self.iter().map(|x| x.encode(env).as_c_arg()).collect(); + unsafe { Term::new(env, list::make_list(env.as_c_arg(), &term_array)) } + } +} + +impl<'a, T> Encoder for &'a [T] +where + T: Encoder, +{ + fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { + let term_array: Vec = self.iter().map(|x| x.encode(env).as_c_arg()).collect(); + unsafe { Term::new(env, list::make_list(env.as_c_arg(), &term_array)) } + } +} + +impl<'a> ListIterator<'a> { + pub fn new_empty(env: Env<'a>) -> Self { + let term = unsafe { get_enif_make_list()(env.as_c_arg(), 0) }; + unsafe { Self::wrap_unchecked(Term::new(env, term)) } + } + + pub fn len(&self) -> Option { + let mut len: u32 = 0; + let success = + unsafe { enif_get_list_length(self.get_env().as_c_arg(), self.as_c_arg(), &mut len) }; + + if success != 1 { + return None; + } + + Some(len as usize) + } + + pub fn empty(&self) -> bool { + self.is_empty_list() + } + + pub fn get_cell(&self) -> Option<(Term<'a>, Term<'a>)> { + let env = self.get_env(); + let mut head = MaybeUninit::uninit(); + let mut tail = MaybeUninit::uninit(); + let success = unsafe { + enif_get_list_cell( + env.as_c_arg(), + self.as_c_arg(), + head.as_mut_ptr(), + tail.as_mut_ptr(), + ) + }; + + if success != 1 { + return None; + } + + unsafe { + Some(( + Term::new(env, head.assume_init()), + Term::new(env, tail.assume_init()), + )) + } + } + + pub fn prepend(&self, head: impl Encoder) -> Self { + Term::list_prepend(self.unwrap(), head) + } + + pub fn reverse(&self) -> Option { + let env = self.get_env(); + let mut list_out = MaybeUninit::uninit(); + let success = unsafe { + enif_make_reverse_list(env.as_c_arg(), self.as_c_arg(), list_out.as_mut_ptr()) + }; + + if success != 1 { + return None; + } + + let term = unsafe { Self::wrap_unchecked(Term::new(env, list_out.assume_init())) }; + + Some(term) + } +} + +/// ## List terms +impl<'a> Term<'a> { + /// Returns a new empty list. + pub fn list_new_empty(env: Env<'a>) -> ListIterator<'a> { + ListIterator::new_empty(env) + } + + /// Returns an iterator over a list term. + /// See documentation for ListIterator for more information. + /// + /// Returns None if the term is not a list. + pub fn into_list_iterator(self) -> NifResult> { + Ok(ListIterator::wrap(self)?) + } + + /// Returns the length of a list term. + /// + /// Returns None if the term is not a list. + /// + /// ### Elixir equivalent + /// ```elixir + /// length(self_term) + /// ``` + pub fn list_length(self) -> NifResult { + let iter: ListIterator = self.try_into()?; + iter.len().ok_or(Error::BadArg) + } + + /// Unpacks a single cell at the head of a list term, + /// and returns the result as a tuple of (head, tail). + /// + /// Returns None if the term is not a list. + /// + /// ### Elixir equivalent + /// ```elixir + /// [head, tail] = self_term + /// {head, tail} + /// ``` + pub fn list_get_cell(self) -> NifResult, Term<'a>)>> { + let iter: ListIterator = self.try_into()?; + + Ok(iter.get_cell()) + } + + /// Makes a copy of the self list term and reverses it. + /// + /// Returns Err(Error::BadArg) if the term is not a list. + pub fn list_reverse(self) -> NifResult> { + let iter: ListIterator = self.try_into()?; + iter.reverse().ok_or(Error::BadArg) + } + + /// Adds `head` in a list cell with `self` as tail. + pub fn list_prepend(self, head: impl Encoder) -> ListIterator<'a> { + let env = self.get_env(); + let head = head.encode(env); + + unsafe { + let term = enif_make_list_cell(env.as_c_arg(), head.as_c_arg(), self.as_c_arg()); + ListIterator::wrap_unchecked(Term::new(env, term)) + } + } +} diff --git a/rustler/src/types/map.rs b/rustler/src/wrapped_types/map.rs similarity index 86% rename from rustler/src/types/map.rs rename to rustler/src/wrapped_types/map.rs index 0e6e59de..486aa8ea 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/wrapped_types/map.rs @@ -1,12 +1,66 @@ //! Utilities used to access and create Erlang maps. use super::atom; +use crate::sys::{enif_get_map_value, enif_make_map_put, enif_make_new_map}; use crate::wrapper::map; -use crate::{Decoder, Encoder, Env, Error, NifResult, Term}; +use crate::{Decoder, Encoder, Env, Error, NifResult, Term, TermType}; +use std::mem::MaybeUninit; use std::ops::RangeInclusive; -pub fn map_new(env: Env) -> Term { - unsafe { Term::new(env, map::map_new(env.as_c_arg())) } +wrapper!( + /// A wrapper around an Erlang map term. + struct Map(TermType::Map) +); + +impl<'a> Env<'a> { + pub fn new_map(self) -> Map<'a> { + unsafe { Map::wrap_unchecked(Term::new(self, enif_make_new_map(self.as_c_arg()))) } + } +} + +impl<'a> Map<'a> { + pub fn get(&self, key: impl Encoder) -> Option> { + let env = self.get_env(); + let key = key.encode(env); + + let mut result = MaybeUninit::uninit(); + let success = unsafe { + enif_get_map_value( + env.as_c_arg(), + self.as_c_arg(), + key.as_c_arg(), + result.as_mut_ptr(), + ) + }; + + if success != 1 { + return None; + } + + unsafe { Some(Term::new(env, result.assume_init())) } + } + + pub fn put(&self, key: impl Encoder, value: impl Encoder) -> NifResult { + let env = self.get_env(); + let key = key.encode(env); + let value = value.encode(env); + + let mut result = MaybeUninit::uninit(); + let success = unsafe { + enif_make_map_put( + env.as_c_arg(), + self.as_c_arg(), + key.as_c_arg(), + value.as_c_arg(), + result.as_mut_ptr(), + ) + }; + + if success != 1 { + return Err(Error::BadArg); + } + unsafe { Ok(Map::wrap_ptr_unchecked(env, result.assume_init())) } + } } /// ## Map terms @@ -18,7 +72,7 @@ impl<'a> Term<'a> { /// %{} /// ``` pub fn map_new(env: Env<'a>) -> Term<'a> { - map_new(env) + env.new_map().unwrap() } /// Construct a new map from two vectors diff --git a/rustler/src/wrapped_types/mod.rs b/rustler/src/wrapped_types/mod.rs new file mode 100644 index 00000000..c7561f73 --- /dev/null +++ b/rustler/src/wrapped_types/mod.rs @@ -0,0 +1,7 @@ +mod list; +mod map; +mod tuple; +mod wrapper; + +pub(crate) use wrapper::wrapper; +pub(crate) use list::ListIterator; diff --git a/rustler/src/types/tuple.rs b/rustler/src/wrapped_types/tuple.rs similarity index 64% rename from rustler/src/types/tuple.rs rename to rustler/src/wrapped_types/tuple.rs index 5048e710..6e1861f2 100644 --- a/rustler/src/types/tuple.rs +++ b/rustler/src/wrapped_types/tuple.rs @@ -1,21 +1,55 @@ -use crate::wrapper::{tuple, NIF_TERM}; -use crate::{Decoder, Encoder, Env, Error, NifResult, Term}; +use crate::{ + sys::{enif_get_tuple, ERL_NIF_TERM}, + Decoder, Encoder, Env, Error, NifResult, Term, TermType, +}; -/// Convert an Erlang tuple to a Rust vector. (To convert to a Rust tuple, use `term.decode()` -/// instead.) -/// -/// # Errors -/// `badarg` if `term` is not a tuple. -pub fn get_tuple(term: Term) -> Result, Error> { +use std::{ffi::c_int, mem::MaybeUninit, ops::Index}; +wrapper!( + struct Tuple(TermType::Tuple) +); + +pub unsafe fn get_tuple<'a>(term: Term<'a>) -> NifResult<&'a [ERL_NIF_TERM]> { let env = term.get_env(); - unsafe { - match tuple::get_tuple(env.as_c_arg(), term.as_c_arg()) { - Ok(terms) => Ok(terms - .iter() - .map(|x| Term::new(env, *x)) - .collect::>()), - Err(_error) => Err(Error::BadArg), - } + let mut arity: c_int = 0; + let mut array_ptr = MaybeUninit::uninit(); + let success = enif_get_tuple( + env.as_c_arg(), + term.as_c_arg(), + &mut arity, + array_ptr.as_mut_ptr(), + ); + if success != 1 { + return Err(Error::BadArg); + } + let term_array = ::std::slice::from_raw_parts(array_ptr.assume_init(), arity as usize); + Ok(term_array) +} + +impl<'a> Tuple<'a> { + pub fn size(&self) -> usize { + self.get_elements().len() + } + + pub fn get(&self, index: usize) -> Option> { + self.get_elements() + .get(index) + .map(|ptr| unsafe { Term::new(self.get_env(), ptr) }) + } + + /// Convert an Erlang tuple to a Rust vector. (To convert to a Rust tuple, use `term.decode()` + /// instead.) + /// + /// # Errors + /// `badarg` if `term` is not a tuple. + pub fn to_vec(&self) -> Vec> { + self.get_elements() + .iter() + .map(|ptr| unsafe { Term::new(self.get_env(), *ptr) }) + .collect() + } + + fn get_elements(&self) -> &'a [ERL_NIF_TERM] { + unsafe { get_tuple(self.unwrap()).unwrap() } } } diff --git a/rustler/src/wrapped_types/wrapper.rs b/rustler/src/wrapped_types/wrapper.rs new file mode 100644 index 00000000..42726ba0 --- /dev/null +++ b/rustler/src/wrapped_types/wrapper.rs @@ -0,0 +1,106 @@ +use crate::sys::ERL_NIF_TERM; +use crate::{Env, Term, TermType}; + +pub struct WrapperError; + +impl From for crate::Error { + fn from(_: WrapperError) -> Self { + crate::Error::BadArg + } +} + +pub(crate) trait Wrapper<'a>: Sized { + const WRAPPED_TYPE: TermType; + + unsafe fn wrap_ptr_unchecked(env: Env<'a>, ptr: ERL_NIF_TERM) -> Self { + Self::wrap_unchecked(Term::new(env, ptr)) + } + + fn wrap(term: Term<'a>) -> Result { + if term.get_type() == Self::WRAPPED_TYPE { + unsafe { Ok(Self::wrap_unchecked(term)) } + } else { + Err(WrapperError) + } + } + + fn unwrap(&self) -> Term<'a>; + unsafe fn wrap_unchecked(term: Term<'a>) -> Self; +} + +impl<'a, T> From for Term<'a> +where + T: Wrapper<'a>, +{ + fn from(term: T) -> Self { + term.unwrap() + } +} + +macro_rules! wrapper { + ( + $(#[$meta:meta])* + struct $name:ident($term_type:path) + ) => { + $(#[$meta])* + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct $name<'a>(Term<'a>); + + use $crate::types::wrapper::Wrapper; + + impl<'a> $name<'a> { + /// Returns a representation of self in the given Env. + /// + /// If the term is already is in the provided env, it will be directly returned. + /// Otherwise the term will be copied over. + pub fn in_env<'b>(&self, env: Env<'b>) -> $name<'b> { + let term = self.unwrap().in_env(env); + unsafe { $name::wrap_unchecked(term) } + } + } + + impl<'a> Wrapper<'a> for $name<'a> { + const WRAPPED_TYPE: $crate::TermType = $term_type; + + unsafe fn wrap_unchecked(term: Term<'a>) -> Self { + $name(term) + } + + fn unwrap(&self) -> Term<'a> { + self.0 + } + } + + impl<'a> std::ops::Deref for $name<'a> { + type Target = Term<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<'a> TryFrom> for $name<'a> { + type Error = $crate::Error; + + fn try_from(term: Term<'a>) -> Result { + use $crate::types::wrapper::Wrapper; + Self::wrap(term).or(Err($crate::Error::BadArg)) + } + } + + impl<'a> $crate::Decoder<'a> for $name<'a> { + fn decode(term: Term<'a>) -> $crate::NifResult { + use $crate::types::wrapper::Wrapper; + Self::wrap(term).or(Err($crate::Error::BadArg)) + } + } + + impl<'a> $crate::Encoder for $name<'a> { + fn encode<'b>(&self, env: $crate::Env<'b>) -> Term<'b> { + self.0.encode(env) + } + } + }; +} + +pub(crate) use wrapper; diff --git a/rustler/src/wrapper/list.rs b/rustler/src/wrapper/list.rs index 33078586..312eb0eb 100644 --- a/rustler/src/wrapper/list.rs +++ b/rustler/src/wrapper/list.rs @@ -1,47 +1,7 @@ use crate::{ - sys::{ - enif_get_list_cell, enif_get_list_length, enif_make_list_cell, enif_make_list_from_array, - enif_make_reverse_list, - }, + sys::enif_make_list_from_array, wrapper::{NIF_ENV, NIF_TERM}, }; -use std::mem::MaybeUninit; - -pub unsafe fn get_list_cell(env: NIF_ENV, list: NIF_TERM) -> Option<(NIF_TERM, NIF_TERM)> { - let mut head = MaybeUninit::uninit(); - let mut tail = MaybeUninit::uninit(); - let success = enif_get_list_cell(env, list, head.as_mut_ptr(), tail.as_mut_ptr()); - - if success != 1 { - return None; - } - Some((head.assume_init(), tail.assume_init())) -} - -pub unsafe fn get_list_length(env: NIF_ENV, list: NIF_TERM) -> Option { - let mut len: u32 = 0; - let success = enif_get_list_length(env, list, &mut len); - - if success != 1 { - return None; - } - Some(len as usize) -} - pub unsafe fn make_list(env: NIF_ENV, arr: &[NIF_TERM]) -> NIF_TERM { enif_make_list_from_array(env, arr.as_ptr(), arr.len() as u32) } - -pub unsafe fn make_list_cell(env: NIF_ENV, head: NIF_TERM, tail: NIF_TERM) -> NIF_TERM { - enif_make_list_cell(env, head, tail) -} - -pub unsafe fn make_reverse_list(env: NIF_ENV, list: NIF_TERM) -> Option { - let mut list_out = MaybeUninit::uninit(); - let success = enif_make_reverse_list(env, list, list_out.as_mut_ptr()); - - if success != 1 { - return None; - } - Some(list_out.assume_init()) -} diff --git a/rustler/src/wrapper/tuple.rs b/rustler/src/wrapper/tuple.rs index 6aa0cc50..621ce19d 100644 --- a/rustler/src/wrapper/tuple.rs +++ b/rustler/src/wrapper/tuple.rs @@ -1,18 +1,7 @@ -use crate::sys::{enif_get_tuple, enif_make_tuple_from_array}; +use crate::sys::enif_make_tuple_from_array; use crate::wrapper::{c_int, NIF_ENV, NIF_ERROR, NIF_TERM}; use std::mem::MaybeUninit; -pub unsafe fn get_tuple<'a>(env: NIF_ENV, term: NIF_TERM) -> Result<&'a [NIF_TERM], NIF_ERROR> { - let mut arity: c_int = 0; - let mut array_ptr = MaybeUninit::uninit(); - let success = enif_get_tuple(env, term, &mut arity, array_ptr.as_mut_ptr()); - if success != 1 { - return Err(NIF_ERROR::BAD_ARG); - } - let term_array = ::std::slice::from_raw_parts(array_ptr.assume_init(), arity as usize); - Ok(term_array) -} - pub unsafe fn make_tuple(env: NIF_ENV, terms: &[NIF_TERM]) -> NIF_TERM { enif_make_tuple_from_array(env, terms.as_ptr(), terms.len() as u32) } diff --git a/rustler_tests/native/rustler_test/src/test_env.rs b/rustler_tests/native/rustler_test/src/test_env.rs index 99f9da4f..956d295d 100644 --- a/rustler_tests/native/rustler_test/src/test_env.rs +++ b/rustler_tests/native/rustler_test/src/test_env.rs @@ -62,12 +62,12 @@ pub fn sublists<'a>(env: Env<'a>, list: Term<'a>) -> NifResult { let reversed_list = saved_reversed_list.load(env); let iter: ListIterator = reversed_list.decode()?; - let empty_list = Vec::::new().encode(env); + let empty_list = ListIterator::new_empty(env); let mut all_sublists = vec![empty_list]; for element in iter { for i in 0..all_sublists.len() { - let new_list = all_sublists[i].list_prepend(element); + let new_list = all_sublists[i].prepend(element); all_sublists.push(new_list); } }