Need help with ocaml-rs?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

zshipko
135 Stars 16 Forks ISC License 234 Commits 1 Opened issues

Description

OCaml extensions in Rust

Services available

!
?

Need anything else?

Contributors list

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

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 vendor

then follow the instructions for editing

.cargo/config

Build options

By default, building

ocaml-sys
will invoke the
ocamlopt
command to figure out the version and location of the OCaml compiler. There are a few environment variables to control this.
  • OCAMLOPT
    (default:
    ocamlopt
    ) is the command that will invoke
    ocamlopt
  • OCAML_VERSION
    (default: result of
    $OCAMLOPT -version
    ) is the target runtime OCaml version.
  • OCAML_WHERE_PATH
    (default: result of
    $OCAMLOPT -where
    ) is the path of the OCaml standard library.

If both

OCAML_VERSION
and
OCAML_WHERE_PATH
are present, their values are used without invoking
ocamlopt
. If any of those two env variables is undefined, then
ocamlopt
will be invoked to obtain both values.

Defining the

OCAML_VERSION
and
OCAML_WHERE_PATH
variables is useful for saving time in CI environments where an OCaml install is not really required (to run
clippy
for example).

Features

  • derive
    • enabled by default, adds
      #[ocaml::func]
      and friends and
      derive
      implementations for
      FromValue
      and
      ToValue
  • link
    • link the native OCaml runtime, this should only be used when no OCaml code will be linked statically
  • no-std
    • Allows
      ocaml
      to be used in
      #![no_std]
      environments like MirageOS

Documentation

https://docs.rs/ocaml

Examples

// Automatically derive `ToValue` and `FromValue`
#[derive(ocaml::ToValue, ocaml::FromValue)]
struct Example {
    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(); 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.

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
| |
[u8]
|
bytes
| |
String
|
string
| |
Option
|
'a option
| |
Result
|
exception
| |
(A, B, C)
|
'a * 'b * 'c
| |
&[Value]
|
'a array
(no copy) | |
Vec
,
&[A]
|
'a array
| |
BTreeMap
|
('a, 'b) list
| |
LinkedList
|
'a list
|

NOTE: Even though

&[Value]
is specifically marked as no copy, any type like
Option
would also qualify since the inner value is not converted to a Rust type. However,
Option
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.

Pointers to Rust values on the OCaml heap

Pointer
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 = ocaml::Pointer::from_value(v); ptr.drop_in_place() }

ocaml::custom_finalize!(MyType, mytype_finalizer);

#[ocaml::func] pub fn new_my_type() -> ocaml::Pointer { 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) { let my_type = t.as_mut(); // MyType has no fields, but normally you // would do something with MyType here }

Custom exception type

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.

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:

  • FromValue
    and
    ToValue
    have been marked
    unsafe
    because converting OCaml values to Rust and back also depends on the OCaml type signature.
    • A possible solution to this would be a
      cbindgen
      like tool that generates the correct OCaml types from the Rust code
  • ToValue
    now takes ownership of the value being converted
  • The
    caml!
    macro has been rewritten as a procedural macro called
    ocaml::func
    , which performs automatic type conversion
    • ocaml::native_func
      and
      ocaml::bytecode_func
      were also added to create functions at a slightly lower level
    • derive
      feature required
  • Added
    derive
    implementations for
    ToValue
    and
    FromValue
    for stucts and enums
    • derive
      feature required
  • i32
    and
    u32
    now map to OCaml's
    int32
    type rather than the
    int
    type
    • Use
      ocaml::Int
      /
      ocaml::Uint
      to refer to the OCaml's
      int
      types now
  • Array
    and
    List
    now take generic types
  • Strings are converted to
    str
    or
    String
    , rather than using the
    Str
    type
  • Tuples are converted to Rust tuples (up to 20 items), rather than using the
    Tuple
    type
  • The
    core
    module has been renamed to
    sys
    and is now just an alias for the
    ocaml-sys
    crate and all sub-module have been removed

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.