diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a24870..684365f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ versions. - Floats can be decoded from integers (#641, fixes #603) - Resource types can implement and use dynamic calls on NIF version 2.16 (#635) - `Encoder` and `Decoder` implementations for `Box` (#644) +- `Reference` type and `env.make_ref()` function (#657) ### Fixed diff --git a/rustler/src/lib.rs b/rustler/src/lib.rs index d1335526..159e7dc7 100644 --- a/rustler/src/lib.rs +++ b/rustler/src/lib.rs @@ -39,7 +39,7 @@ mod term; pub use crate::term::Term; pub use crate::types::{ Atom, Binary, Decoder, Encoder, ErlOption, ListIterator, LocalPid, MapIterator, NewBinary, - OwnedBinary, + OwnedBinary, Reference, }; #[cfg(feature = "big_integer")] diff --git a/rustler/src/types/mod.rs b/rustler/src/types/mod.rs index 52c141cf..c3f11ced 100644 --- a/rustler/src/types/mod.rs +++ b/rustler/src/types/mod.rs @@ -2,8 +2,6 @@ use crate::{Env, Error, NifResult, Term}; #[macro_use] pub mod atom; -pub mod i128; -pub mod path; pub use crate::types::atom::Atom; pub mod binary; @@ -32,6 +30,13 @@ pub mod tuple; pub mod local_pid; pub use self::local_pid::LocalPid; +#[doc(hidden)] +pub mod reference; +pub use self::reference::Reference; + +pub mod i128; +pub mod path; + pub mod truthy; pub mod elixir_struct; diff --git a/rustler/src/types/reference.rs b/rustler/src/types/reference.rs new file mode 100644 index 00000000..013e3263 --- /dev/null +++ b/rustler/src/types/reference.rs @@ -0,0 +1,64 @@ +use std::ops::Deref; + +use crate::{Decoder, Encoder, Env, Error, NifResult, Term}; + +use crate::sys::enif_make_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<'a> Encoder for Reference<'a> { + fn encode<'b>(&self, env: Env<'b>) -> Term<'b> { + self.0.encode(env) + } +} + +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()))) } + } +} diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index b2c2232f..bfebade6 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -103,6 +103,7 @@ defmodule RustlerTest do def whereis_pid(_), do: err() def is_process_alive(_), do: err() def sublists(_), do: err() + def make_refs(), do: err() def tuple_echo(_), do: err() def record_echo(_), do: err() diff --git a/rustler_tests/native/rustler_test/src/test_env.rs b/rustler_tests/native/rustler_test/src/test_env.rs index 06dd63d6..99f9da4f 100644 --- a/rustler_tests/native/rustler_test/src/test_env.rs +++ b/rustler_tests/native/rustler_test/src/test_env.rs @@ -2,7 +2,7 @@ use rustler::env::{OwnedEnv, SavedTerm, SendError}; use rustler::types::atom; use rustler::types::list::ListIterator; use rustler::types::LocalPid; -use rustler::{Atom, Encoder, Env, NifResult, Term}; +use rustler::{Atom, Encoder, Env, NifResult, Reference, Term}; use std::thread; // Send a message to several PIDs. @@ -84,3 +84,10 @@ pub fn sublists<'a>(env: Env<'a>, list: Term<'a>) -> NifResult { Ok(atom::ok()) } + +#[rustler::nif] +fn make_refs<'a>(env: Env<'a>) -> (bool, Reference<'a>, Reference<'a>) { + let first = env.make_ref(); + let second = env.make_ref(); + (first != second, first, second) +} diff --git a/rustler_tests/test/env_test.exs b/rustler_tests/test/env_test.exs index 29c06acc..6e016bbd 100644 --- a/rustler_tests/test/env_test.exs +++ b/rustler_tests/test/env_test.exs @@ -104,4 +104,13 @@ defmodule RustlerTest.EnvTest do assert :error == RustlerTest.send(task.pid, :msg) assert :error == RustlerTest.send(task.pid, :msg) end + + test "make_ref" do + {different, ref1, ref2} = RustlerTest.make_refs() + + assert different + assert is_reference(ref1) + assert is_reference(ref2) + assert ref1 != ref2 + end end