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
Take a look at the ocaml-rust-starter project for a basic example to help get started with ocaml-rs.
On the Rust side, you will need to add the following to your Cargo.toml:
ocaml = "*"or
ocaml = {git = "https://github.com/zshipko/ocaml-rs"}For macOS you will need also to add the following to your project's .cargo/config file:
[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. To do this you should run:
cargo vendorthen follow the instructions for editing .cargo/config
derive- enabled by default, adds
#[ocaml::func]and friends andderiveimplementations forFromValueandToValue
- enabled by default, adds
link- link the native OCaml runtime, this should only be used when no OCaml code will be linked statically
no-std- Allows
ocamlto be used in#![no_std]environments like MirageOS
- Allows
// Automatically derive `ToValue` and `FromValue`
#[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<f64>) -> Result<f64, ocaml::Error> {
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();
ocaml::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)
}Note: By default the func macro will create a bytecode wrapper (using bytecode_func) for functions with more than 5 arguments.
The OCaml stubs would look like this:
type example = {
name: string;
i: int;
}
external incr_example: example -> example = "incr_example"
external build_tuple: int -> int * int * int = "build_tuple"
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/src or ocaml-vec.
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 |
NOTE: Even though &[Value] is specifically marked as no copy, any 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 and &[u8] is zero-copy, however ToValue for str and &[u8] creates a new value - this is necessary to ensure the string is registered with the OCaml runtime.
If you're concerned with minimizing allocations/conversions you should use Value type directly.
Pointer<T> can be used to create and access Rust types on the OCaml heap.
For example, for a type that implements Custom:
use ocaml::FromValue;
struct MyType;
unsafe extern "C" fn mytype_finalizer(v: ocaml::Value) {
let ptr: ocaml::Pointer<MyType> = ocaml::Pointer::from_value(v);
ptr.drop_in_place()
}
ocaml::custom_finalize!(MyType, mytype_finalizer);
#[ocaml::func]
pub fn new_my_type() -> ocaml::Pointer<MyType> {
ocaml::Pointer::alloc_custom(MyType)
// ocaml::Pointer::alloc_final(MyType, finalizer) can also be used
// if you don't intend to implement `Custom`
}
#[ocaml::func]
pub fn my_type_example(t: ocaml::Pointer<MyType>) {
let my_type = t.as_mut();
// MyType has no fields, but normally you
// would do something with MyType here
}When a Rust panic or Err is encountered it will be raised as a Failure on the OCaml side, to configure a custom exception type you can register it with the OCaml runtime using the name Rust_exception:
exception Rust
let () = Callback.register_exception "Rust_error" (Rust "")It must take a single string argument.
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:
FromValueandToValuehave been markedunsafebecause converting OCaml values to Rust and back also depends on the OCaml type signature.- A possible solution to this would be a
cbindgenlike tool that generates the correct OCaml types from the Rust code
- A possible solution to this would be a
ToValuenow takes ownership of the value being converted- The
caml!macro has been rewritten as a procedural macro calledocaml::func, which performs automatic type conversionocaml::native_funcandocaml::bytecode_funcwere also added to create functions at a slightly lower levelderivefeature required
- Added
deriveimplementations forToValueandFromValuefor stucts and enumsderivefeature required
i32andu32now map to OCaml'sint32type rather than theinttype- Use
ocaml::Int/ocaml::Uintto refer to the OCaml'sinttypes now
- Use
ArrayandListnow take generic types- Strings are converted to
strorString, rather than using theStrtype - Tuples are converted to Rust tuples (up to 20 items), rather than using the
Tupletype - The
coremodule has been renamed tosysand is now just an alias for theocaml-syscrate and all sub-module have been removed