Safe OCaml-Rust Foreign Function Interface

Overview

ocaml-rust

This repo contains code for a proof of concept for a safe OCaml-Rust interop inspired by cxx. This is mostly optimized for calling Rust code from OCaml at the moment. The interface to be exposed is defined in Rust and used both to generate some Rust wrapping code as a macro but also the necessary OCaml type and function definitions.

Running the Examples

The following runs a small example and can be useful to debug issues.

dune exec --root=tests/ocaml ./test_cmd.exe

Running the tests can be done via:

make test

Calling Rust Functions from OCaml

In this example, the Rust code to be exposed is specified via the following code. The #[ocaml_rust::bridge] macros wraps the Rust function in a way that can be called from OCaml.

#[ocaml_rust::bridge]
mod ffi {
    extern "Rust" {
        fn add_one(x: isize) -> isize;
    }
}

fn add_one(x: isize) -> isize {
    x + 1
}

The OCaml code generation code in gen/cmd will automatically generate the OCaml external definition:

type isize = int;;
module Ffi = struct
  external add_one : isize -> isize = "__ocaml_ffi_add_one"
end

And then OCaml code can simply refer to this module to call the Rust function.

let () = Stdio.printf "%d\n%!" (Test_gen.Ffi.add_one 41)

Sharing Type Definitions between OCaml and Rust

It is also possible to define struct or enum types in the ffi module. The equivalent OCaml record or variant definitions will be generated and automatically converted too.

For example, a ffi module can be defined as the following:

#[ocaml_rust::bridge]
mod ffi3 {
    #[derive(Debug, Clone)]
    enum MyEnum {
        NoArg,
        OneArg(isize),
        TwoArgs(isize, String),
        StructArgs { x: isize, y: String },
    }

    #[derive(Debug, Clone)]
    struct MyStruct {
        x: isize,
        y: String,
        z: (isize, Option<String>, f64),
        zs: Vec<f64>,
    }

    extern "Rust" {
        fn mystruct_to_string(v: &MyStruct) -> String;
    }
}

This results in the following OCaml code being generated.

module Ffi3 = struct
  type my_enum =
  | NoArg
  | OneArg of isize
  | TwoArgs of isize * string
  | StructArgs of { x: isize; y: string }
  [@@boxed];;

  type my_struct = {
    x: isize;
    y: string;
    z: (isize * string option * f64);
    zs: f64 array;
  } [@@boxed];;

  external mystruct_to_string : my_struct -> string = "__ocaml_ffi3_mystruct_to_string"
end

Finally, defining type aliases in the ffi module results in the Rust data to be wrapped in an OCaml abstract type. E.g.:

#[ocaml_rust::bridge]
mod ffi2 {
    type MyVec = Vec<i64>;

    extern "Rust" {
        fn vec_new() -> MyVec;
        fn vec_push(vec: &mut MyVec, v: isize);
        fn vec_content(vec: &MyVec) -> Vec<i64>;
    }
}

And the resulting OCaml module is:

module Ffi2 = struct
  type my_vec;;
  external vec_new : unit -> my_vec = "__ocaml_ffi2_vec_new"
  external vec_push : my_vec -> isize -> unit = "__ocaml_ffi2_vec_push"
  external vec_content : my_vec -> i64 array = "__ocaml_ffi2_vec_content"

Passing OCaml Closures to Rust Code

It is also possible to pass OCaml closure as argument to Rust functions. The following example illustrates how to do this:

#[ocaml_rust::bridge]
mod ffi4 {
    extern "Rust" {
        fn map_callback(vs: &Vec<isize>, f: &mut Fn1<isize, String>) -> Vec<String>;
    }
}

fn map_callback(vs: &Vec<isize>, f: &mut Fn1<isize, String>) -> Vec<String> {
    vs.iter().map(|x| f.call1(*x)).collect()
}

Then the map_callback function can be used in OCaml by passing it an OCaml closure as argument.

Ffi4.map_callback [| 3; 1; 4; 1; 5; 9; 2 |] (Printf.sprintf "<%d>")

Missing Bits

This is only a proof of concept at the moment, the code is unlikely to work well on real-world examples, the following bits would have to be improved:

  • Generalize the use of ocaml_boxroot to avoid GC issues when building OCaml values.
  • Properly handle float arrays and record/struct using floats only.
  • Improve the wrapping of the custom blocks used for abstract types.
  • Proper packaging.
  • Make it possible to call the OCaml code from Rust, add the extern "OCaml" construct.

Other OCaml-Rust FFI

You might also like...
A safe, fast, lightweight embeddable scripting language written in Rust.

Bud (budlang) A safe, fast, lightweight embeddable scripting language written in Rust. WARNING: This crate is not anywhere near being ready to publish

A comprehensive collection of resources and learning materials for Rust programming, empowering developers to explore and master the modern, safe, and blazingly fast language.

🦀 Awesome Rust Lang ⛰️ Project Description : Welcome to the Awesome Rust Lang repository! This is a comprehensive collection of resources for Rust, a

nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor.

🔗 nvim-oxi nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor. The project is mostly intended for p

More than safe rust abstractions over rytm-sys, an unofficial SDK for writing software for Analog Rytm running on firmware 1.70.
More than safe rust abstractions over rytm-sys, an unofficial SDK for writing software for Analog Rytm running on firmware 1.70.

rytm-rs More than safe rust abstractions over rytm-sys, an unofficial SDK for writing software for Analog Rytm running on firmware 1.70. On top of CC

A fast, powerful, and safe interpreter written in rust!

Kraber A fast, powerful, and safe programming language written in rust! About It packs a punch like the Kraber .50-Cal. Kraber is designed to be minim

A safe and idiomatic wrapper over shared memory APIs in rust with proper cleanups.

shmem-bind A safe and idiomatic wrapper over shared memory APIs in rust with proper cleanups. Quick start: check the message-passing example for bette

A thread-safe signal/slot library based on boost::signals2

About signals2 is a thread-safe signal/slot library based on the boost::signals2 C++ library. Signals are objects that contain a list of callback func

🗄️ A simple (and safe!) to consume history of Client and Studio deployment versions.

🗄️ Roblox Version Archive A simple (and safe!) to consume history of Client and Studio deployment versions. About Parsing Roblox's DeployHistory form

The safe, fast and sane package manager for Linux

moss-rs A rewrite of the Serpent OS tooling in Rust, enabling a robust implementation befitting Serpent and Solus We will initially focus on moss and

Comments
  • Fix some unsafe accesses to OCaml values

    Fix some unsafe accesses to OCaml values

    The current conversion from Rust values to OCaml values (represented by the instances of the trait ToValue) does not seem correct for nested values. For instance, consider the pair case:

    let v = unsafe { ocaml_sys::caml_alloc_tuple(2) };
    let res = pin(v);
    T1::to_value(v1, |x| unsafe { ocaml_sys::store_field(v, 0, x) });
    T2::to_value(v2, |x| unsafe { ocaml_sys::store_field(v, 1, x) });
    res
    

    It might be the case that converting v2 to an OCaml value allocates, and moves v1 around. I don't think there is currently a guarantee that the value we stored in v[0] is still valid. I believe that the first commit in this branch segfaults throught exhibiting a variation of this issue.

    This MR tries to implement ToValue using continuations (by first building a RootedValue to have a stable way to refer to OCaml values accross GCs, calling the provided continuation once the value has been assembled, then dropping the rooted value).

    Unfortunately, this branch does not fix the segfault at this stage.

    opened by braibant 6
  • Unnecessary rooting

    Unnecessary rooting

    @braibant do you have some thoughts on whether the rooting is necessary there? I would think that not but may be missing something. If it's actually not necessary, the same probably applies here too.

    opened by LaurentMazare 0
Owner
Laurent Mazare
Laurent Mazare
Collection of immutable and persistent data structures written in Rust, inspired by the standard libraries found in Haskell, Closure and OCaml

PRust: (P)ersistent & Immutable Data Structures in (Rust) This library houses a collection of immutable and persistent data structures, inspired by th

Victor Colombo 13 Aug 13, 2023
Command line interface as a function.

Command line interface as a function. fncmd fncmd is an opinionated command line parser frontend that wraps around clap. The functionality is mostly i

Yu Shimura 67 Dec 7, 2022
Rustato: A powerful, thread-safe global state management library for Rust applications, offering type-safe, reactive state handling with an easy-to-use macro-based API.

Rustato State Manager A generical thread-safe global state manager for Rust Introduction • Features • Installation • Usage • Advanced Usage • Api Refe

BiteCraft 8 Sep 16, 2024
A simple tutorial on how to call a C function from Rust 🦀

How to call a C function from Rust ?? ☎️ I was working on a Rust project where we needed to interact with code written in C. I had to learn how to wor

Vanja Cosic 58 Dec 22, 2022
A cargo subcommand that displays ghidra function output through the use of the rizin rz-ghidra project.

cargo-rz-ghidra A cargo subcommand that displays ghidra function output through the use of the rizin rz-ghidra project. Install cargo install --git ht

wcampbell 4 Nov 5, 2022
⚡ A Blazing fast alternative to the stock windows folder delete function!

Turbo Delete A blazing fast alternative to the default Windows delete. Turbodelete is a blazing fast alternative to the default Windows delete functio

Tejas Ravishankar 165 Dec 4, 2022
a Rust library implementing safe, lightweight context switches, without relying on kernel services

libfringe libfringe is a library implementing safe, lightweight context switches, without relying on kernel services. It can be used in hosted environ

edef 473 Dec 28, 2022
Next-generation, type-safe CLI parser for Rust

Next-generation, type-safe CLI parser for Rust

0918nobita 19 Jul 20, 2022
An abstract, safe, and concise color conversion library for rust nightly This requires the feature adt_const_params

colortypes A type safe color conversion library This crate provides many methods for converting between color types. Everything is implemented abstrac

Jacob 13 Dec 7, 2022
Safe Unix shell-like parameter expansion/variable substitution via cross-platform CLI or Rust API

Safe Unix shell-like parameter expansion/variable substitution for those who need a more powerful alternative to envsubst but don't want to resort to

Isak Wertwein 4 Oct 4, 2022