rustler 0.13.0

Safe Rust wrappers for creating Erlang NIF functions
Documentation
use ::{ NifEnv, NifTerm };
use ::wrapper::nif_interface::{ self, NIF_ENV, NIF_TERM };
use ::types::pid::NifPid;
use std::ptr;
use std::sync::{Arc, Weak};

impl<'a> NifEnv<'a> {
    /// Send a message to a process.
    ///
    /// The Erlang VM imposes some odd restrictions on sending messages.
    /// You can send messages in either of these situations:
    ///
    /// *   The current thread is managed by the Erlang VM, and `self` is the
    ///     environment of the calling process (that is, the environment that
    ///     Rustler passed in to your NIF); *or*
    ///
    /// *   The current thread is *not* managed by the Erlang VM.
    ///
    /// # Panics
    ///
    /// Panics if the above rules are broken (by trying to send a message from
    /// an `OwnedEnv` on a thread that's managed by the Erlang VM).
    ///
    pub fn send(self, pid: &NifPid, message: NifTerm<'a>) {
        let thread_type = nif_interface::enif_thread_type();
        let env =
            if thread_type == nif_interface::ERL_NIF_THR_UNDEFINED {
                ptr::null_mut()
            } else if thread_type == nif_interface::ERL_NIF_THR_NORMAL_SCHEDULER ||
                      thread_type == nif_interface::ERL_NIF_THR_DIRTY_CPU_SCHEDULER ||
                      thread_type == nif_interface::ERL_NIF_THR_DIRTY_IO_SCHEDULER {
                // Panic if `self` is not the environment of the calling process.
                self.pid();

                self.as_c_arg()
            } else {
                panic!("NifEnv::send(): unrecognized calling thread type");
            };

        // Send the message.
        unsafe {
            nif_interface::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg());
        }
    }
}


/// A process-independent environment, a place where Erlang terms can be created outside of a NIF
/// call.
///
/// Rust code can use an owned environment to build a message and send it to an
/// Erlang process.
///
///     use rustler::env::OwnedEnv;
///     use rustler::types::pid::NifPid;
///     use rustler::NifEncoder;
///
///     fn send_string_to_pid(data: &str, pid: &NifPid) {
///         let mut msg_env = OwnedEnv::new();
///         msg_env.send_and_clear(pid, |env| data.encode(env));
///     }
///
/// There's no way to run Erlang code in an `OwnedEnv`. It's not a process. It's just a workspace
/// for building terms.
pub struct OwnedEnv {
    env: Arc<NIF_ENV>
}

unsafe impl Send for OwnedEnv {}

impl OwnedEnv {
    /// Allocates a new process-independent environment.
    pub fn new() -> OwnedEnv {
        OwnedEnv {
            env: Arc::new(unsafe { nif_interface::enif_alloc_env() })
        }
    }

    /// Run some code in this environment.
    pub fn run<F, R>(&self, closure: F) -> R
        where F: for<'a> FnOnce(NifEnv<'a>) -> R
    {
        let env_lifetime = ();
        let env = unsafe { NifEnv::new(&env_lifetime, *self.env) };
        closure(env)
    }

    /// Send a message from a Rust thread to an Erlang process.
    ///
    /// The environment is cleared as though by calling the `.clear()` method.
    /// To avoid that, use `env.send(pid, term)` instead.
    ///
    /// # Panics
    ///
    /// Panics if called from a thread that is managed by the Erlang VM. You
    /// can only use this method on a thread that was created by other
    /// means. (This curious restriction is imposed by the Erlang VM.)
    ///
    pub fn send_and_clear<F>(&mut self, recipient: &NifPid, closure: F)
        where F: for<'a> FnOnce(NifEnv<'a>) -> NifTerm<'a>
    {
        if nif_interface::enif_thread_type() != nif_interface::ERL_NIF_THR_UNDEFINED {
            panic!("send_and_clear: current thread is managed");
        }

        let message = self.run(|env| closure(env).as_c_arg());

        let c_env = *self.env;
        self.env = Arc::new(c_env);  // invalidate SavedTerms
        unsafe {
            nif_interface::enif_send(ptr::null_mut(), recipient.as_c_arg(), c_env, message);
        }
    }

    /// Free all terms in this environment and clear it for reuse.
    ///
    /// This invalidates `SavedTerm`s that were saved in this environment;
    /// if you later try to `.load()` one, you'll get a panic.
    ///
    /// Unless you call this method after a call to `run()`, all terms created within the
    /// environment hang around in memory until the `OwnedEnv` is dropped: garbage collection does
    /// not continually happen as needed in a NIF environment.
    pub fn clear(&mut self) {
        let c_env = *self.env;
        self.env = Arc::new(c_env);
        unsafe { nif_interface::enif_clear_env(c_env); }
    }

    /// Save a term for use in a later call to `.run()` or `.send()`.
    ///
    /// For your safety, Rust doesn't let you save `NifTerm` values from one `.run()` call to a
    /// later `.run()` call. If you try, it'll complain about lifetimes.
    ///
    /// `.save()` offers a way to do this. For example, maybe you'd like to copy a term from the
    /// caller into an `OwnedEnv`, then use that term on another thread.
    ///
    ///     # use rustler::{ NifEnv, NifTerm };
    ///     use rustler::env::OwnedEnv;
    ///     use std::thread;
    ///
    ///     fn thread_example<'a>(env: NifEnv<'a>, term: NifTerm<'a>) {
    ///         // Copy `term` into a new OwnedEnv, for use on another thread.
    ///         let mut thread_env = OwnedEnv::new();
    ///         let saved_term = thread_env.save(term);
    ///
    ///         thread::spawn(move || {
    ///             // Now run some code on the thread, using the saved term.
    ///             thread_env.run(|env| {
    ///                 let term = saved_term.load(env);
    ///                 //... do stuff with term ...
    ///             });
    ///         });
    ///     }
    ///
    /// **Note: There is no way to save terms across `OwnedEnv::send()` or `clear()`.**
    /// If you try, the `.load()` call will panic.
    pub fn save<'a>(&self, term: NifTerm<'a>) -> SavedTerm {
        SavedTerm {
            term: self.run(|env| term.in_env(env).as_c_arg()),
            env_generation: Arc::downgrade(&self.env),
        }
    }
}

impl Drop for OwnedEnv {
    fn drop(&mut self) {
        unsafe { nif_interface::enif_free_env(*self.env); }
    }
}

/// A term that was created in an `OwnedEnv` and saved for later use.
///
/// These are created by calling `OwnedEnv::save()`. See that method's documentation for an
/// example.
#[derive(Clone)]
pub struct SavedTerm {
    env_generation: Weak<NIF_ENV>,
    term: NIF_TERM,
}

unsafe impl Send for SavedTerm {}

impl SavedTerm {
    /// Load this saved term back into its environment.
    ///
    /// # Panics
    ///
    /// `env` must be the `NifEnv` of a `.run()` or `.send()` call on the
    /// `OwnedEnv` where this term was saved, and the `OwnedEnv` must not have
    /// been cleared or dropped since then. Otherwise this method will panic.
    pub fn load<'a>(&self, env: NifEnv<'a>) -> NifTerm<'a> {
        // Check that the saved term is still valid.
        match self.env_generation.upgrade() {
            None =>
                panic!("term is from a cleared or dropped OwnedEnv"),
            Some(ref env_arc) if **env_arc == env.as_c_arg() =>
                unsafe { NifTerm::new(env, self.term) },
            _ =>
                panic!("can't load SavedTerm into a different environment"),
        }
    }
}