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;