ocaml_interop/
lib.rs

1// Copyright (c) Viable Systems and TezEdge Contributors
2// SPDX-License-Identifier: MIT
3
4#![doc(html_root_url = "https://docs.rs/ocaml-interop/0.11.2")]
5
6//! _Zinc-iron alloy coating is used in parts that need very good corrosion protection._
7//!
8//! **API IS CONSIDERED UNSTABLE AT THE MOMENT AND IS LIKELY TO CHANGE IN THE FUTURE**
9//!
10//! **IMPORTANT: This version of `ocaml-interop` exclusively supports OCaml 5.x and leverages its domain-based concurrency model. OCaml 4.x is no longer supported.**
11//!
12//! [ocaml-interop](https://github.com/tizoc/ocaml-interop) is an OCaml<->Rust FFI with an emphasis
13//! on safety inspired by [caml-oxide](https://github.com/stedolan/caml-oxide),
14//! [ocaml-rs](https://github.com/zshipko/ocaml-rs) and [CAMLroot](https://arxiv.org/abs/1812.04905).
15//!
16//! ## Table of Contents
17//!
18//! - [Usage](#usage)
19//!   * [The OCaml runtime handle](#the-ocaml-runtime-handle)
20//!   * [OCaml value representation](#ocaml-value-representation)
21//!   * [Converting between OCaml and Rust data](#converting-between-ocaml-and-rust-data)
22//!     + [`FromOCaml` trait](#fromocaml-trait)
23//!     + [`ToOCaml` trait](#toocaml-trait)
24//!   * [Calling convention](#calling-convention)
25//!   * [OCaml exceptions](#ocaml-exceptions)
26//!   * [Calling into OCaml from Rust](#calling-into-ocaml-from-rust)
27//!   * [Calling into Rust from OCaml](#calling-into-rust-from-ocaml)
28//! - [References and links](#references-and-links)
29//!
30//! ## Usage
31//!
32//! ### Runtime Initialization and Management
33//!
34//! For Rust programs that intend to call into OCaml code, the OCaml runtime must first be initialized
35//! from the Rust side. This is done using [`OCamlRuntime::init`]. If your Rust code is being called
36//! as a library from an existing OCaml program, the OCaml runtime will already be initialized by OCaml,
37//! `OCamlRuntime::init()` must not be called from Rust in that scenario.
38//!
39//! When initializing from Rust:
40//!
41//! ```rust,no_run
42//! # use ocaml_interop::{OCamlRuntime, OCamlRuntimeStartupGuard};
43//! # fn main() -> Result<(), String> {
44//! // Initialize the OCaml runtime. This also sets up boxroot.
45//! let _guard: OCamlRuntimeStartupGuard = OCamlRuntime::init()?;
46//! // The OCaml runtime is now active.
47//! // ... OCaml operations occur here, typically within `OCamlRuntime::with_domain_lock` ...
48//! // When `_guard` goes out of scope, it automatically calls `boxroot_teardown`
49//! // and `caml_shutdown` for proper cleanup.
50//! # Ok(())
51//! # }
52//! ```
53//!
54//! [`OCamlRuntime::init`] returns a [`Result<OCamlRuntimeStartupGuard, String>`]. The [`OCamlRuntimeStartupGuard`]
55//! is an RAII type that ensures the OCaml runtime (including boxroot) is properly shut down when it's dropped.
56//! Both [`OCamlRuntimeStartupGuard`] and [`OCamlRuntime`] are `!Send` and `!Sync` to enforce thread safety
57//! in line with OCaml 5's domain-based concurrency.
58//!
59//! ### Acquiring and Using the OCaml Runtime Handle
60//!
61//! Most operations require a mutable reference to the OCaml runtime, `cr: &mut OCamlRuntime`.
62//!
63//! **When Calling OCaml from Rust:**
64//! The primary way to obtain `cr` is via [`OCamlRuntime::with_domain_lock`]. This method ensures
65//! the current thread is registered as an OCaml domain and holds the OCaml runtime lock:
66//!
67//! ```rust,no_run
68//! # use ocaml_interop::{OCamlRuntime, ToOCaml, OCaml};
69//! # fn main() -> Result<(), String> {
70//! # let _guard = OCamlRuntime::init()?;
71//! OCamlRuntime::with_domain_lock(|cr| {
72//!     // `cr` is the &mut OCamlRuntime.
73//!     // All OCaml interactions happen here.
74//!     let _ocaml_string: OCaml<String> = "Hello, OCaml!".to_ocaml(cr);
75//! });
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! **When OCaml Calls Rust:**
81//! For functions exported using [`ocaml_export!`], `cr` is automatically provided as the first argument.
82//!
83//! Un-rooted non-immediate OCaml values have a lifetime associated with the current scope of `cr` usage.
84//! Accessing them after the OCaml runtime has been re-entered (e.g., another call to `with_domain_lock` or
85//! a call into OCaml that might trigger the GC) can lead to use-after-free errors if not properly rooted.
86//!
87//! ### OCaml value representation
88//!
89//! OCaml values are exposed to Rust using three types:
90//!
91//! - [`OCaml`]`<'gc, T>` is the representation of OCaml values in Rust. These values become stale
92//!   after calls into the OCaml runtime and must be re-referenced.
93//! - [`BoxRoot`]`<T>` is a container for an [`OCaml`]`<T>` value that is rooted and tracked by
94//!   OCaml's Garbage Collector. `BoxRoot::new()` and `BoxRoot::keep()` will panic if the
95//!   underlying boxroot operation fails. `BoxRoot<T>` is `!Send` and `!Sync` due to its
96//!   affinity with OCaml's domain-specific GC state.
97//! - [`OCamlRef`]`<'a, T>` is a reference to an [`OCaml`]`<T>` value that may or may not be rooted.
98//!
99//! ### Converting between OCaml and Rust data
100//!
101//! #### [`FromOCaml`] trait
102//!
103//! The [`FromOCaml`] trait implements conversion from OCaml values into Rust values, using the `from_ocaml` function.
104//!
105//! [`OCaml`]`<T>` values have a `to_rust()` method that is usually more convenient than `Type::from_ocaml(ocaml_value)`,
106//! and works for any combination that implements the `FromOCaml` trait.
107//!
108//! [`OCamlRef`]`<T>` values have a `to_rust(cr)` that needs an [`OCamlRuntime`] reference to be passed to it.
109//!
110//! #### [`ToOCaml`] trait
111//!
112//! The [`ToOCaml`] trait implements conversion from Rust values into OCaml values, using the `to_ocaml` method.
113//! It takes a single parameter that must be a `&mut OCamlRuntime`.
114//!
115//! ### Calling convention
116//!
117//! There are two possible calling conventions in regards to rooting, one with *callee rooted arguments*,
118//! and another with *caller rooted arguments*.
119//!
120//! #### Callee rooted arguments calling convention
121//!
122//! With this calling convention, values that are arguments to a function call are passed directly.
123//! Functions that receive arguments are responsible for rooting them. This is how OCaml's C API and
124//! `ocaml-interop` versions before `0.5.0` work.
125//!
126//! #### Caller rooted arguments calling convention
127//!
128//! With this calling convention, values that are arguments to a function call must be rooted by the caller.
129//! Then instead of the value, it is the root pointing to the value that is passed as an argument.
130//! This is how `ocaml-interop` works starting with version `0.5.0`.
131//!
132//! When a Rust function is called from OCaml, it will receive arguments as [`OCamlRef`]`<T>` values,
133//! and when a OCaml function is called from Rust, arguments will be passed as [`OCamlRef`]`<T>` values.
134//!
135//! #### Return values
136//!
137//! When an OCaml function is called from Rust, the return value is a [`BoxRoot`]`<T>`.
138//!
139//! Rust functions that are meant to be called from OCaml must return [`OCaml`]`<T>` values.
140//!
141//! ### OCaml exceptions
142//!
143//! If an OCaml function called from Rust raises an exception, this will result in a panic.
144//!
145//! OCaml functions meant to be called from Rust should not raise exceptions to signal errors,
146//! but instead return `result` or `option` values, which can then be mapped into `Result` and
147//! `Option` values in Rust.
148//!
149//! ### Calling into OCaml from Rust
150//!
151//! The following code defines two OCaml functions and registers them using the `Callback.register` mechanism:
152//!
153//! ```ocaml
154//! let increment_bytes bytes first_n =
155//!   let limit = (min (Bytes.length bytes) first_n) - 1 in
156//!   for i = 0 to limit do
157//!     let value = (Bytes.get_uint8 bytes i) + 1 in
158//!     Bytes.set_uint8 bytes i value
159//!   done;
160//!   bytes
161//!
162//! let twice x = 2 * x
163//!
164//! let () =
165//!   Callback.register "increment_bytes" increment_bytes;
166//!   Callback.register "twice" twice
167//! ```
168//!
169//! To be able to call these from Rust, there are a few things that need to be done:
170//!
171//! - Rust-driven programs must initialize the OCaml runtime.
172//! - Functions that were exported from the OCaml side with `Callback.register` have to be declared using the [`ocaml!`] macro.
173//!
174//! ### Example
175//!
176//! ```rust,no_run
177//! use ocaml_interop::{
178//!     BoxRoot, FromOCaml, OCaml, OCamlInt, OCamlRef, ToOCaml, OCamlRuntime
179//! };
180//!
181//! // To call an OCaml function, it first has to be declared inside an `ocaml!` macro block:
182//! mod ocaml_funcs {
183//!     use ocaml_interop::{ocaml, OCamlInt};
184//!
185//!     ocaml! {
186//!         // OCaml: `val increment_bytes: bytes -> int -> bytes`
187//!         // registered with `Callback.register "increment_bytes" increment_bytes`.
188//!         // In Rust, this will be exposed as:
189//!         //     pub fn increment_bytes(
190//!         //         _: &mut OCamlRuntime,
191//!         //         bytes: OCamlRef<String>,
192//!         //         first_n: OCamlRef<OCamlInt>,
193//!         //     ) -> BoxRoot<String>;
194//!         pub fn increment_bytes(bytes: String, first_n: OCamlInt) -> String;
195//!         // OCaml: `val twice: int -> int`
196//!         // registered with `Callback.register "twice" twice`.
197//!         // In Rust this will be exposed as:
198//!         //     pub fn twice(
199//!         //         _: &mut OCamlRuntime,
200//!         //         num: OCamlRef<OCamlInt>,
201//!         //     ) -> BoxRoot<OCamlInt>;
202//!         pub fn twice(num: OCamlInt) -> OCamlInt;
203//!     }
204//! }
205//!
206//! fn increment_bytes(
207//!     cr: &mut OCamlRuntime,
208//!     bytes1: String,
209//!     bytes2: String,
210//!     first_n: usize,
211//! ) -> (String, String) {
212//!     // Any calls into the OCaml runtime takes as input a `&mut` reference to an `OCamlRuntime`
213//!     // value that is obtained as the result of initializing the OCaml runtime with the
214//!     // `OCamlRuntime::init()` call.
215//!     // The `ToOCaml` trait provides the `to_ocaml` and `to_boxroot` methods to convert Rust
216//!     // values into OCaml values.
217//!     // Here `to_boxroot` is used to produce OCaml values that are already rooted.
218//!     let ocaml_bytes1_rooted: BoxRoot<String> = bytes1.to_boxroot(cr);
219//!     let ocaml_bytes2_rooted = bytes2.to_boxroot(cr);
220//!
221//!     // Rust `i64` integers can be converted into OCaml fixnums with `OCaml::of_i64`
222//!     // and `OCaml::of_i64_unchecked`.
223//!     // Such conversion doesn't require any allocation on the OCaml side, and doesn't
224//!     // invalidate other `OCaml<T>` values. In addition, these immediate values require rooting.
225//!     let ocaml_first_n: OCaml<'static, OCamlInt> =
226//!         unsafe { OCaml::of_i64_unchecked(first_n as i64) };
227//!
228//!     // Any OCaml function (declared above in a `ocaml!` block) can be called as a regular
229//!     // Rust function, by passing a `&mut OCamlRuntime` as the first argument, followed by
230//!     // the rest of the arguments declared for that function.
231//!     // Arguments to these functions must be `OCamlRef<T>` values. These are the result of
232//!     // dereferencing `OCaml<T>` and `BoxRoot<T>` values.
233//!     let result1 = ocaml_funcs::increment_bytes(
234//!         cr,                   // &mut OCamlRuntime
235//!         &ocaml_bytes1_rooted, // OCamlRef<String>
236//!         &ocaml_first_n,       // OCamlRef<OCamlInt>
237//!     );
238//!
239//!     let result2 = ocaml_funcs::increment_bytes(
240//!         cr,
241//!         &ocaml_bytes2_rooted,
242//!         &ocaml_first_n,
243//!     );
244//!
245//!     (result1.to_rust(cr), result2.to_rust(cr))
246//! }
247//!
248//! fn twice(cr: &mut OCamlRuntime, num: usize) -> usize {
249//!     let ocaml_num = unsafe { OCaml::of_i64_unchecked(num as i64) };
250//!     let result = ocaml_funcs::twice(cr, &ocaml_num);
251//!     result.to_rust::<i64>(cr) as usize
252//! }
253//!
254//! fn entry_point() {
255//!     // IMPORTANT: the OCaml runtime has to be initialized first.
256//!     let _guard = OCamlRuntime::init().unwrap();
257//!     OCamlRuntime::with_domain_lock(|cr| {
258//!         // `cr` is the OCaml runtime handle, must be passed to any function
259//!         // that interacts with the OCaml runtime.
260//!         let first_n = twice(cr, 5);
261//!         let bytes1 = "000000000000000".to_owned();
262//!         let bytes2 = "aaaaaaaaaaaaaaa".to_owned();
263//!         println!("Bytes1 before: {}", bytes1);
264//!         println!("Bytes2 before: {}", bytes2);
265//!         let (result1, result2) = increment_bytes(cr, bytes1, bytes2, first_n);
266//!         println!("Bytes1 after: {}", result1);
267//!         println!("Bytes2 after: {}", result2);
268//!     });
269//!     // `OCamlRuntimeStartupGuard`'s `Drop` implementation will pefrorm the necessary cleanup
270//!     // to shutdown the OCaml runtime.
271//! }
272//! ```
273//!
274//! ### Calling into Rust from OCaml
275//!
276//! To be able to call a Rust function from OCaml, it has to be defined in a way that exposes it to OCaml. This can be done with the [`ocaml_export!`] macro.
277//!
278//! #### Example
279//!
280//! ```rust,no_run
281//! use ocaml_interop::{
282//!     ocaml_export, FromOCaml, OCamlInt, OCaml, OCamlBytes,
283//!     OCamlRef, ToOCaml,
284//! };
285//!
286//! // `ocaml_export` expands the function definitions by adding `pub` visibility and
287//! // the required `#[no_mangle]` and `extern` declarations. It also takes care of
288//! // acquiring the OCaml runtime handle and binding it to the name provided as
289//! // the first parameter of the function.
290//! ocaml_export! {
291//!     // The first parameter is a name to which the GC frame handle will be bound to.
292//!     // The remaining parameters must have type `OCamlRef<T>`, and the return
293//!     // value `OCaml<T>`.
294//!     fn rust_twice(cr, num: OCamlRef<OCamlInt>) -> OCaml<OCamlInt> {
295//!         let num: i64 = num.to_rust(cr);
296//!         unsafe { OCaml::of_i64_unchecked(num * 2) }
297//!     }
298//!
299//!     fn rust_increment_bytes(
300//!         cr,
301//!         bytes: OCamlRef<OCamlBytes>,
302//!         first_n: OCamlRef<OCamlInt>,
303//!     ) -> OCaml<OCamlBytes> {
304//!         let first_n: i64 = first_n.to_rust(cr);
305//!         let first_n = first_n as usize;
306//!         let mut vec: Vec<u8> = bytes.to_rust(cr);
307//!
308//!         for i in 0..first_n {
309//!             vec[i] += 1;
310//!         }
311//!
312//!         vec.to_ocaml(cr)
313//!     }
314//! }
315//! ```
316//!
317//! Then in OCaml, these functions can be referred to in the same way as C functions:
318//!
319//! ```ocaml
320//! external rust_twice: int -> int = "rust_twice"
321//! external rust_increment_bytes: bytes -> int -> bytes = "rust_increment_bytes"
322//! ```
323//!
324//! ## References and links
325//!
326//! - OCaml Manual: [Chapter 20  Interfacing C with OCaml](https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html).
327//! - [Safely Mixing OCaml and Rust](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxtbHdvcmtzaG9wcGV8Z3g6NDNmNDlmNTcxMDk1YTRmNg) paper by Stephen Dolan.
328//! - [Safely Mixing OCaml and Rust](https://www.youtube.com/watch?v=UXfcENNM_ts) talk by Stephen Dolan.
329//! - [CAMLroot: revisiting the OCaml FFI](https://arxiv.org/abs/1812.04905).
330//! - [caml-oxide](https://github.com/stedolan/caml-oxide), the code from that paper.
331//! - [ocaml-rs](https://github.com/zshipko/ocaml-rs), another OCaml<->Rust FFI library.
332
333mod boxroot;
334mod closure;
335mod conv;
336mod error;
337mod macros;
338mod memory;
339mod mlvalues;
340mod runtime;
341mod value;
342
343pub use crate::boxroot::BoxRoot;
344
345pub use crate::closure::{OCamlFn1, OCamlFn2, OCamlFn3, OCamlFn4, OCamlFn5};
346pub use crate::conv::{FromOCaml, ToOCaml};
347pub use crate::memory::alloc_cons as cons;
348pub use crate::memory::OCamlRef;
349pub use crate::memory::{alloc_error, alloc_ok};
350pub use crate::mlvalues::{
351    bigarray, DynBox, OCamlBytes, OCamlException, OCamlFloat, OCamlFloatArray, OCamlInt,
352    OCamlInt32, OCamlInt64, OCamlList, OCamlUniformArray, RawOCaml,
353};
354pub use crate::runtime::{OCamlRuntime, OCamlRuntimeStartupGuard};
355pub use crate::value::OCaml;
356
357#[doc(hidden)]
358pub mod internal {
359    pub use crate::closure::OCamlClosure;
360    pub use crate::memory::{alloc_tuple, caml_alloc, store_field};
361    pub use crate::mlvalues::tag;
362    pub use crate::mlvalues::UNIT;
363    pub use crate::runtime::internal::recover_runtime_handle_mut;
364    pub use ocaml_boxroot_sys::boxroot_teardown;
365    pub use ocaml_sys::caml_hash_variant;
366
367    // To bypass ocaml_sys::int_val unsafe declaration
368    pub fn int_val(val: super::RawOCaml) -> isize {
369        unsafe { ocaml_sys::int_val(val) }
370    }
371}
372
373#[doc(hidden)]
374#[cfg(doctest)]
375pub mod compile_fail_tests;
376
377#[doc(hidden)]
378#[cfg(test)]
379mod compile_ok_tests;