Any tips on using Rustler with #[rustler::nif] marked functions?

I note that sometimes I am able to use functions marked as #[rustler::nif] in my Rust code. But other times it is like this consumes the function away from the rest of Rust and it can’t see it anymore.

This is not the end of the world. I can ensure all #[rustler::nif] functions are not used inside Rust and just exist as public “getters” and “setters” from Elixir.

Though it leads to some redundancy and annoyances for debugging the internal rust functions (as you can’t just add this tag and test them from Elixir console).

For example, with the following:

ERROR

error[E0425]: cannot find function `test_break` in this scope
  --> src/conversions.rs:16:5
   |
16 |     test_break();
   |     ^^^^^^^^^^ not found in this scope

For more information about this error, try `rustc --explain E0425`.
error: could not compile `clust_r` (lib) due to 1 previous error

== Compilation error in file lib/clust.ex ==
** (RuntimeError) Rust NIF compile error (rustc exit code 101)
    (rustler 0.34.0) lib/rustler/compiler.ex:36: Rustler.Compiler.compile_crate/3
    lib/clust.ex:7: (module)

RUST:

#[rustler::nif]  
fn test_break()-> i64 {
    return 5;
}

fn gps_vec() -> Vec<f32> { 
    
    test_break(); //THE ERROR LINE, REMOVING ALLOWS BUILD (OR REMOVE #[rustler::nif]  ABOVE)
    
	let mut gps: Vec<f32> = Vec::new(); 
	gps.push(52.0 as f32); 
	gps.push(43.0 as f32);
    return gps;
}

ELIXIR:

def test_break(), do: :erlang.nif_error(:nif_not_loaded)

Is this generally the case? Strangely sometimes I can build this way, but I notice I get way too many of these errors once I have the function referenced as #[rustler::nif] for Elixir and try to also access it from inside my Rust code.

mix clean, deleting the .so Rustler files and deleting _build don’t help, so it seems like a real issue.

Has anyone noticed this as well? Is it a known fact?

How do you typically debug Rust code in Rustler inside Elixir? Kind of challenging to get anything out of there to see what it is doing.

Thanks for any ideas.

I think this is a real problem to be aware of. I have just implemented my “solution” and it works fine.

  • make wrappers for the Rust functions in elixir and add _nif to their names for clarity and consistency
  • wrap the Rust functions that need to be accessed from Elixir and Rust in this.
  • this also solves issues of reference based arguments in Rust functions.

For example:

ELIXIR

def process_gps_nif(_gps, _res), do: :erlang.nif_error(:nif_not_loaded) 

RUST

fn process_gps(gps_point: &Vec<f32>, res: u32) -> i64 {
    //real rust function
}

//elixir wrapper:
#[rustler::nif]
fn process_gps_nif(gps_point: Vec<f32>, res: u32) -> i64 { process_gps(&gps_point, res) }

There is the minor hassle of creating a wrapper each time but the consistency is worth it. This is solving the errors and it lets you handle things like references in the arguments of the Rust code from Elixir with minimal effort.

The functions that are marked as #[rustler::nif] are translated into a wrapped function that is safe to call from the BEAM. I don’t think you could ever call the unwrapped function. I had a PR lying around that made this possible, but IIRC it crashed on some platform with very strange errors. I’ll have a look at it again.

1 Like