Overview
This document explains how to properly implement message sending from Rust NIFs to Erlang processes. This is needed for dala_deliver_webview_eval_result and similar functions that are called from ObjC/Java callbacks.
The Challenge
Functions like dala_deliver_webview_eval_result are:
- Called from ObjC (iOS) or Java (Android) callbacks
- Need to send messages to Erlang processes (e.g.,
:dala_screen) - Don't have direct access to
Envfrom the NIF context
Solution: Use Cached Environment
1. Cache the Environment
In lib.rs, cache the Erlang environment during NIF initialization:
use std::sync::Mutex;
use rustler::Env;
lazy_static::lazy_static! {
static ref CACHED_ENV: Mutex<Option<usize>> = Mutex::new(None);
}
#[rustler::nif]
fn cache_env<'a>(env: Env<'a>) -> NifResult<Term<'a>> {
let env_ptr = env.as_c_arg() as usize;
let mut cached = CACHED_ENV.lock().unwrap();
*cached = Some(env_ptr);
ok(env)
}Register cache_env in rustler::init! macro.
2. Reconstruct Env from Cached Pointer
In dala_deliver_webview_eval_result:
#[no_mangle]
pub extern "C" fn dala_deliver_webview_eval_result(json_utf8: *const std::ffi::c_char) {
unsafe {
if json_utf8.is_null() { return; }
let json = {
let cstr = std::ffi::CStr::from_ptr(json_utf8);
match cstr.to_str() {
Ok(s) => s.to_string(),
Err(_) => return,
}
};
let cached = CACHED_ENV.lock().unwrap();
if let Some(env_ptr) = *cached {
// Reconstruct Env from cached pointer
// NOTE: This is unsafe and may not work correctly
// The proper way is to use Erlang's C API
let env = Env::new(
std::mem::transmute::<usize, *mut rustler::wrapper::env::c_erl_nif_env>(env_ptr)
);
// Send message to :dala_screen process
// This requires proper process lookup
eprintln!("[Dala] WebView eval result: {}", json);
}
}
}The Proper Way: Use Erlang C API
The correct way to send messages from C code (which Rust can call) is to use Erlang's C API:
Erlang C API Functions
#include <erl_nif.h>
// Get process ID by name
ErlNifPid* pid = enif_whereis(env, "dala_screen");
// Send a tuple message
ErlNifTerm* message = enif_make_tuple(env, 3,
enif_make_atom(env, "webview"),
enif_make_atom(env, "eval_result"),
enif_make_binary(env, json, strlen(json))
);
enif_send(env, pid, message, 0);Rust FFI to Erlang C API
Create Rust bindings to Erlang C API:
use std::os::raw::c_char;
extern "C" {
fn enif_whereis(env: *mut c_void, name: *const c_char) -> *mut c_void;
fn enif_send(env: *mut c_void, to: *mut c_void, msg: *mut c_void, flags: c_int) -> c_int;
// ... other functions
}
pub fn send_to_dala_screen(json: &str) {
unsafe {
let env = ...; // Get cached env
let pid = enif_whereis(env, "dala_screen\0".as_ptr() as *const c_char);
if !pid.is_null() {
// Create and send message
}
}
}Alternative: Use Rustler's encode/decode
Rustler provides functions to encode/decode Erlang terms:
use rustler::types::{atom, binary, tuple};
fn send_message(env: &Env, pid: rustler::types::pid::Pid, json: &str) {
let webview_atom = atom::Atom::from_str(env, "webview")
.unwrap()
.to_term(env)
.unwrap();
let eval_result_atom = atom::Atom::from_str(env, "eval_result")
.unwrap()
.to_term(env)
.unwrap();
let json_term = binary::Binary::from_bytes(json.as_bytes())
.to_term(env)
.unwrap();
let message = tuple::make_tuple(env, &[webview_atom, eval_result_atom, json_term]);
// Send using env.send or similar
}Recommended Implementation Steps
- For now (stub): Log the result using
eprintln! - Short term: Implement using Erlang C API FFI
- Long term: Create proper Rust bindings for Erlang C API
Current Status in Dala
- ✅
CACHED_ENVis implemented - ✅
cache_envNIF is implemented - ✅
dala_deliver_webview_eval_resultlogs results - ❌ Message sending is not implemented (stub)
Next Steps
- Create FFI bindings to Erlang C API (
enif_whereis,enif_send, etc.) - Implement
send_to_process(pid, term)function - Update
dala_deliver_webview_eval_resultto actually send messages - Test with a running Dala app