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.
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
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)
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"
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>")
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:
ocaml_boxroot to avoid GC issues when building OCaml values.extern "OCaml" construct.116 commits
1 commits
Rust
65.9%
OCaml
33.3%
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.
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
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)
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"
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>")
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:
ocaml_boxroot to avoid GC issues when building OCaml values.extern "OCaml" construct.116 commits
1 commits
Rust
65.9%
OCaml
33.3%