OCaml By Examples

rust

dune

We can interact with Rust in the same way we can interact with C: we use dune to build a static library of the rust code, and we later link this library to our ocaml code.
The magic happens within the `_build` directory, where dune will copy and build things out. Let's avoid copying the entire `target` repository that cargo creates.
(dirs :standard \ target)
Let's add a rule to build the rust static library every time we rune a dune command.
(rule
`target` specifies the file produced by the rule. You can also use `targets` if you need to list several files.
 (target librust_thing.a)
dune will avoid re-running this rule every time, that is unless one of the dependencies listed in `deps` has changed.
 (deps Cargo.toml lib.rs)
Here's the actual content of the rule, `progn` lists a series of commands that have to be executed in sequence. In our example, we run `cargo build` on our project, and we then extract the produced library.
 (action
  (progn
   (run cargo build --release)
   (run cp target/release/librust_thing.a .))))
Let's define the ocaml executable that will call the Rust code. Notice that it lists the library in the `foreign_archives` field.
(executable
 (name main)
 (foreign_archives rust_thing))

Cargo.toml

Here's our Rust library defined in a typical `Cargo.toml` file. Notice that the library name will prepend the word `lib` to the name of the library, as well as convert any hyphen to an underscore (typical in Rust).
[package]
name = "rust-thing"
version = "0.1.0"
edition = "2018"

[lib]
path = "lib.rs"
As we're creating a static library to be linked with our OCaml code, we need to specify that.
The [ocaml](https://crates.io/crates/ocaml) crate is used to facilitate exporting and importing types and functions to OCaml.
crate-type = ["staticlib"]

[dependencies]
ocaml = { version = "0.22.0" }

lib.rs

The derive macros `ocaml::IntoValue` and `ocaml::FromValue` allow you to convert entire types to OCaml types, which allows you to store them on the OCaml heap and have the OCaml's garbage collector handle them.
#[derive(ocaml::IntoValue, ocaml::FromValue, Debug)]
pub struct House {
    name: String,
    windows: u8,
    door: bool,
    cupboards: u64,
}
The `ocaml::func` derive macro can be used to export functions to OCaml. Here the returned value will be converted to an OCaml type thanks to the `ocaml::IntoValue` a few lines above.
#[ocaml::func]
pub fn build_house(name: String, windows: u8) -> House {
    House {
        name,
        windows,
        door: true,
        cupboards: 4,
    }
}

#[ocaml::func]
pub fn destroy_house(mut house: House) -> House {
    house.door = false;
    house
}

#[ocaml::func]
pub fn display_house(house: House) {
    println!("{:?}", house);
}
The `ocaml::custom!` macro can be used to store custom types on the OCaml heap. As these will be ignored by the OCaml garbage collector, it is important to define a finalizer that will drop the value when it is garbage-collected. You have to use this when you can't implement ocaml::FromValue and ocaml::IntoValue for some type in your structure.
#[ocaml::func]
pub fn bump_windows(house: House, num: ocaml::Int) -> Option<House> {
    if house.num + num >= 10 {
        return None
    } else {
        Some(House {
            windows: house.windows + num,
            ..house
        })
    }
}

#[derive(Debug, Clone)]
pub struct Opaque(String);

ocaml::custom!(Opaque {
    finalize: finalizer,
});

unsafe extern "C" fn finalizer(v: ocaml::Raw) {
    let ptr = v.as_pointer::<Opaque>();
    ptr.drop_in_place()
}

unsafe impl ocaml::FromValue<'_> for Opaque {
    fn from_value(value: ocaml::Value) -> Self {
        let x: ocaml::Pointer<Self> = ocaml::FromValue::from_value(value);
        x.as_ref().clone()
    }
}

#[ocaml::func]
pub fn create_opaque() -> Opaque {
    Opaque("sup".to_string())
}

#[ocaml::func]
pub fn print_opaque(opaque: Opaque) {
    println!("{:?}", opaque);
}

main.ml

type house = {
  name: string;
  windows: int;
  door: bool;
  cupboards: int64;
}

We can re-define the Rust structure in OCaml with our own record type. Note that the name of that type doesn't have to match the Rust name.
External functions have their signatures defined with the `external` keyword.
external build : string -> int -> house = "build_house"

external destroy : house -> house = "destroy_house"

external display : house -> unit = "display_house"

let () = 
  let h = build "casa" 5 in
  let h = destroy h in
  display h

module Opaque = struct
  type t

  external create : unit -> t = "create_opaque"

  external print : t -> unit = "print_opaque"

end

let () =
  let opaque = Opaque.create () in
  Opaque.print opaque

console

...
$ dune exec ./main exe
House { name: "casa", windows: 5, door: false, cupboards: 4 }
you've reached the end!