ocaml-rs - OCaml extensions in Rust

ocaml-rs allows for OCaml extensions to be written directly in Rust with no C stubs. It was originally forked from raml, but has been almost entirely re-written thanks to support from the OCaml Software Foundation.

Works with OCaml versions 4.06.0 and up

Please report any issues on github

Getting started

OCaml:

Take a look at test/src/dune for an example dune file to get you started.

Rust

Typically just include:

toml ocaml = "0.10"

or

toml ocaml = {git = "https://github.com/zshipko/ocaml-rs"}

in your Cargo.toml.

On macOS you will need also to add the following to your project's .cargo/config file:

toml [build] rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"]

This is because macOS doesn't allow undefined symbols in dynamic libraries by default.

Additionally, if you plan on releasing to OPAM, you will need to vendor your Rust dependencies to avoid making network requests during the build phase, since reaching out to crates.io/github will be blocked by the OPAM sandbox.

Features

Documentation

https://docs.rs/ocaml

Examples

``rust // Automatically deriveToValueandFromValue`

[derive(ocaml::ToValue, ocaml::FromValue)]

struct Example<'a> { name: &'a str, i: ocaml::Int, }

[ocaml::func]

pub fn incr_example(mut e: Example) -> Example { e.i += 1; e }

[ocaml::func]

pub fn build_tuple(i: ocaml::Int) -> (ocaml::Int, ocaml::Int, ocaml::Int) { (i + 1, i + 2, i + 3) }

[ocaml::func]

pub fn average(arr: ocaml::Array) -> Result { let mut sum = 0f64;

for i in 0..arr.len() {
    sum += arr.get_double(i)?;
}

Ok(sum / arr.len() as f64)

}

// A native_func must take ocaml::Value for every argument and return an ocaml::Value // these functions have minimal overhead compared to wrapping with func

[ocaml::native_func]

pub fn incr(value: ocaml::Value) -> ocaml::Value { let i = value.int_val(); Value::int(i + 1) }

// This is equivalent to:

[no_mangle]

pub extern "C" fn incr2(value: ocaml::Value) -> ocaml::Value { ocaml::body!((value) { let i = value.int_val(); ocaml::Value::int( i + 1) }) }

// ocaml::native_func is responsible for: // - Ensures that #[no_mangle] and extern "C" are added, in addition to wrapping // - Wraps the function body using ocaml::body!

// Finally, if your function is marked [@@unboxed] and [@@noalloc] in OCaml then you can avoid // boxing altogether for f64 arguments using a plain C function and a bytecode function // definition:

[no_mangle]

pub extern "C" fn incrf(input: f64) -> f64 { input + 1.0 }

[cfg(feature = "derive")]

[ocaml::bytecode_func]

pub fn incrf_bytecode(input: f64) -> f64 { incrf(input) } ```

The OCaml stubs would look like this:

```ocaml type example = { name: string; i: int; }

external increxample: example -> example = "increxample" external buildtuple: int -> int * int * int = "buildtuple" external average: float array -> float = "average" external incr: int -> int = "incr" external incr2: int -> int = "incr2" external incrf: float -> float = "incrf_bytecode" "incrf" [@@unboxed] [@@noalloc] ```

For more examples see ./test or ocaml-vec for an example project using ocaml-rs.

Type conversion

This chart contains the mapping between Rust and OCaml types used by ocaml::func

| Rust type | OCaml type | | ---------------- | -------------------- | | () | unit | | isize | int | | usize | int | | i8 | int | | u8 | int | | i16 | int | | u16 | int | | i32 | int32 | | u32 | int32 | | i64 | int64 | | u64 | int64 | | f32 | float | | f64 | float | | str | string | | String | string | | Option<A> | 'a option | | Result<A, B> | exception | | (A, B, C) | 'a * 'b * 'c | | &[Value] | 'a array (no copy) | | Vec<A>, &[A] | 'a array | | BTreeMap<A, B> | ('a, 'b) list | | LinkedList<A> | 'a list |

Even though &[Value] is specifically marked as no copy, a type like Option<Value> would also qualify since the inner value is not converted to a Rust type. However, Option<String> will do full unmarshaling into Rust types. Another thing to note: FromValue for str is zero-copy, however ToValue for str creates a new value - this is necessary to ensure the string lives long enough.

If you're concerned with minimizing allocations/conversions you should prefer to use Value type directly.

Upgrading

Since 0.10 and later have a much different API compared to earlier version, here is are some major differences that should be considered when upgrading: