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.12.0")]
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: Starting with version `0.11.0` only OCaml 5.x is 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//!   * [Runtime Initialization and Management](#runtime-initialization-and-management)
20//!   * [Acquiring and Using the OCaml Runtime Handle](#acquiring-and-using-the-ocaml-runtime-handle)
21//!   * [OCaml value representation](#ocaml-value-representation)
22//!   * [Converting between OCaml and Rust data](#converting-between-ocaml-and-rust-data)
23//!   * [Calling convention](#calling-convention)
24//!   * [OCaml exceptions](#ocaml-exceptions)
25//!   * [Calling into OCaml from Rust](#calling-into-ocaml-from-rust)
26//!   * [Calling into Rust from OCaml](#calling-into-rust-from-ocaml)
27//! - [User Guides](user_guides)
28//! - [References and links](#references-and-links)
29//!
30//! ## Usage
31//!
32//! This section provides a high-level overview of `ocaml-interop`. For detailed explanations,
33//! tutorials, and best practices, please refer to the [User Guides module](user_guides).
34//!
35//! ### Runtime Initialization and Management
36//!
37//! Proper initialization and management of the OCaml runtime is crucial, especially when Rust
38//! code drives the execution. This involves using [`OCamlRuntime::init`] and managing its
39//! lifecycle with [`OCamlRuntimeStartupGuard`].
40//!
41//! For detailed information, see
42//! [OCaml Runtime (Part 5)](user_guides::part5_managing_the_ocaml_runtime_for_rust_driven_programs).
43//!
44//! ### Acquiring and Using the OCaml Runtime Handle
45//!
46//! Most interop operations require an OCaml runtime handle (`cr: &mut OCamlRuntime`).
47//! This handle is obtained differently depending on whether Rust calls OCaml or OCaml calls Rust.
48//!
49//! See these guides for more details:
50//! - [Part 2: Fundamental Concepts](user_guides::part2_fundamental_concepts)
51//! - [OCaml Runtime (Part 5)](user_guides::part5_managing_the_ocaml_runtime_for_rust_driven_programs)
52//!
53//! ### OCaml value representation
54//!
55//! OCaml values are represented in Rust using types like [`OCaml<'gc, T>`](OCaml),
56//! [`BoxRoot<T>`](BoxRoot), and [`OCamlRef<'a, T>`](OCamlRef), each with specific roles
57//! in memory management and GC interaction.
58//!
59//! Learn more in [Part 2: Fundamental Concepts](user_guides::part2_fundamental_concepts).
60//!
61//! ### Converting between OCaml and Rust data
62//!
63//! The traits [`FromOCaml`] and [`ToOCaml`] facilitate data conversion
64//! between Rust and OCaml types.
65//!
66//! For conversion details and examples, refer to
67//! [Part 2: Fundamental Concepts](user_guides::part2_fundamental_concepts), as well as the guides
68//! on exporting and invoking functions.
69//!
70//! ### Calling convention
71//!
72//! `ocaml-interop` uses a caller-rooted argument convention for safety, where the caller is
73//! responsible for ensuring arguments are rooted before a function call.
74//!
75//! This is explained further in [Part 2: Fundamental Concepts](user_guides::part2_fundamental_concepts).
76//!
77//! ### OCaml exceptions
78//!
79//! By default, Rust panics in exported functions are caught and translated to OCaml exceptions.
80//! Conversely, OCaml exceptions raised during calls from Rust will result in Rust panics.
81//!
82//! For error handling strategies, see
83//! [Part 2: Fundamental Concepts](user_guides::part2_fundamental_concepts) and
84//! [Part 6: Advanced Topics](user_guides::part6_advanced_topics).
85//!
86//! ### Calling into OCaml from Rust
87//!
88//! To call OCaml functions from Rust, they typically need to be registered in OCaml
89//! (e.g., using `Callback.register`) and then declared in Rust using the [`ocaml!`] macro.
90//! This setup allows Rust to find and invoke these OCaml functions.
91//!
92//! For a comprehensive guide on calling OCaml functions from Rust,
93//! including detailed examples and best practices, please see:
94//! [Invoking OCaml Functions (Part 4)](user_guides::part4_invoking_ocaml_functions_from_rust).
95//!
96//! ### Calling into Rust from OCaml
97//!
98//! Rust functions can be exposed to OCaml using the [`#[ocaml_interop::export]`](export)
99//! procedural macro, which handles FFI boilerplate, type marshalling, and panic safety.
100//!
101//! Attributes like `no_panic_catch`, `bytecode`, and `noalloc` allow customization.
102//!
103//! For a detailed guide, see
104//! [Exporting Rust Functions (Part 3)](user_guides::part3_exporting_rust_functions_to_ocaml).
105//!
106//! ## References and links
107//!
108//! - OCaml Manual: [Chapter 20  Interfacing C with OCaml](https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html).
109//! - [Safely Mixing OCaml and Rust](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxtbHdvcmtzaG9wcGV8Z3g6NDNmNDlmNTcxMDk1YTRmNg) paper by Stephen Dolan.
110//! - [Safely Mixing OCaml and Rust](https://www.youtube.com/watch?v=UXfcENNM_ts) talk by Stephen Dolan.
111//! - [CAMLroot: revisiting the OCaml FFI](https://arxiv.org/abs/1812.04905).
112//! - [caml-oxide](https://github.com/stedolan/caml-oxide), the code from that paper.
113//! - [ocaml-rs](https://github.com/zshipko/ocaml-rs), another OCaml<->Rust FFI library.
114
115mod boxroot;
116mod closure;
117mod conv;
118mod error;
119mod macros;
120mod memory;
121mod mlvalues;
122mod runtime;
123#[doc = include_str!("../docs/README.md")]
124pub mod user_guides;
125mod value;
126
127pub use crate::boxroot::BoxRoot;
128
129pub use crate::closure::{OCamlFn1, OCamlFn2, OCamlFn3, OCamlFn4, OCamlFn5};
130pub use crate::conv::{FromOCaml, ToOCaml};
131pub use crate::memory::alloc_cons as cons;
132pub use crate::memory::OCamlRef;
133pub use crate::memory::{alloc_error, alloc_ok};
134pub use crate::mlvalues::{
135    bigarray, DynBox, OCamlBytes, OCamlException, OCamlFloat, OCamlFloatArray, OCamlInt,
136    OCamlInt32, OCamlInt64, OCamlList, OCamlUniformArray, RawOCaml,
137};
138pub use crate::runtime::{OCamlRuntime, OCamlRuntimeStartupGuard};
139pub use crate::value::OCaml;
140
141/// Exports a Rust function to OCaml.
142///
143/// This procedural macro handles the complexities of the OCaml Foreign Function Interface (FFI),
144/// allowing Rust functions to be called from OCaml code. It generates the necessary
145/// `extern "C"` wrapper function and manages type conversions and memory safety.
146///
147/// ## Basic Usage
148///
149/// ```rust
150/// use ocaml_interop::{OCaml, OCamlRuntime, OCamlBytes, OCamlInt, ToOCaml};
151///
152/// #[ocaml_interop::export]
153/// fn process_bytes(cr: &mut OCamlRuntime, data: OCaml<OCamlBytes>) -> OCaml<OCamlInt> {
154///     let byte_slice: &[u8] = &data.to_rust::<Vec<u8>>();
155///     let length = byte_slice.len() as i64;
156///     length.to_ocaml(cr)
157/// }
158/// ```
159///
160/// The macro generates an `extern "C"` function with the same identifier as the Rust function
161/// (e.g., `process_bytes` in the example above).
162///
163/// ## Key Features
164///
165/// *   **Automatic FFI Boilerplate:** Generates the `extern "C"` wrapper and handles argument/return
166///     value marshalling.
167/// *   **Type Safety:** Utilizes types like [`OCaml<T>`] and [`BoxRoot<T>`] to provide safe
168///     abstractions over OCaml values.
169/// *   **Argument Handling:**
170///     *   The first argument *must* be [`&mut OCamlRuntime`] (or [`&OCamlRuntime`] if `noalloc`
171///         is used).
172///     *   [`OCaml<'gc, T>`]: For OCaml values passed as arguments. These are *not* automatically
173///         rooted by the macro. Their lifetime `'gc` is tied to the current function call's
174///         [`OCamlRuntime`] scope. Root them explicitly (e.g., with [`BoxRoot<T>`]) if they need to
175///         persist beyond this scope or be re-passed to OCaml.
176///     *   [`BoxRoot<T>`]: If an argument is declared as `BoxRoot<T>`, the macro automatically
177///         roots the incoming OCaml value before your function body executes. This ensures the value
178///         is valid throughout the function, even across further OCaml calls.
179///     *   **Direct Primitive Types:** Supports direct mapping for Rust primitive types like `f64`,
180///         `i64`, `i32`, `bool`, and `isize` as arguments. The OCaml `external` declaration
181///         must use corresponding `[@@unboxed]` or `[@untagged]` attributes.
182/// *   **Return Types:**
183///     *   Typically, functions return [`OCaml<T>`].
184///     *   Direct primitive types (see above) can also be returned.
185/// *   **Panic Handling:**
186///     *   By default, Rust panics are caught and raised as an OCaml exception (`RustPanic of string`
187///         if registered, otherwise `Failure`).
188///     *   This can be disabled with `#[ocaml_interop::export(no_panic_catch)]`. Use with caution.
189/// *   **Bytecode Function Generation:**
190///     *   Use `#[ocaml_interop::export(bytecode = "my_ocaml_bytecode_function_name")]` to generate
191///         a wrapper for OCaml bytecode compilation.
192///     *   The OCaml `external` declaration should then specify both the bytecode and native
193///         function names: `external rust_fn : int -> int = "bytecode_stub_name" "native_c_stub_name"`.
194/// *   **`noalloc` Attribute:**
195///     *   `#[ocaml_interop::export(noalloc)]` for functions that must not trigger OCaml GC
196///         allocations.
197///     *   Requires the runtime argument to be `cr: &OCamlRuntime` (immutable).
198///     *   Implies `no_panic_catch`. Panics in `noalloc` functions lead to undefined behavior.
199///     *   The corresponding OCaml `external` **must** be annotated with `[@@noalloc]`.
200///     *   The user is responsible for ensuring no OCaml allocations occur in the Rust function body.
201///
202/// ## Argument and Return Value Conventions
203///
204/// The macro handles the conversion between OCaml's representation and Rust types.
205///
206/// ### OCaml Values
207///
208/// *   [`OCaml<T>`]: Represents an OCaml value that is not yet rooted. Its lifetime is tied to the
209///     current OCaml runtime scope.
210/// *   [`BoxRoot<T>`]: Represents an OCaml value that has been rooted and is protected from the OCaml
211///     garbage collector. The `#[ocaml_interop::export]` macro can automatically root arguments
212///     if they are specified as `BoxRoot<T>`.
213///
214/// ### Direct Primitive Type Mapping
215///
216/// For performance, certain Rust primitive types can be directly mapped to unboxed or untagged
217/// OCaml types. This avoids boxing overhead.
218///
219/// | Rust Type | OCaml Type        | OCaml `external` Attribute(s) Needed |
220/// | :-------- | :---------------- | :----------------------------------- |
221/// | `f64`     | `float`           | `[@@unboxed]` (or on arg/ret type)   |
222/// | `i64`     | `int64`           | `[@@unboxed]` (or on arg/ret type)   |
223/// | `i32`     | `int32`           | `[@@unboxed]` (or on arg/ret type)   |
224/// | `bool`    | `bool`            | `[@untagged]` (or on arg/ret type)   |
225/// | `isize`   | `int`             | `[@untagged]` (or on arg/ret type)   |
226/// | `()`      | `unit`            | (Usually implicit for return)        |
227///
228/// **Example (OCaml `external` for direct primitives):**
229/// ```ocaml
230/// external process_primitive_values :
231///   (int [@untagged]) ->
232///   (bool [@untagged]) ->
233///   (float [@unboxed]) ->
234///   (int32 [@unboxed]) =
235///   "" "process_primitive_values"
236/// ```
237///
238/// For more detailed information, refer to the user guides, particularly
239/// [Exporting Rust Functions (Part 3)](user_guides::part3_exporting_rust_functions_to_ocaml)
240///
241/// [`OCaml<T>`]: OCaml
242/// [`OCaml<'gc, T>`]: OCaml
243/// [`BoxRoot<T>`]: BoxRoot
244/// [`&mut OCamlRuntime`]: OCamlRuntime
245/// [`&OCamlRuntime`]: OCamlRuntime
246pub use ocaml_interop_derive::export;
247
248#[doc(hidden)]
249pub mod internal {
250    pub use crate::closure::OCamlClosure;
251    pub use crate::memory::{alloc_tuple, caml_alloc, store_field};
252    pub use crate::mlvalues::tag;
253    pub use crate::mlvalues::UNIT;
254    pub use crate::runtime::internal::{recover_runtime_handle, recover_runtime_handle_mut};
255    pub use ocaml_boxroot_sys::boxroot_teardown;
256    pub use ocaml_sys::caml_hash_variant;
257    use std::ffi::CString;
258    use std::sync::OnceLock;
259
260    // To bypass ocaml_sys::int_val unsafe declaration
261    pub fn int_val(val: super::RawOCaml) -> isize {
262        unsafe { ocaml_sys::int_val(val) }
263    }
264
265    // To bypass ocaml_sys::caml_sys_double_val unsafe declaration
266    pub fn float_val(val: super::RawOCaml) -> f64 {
267        unsafe { ocaml_sys::caml_sys_double_val(val) }
268    }
269
270    pub fn int32_val(val: super::RawOCaml) -> i32 {
271        unsafe { crate::mlvalues::int32_val(val) }
272    }
273
274    pub fn int64_val(val: super::RawOCaml) -> i64 {
275        unsafe { crate::mlvalues::int64_val(val) }
276    }
277
278    pub fn alloc_int32(val: i32) -> super::RawOCaml {
279        unsafe { ocaml_sys::caml_copy_int32(val) }
280    }
281
282    pub fn alloc_int64(val: i64) -> super::RawOCaml {
283        unsafe { ocaml_sys::caml_copy_int64(val) }
284    }
285
286    pub fn alloc_float(val: f64) -> super::RawOCaml {
287        unsafe { ocaml_sys::caml_copy_double(val) }
288    }
289
290    pub fn make_ocaml_bool(val: bool) -> super::RawOCaml {
291        unsafe { ocaml_sys::val_int(val as isize) }
292    }
293
294    pub fn make_ocaml_int(val: isize) -> super::RawOCaml {
295        unsafe { ocaml_sys::val_int(val) }
296    }
297
298    // Static storage for the OCaml exception constructor for Rust panics.
299    // This will hold the OCaml value for an exception like `exception RustPanic of string`.
300    static RUST_PANIC_EXCEPTION_CONSTRUCTOR: OnceLock<Option<super::RawOCaml>> = OnceLock::new();
301
302    /// Retrieves the OCaml exception constructor for `RustPanic`.
303    ///
304    /// This function attempts to get a reference to an OCaml exception constructor
305    /// that should be registered from the OCaml side using a name (e.g., "rust_panic_exn").
306    /// Example OCaml registration:
307    /// ```ocaml
308    /// exception RustPanic of string
309    /// let () = Callback.register_exception "rust_panic_exn" (RustPanic "")
310    /// ```
311    /// Returns `None` if the exception is not found (i.e., not registered by the OCaml code).
312    ///
313    /// # Safety
314    /// This function is unsafe because `ocaml_sys::caml_named_value` interacts with the OCaml
315    /// runtime and must be called when the OCaml runtime lock is held and the runtime is initialized.
316    unsafe fn get_rust_panic_exception_constructor() -> Option<super::RawOCaml> {
317        if let Some(constructor_val_opt) = RUST_PANIC_EXCEPTION_CONSTRUCTOR.get() {
318            return *constructor_val_opt;
319        }
320
321        let exn_name_cstr = CString::new("rust_panic_exn").unwrap();
322        let constructor_ptr = ocaml_sys::caml_named_value(exn_name_cstr.as_ptr());
323
324        if constructor_ptr.is_null() {
325            RUST_PANIC_EXCEPTION_CONSTRUCTOR.set(None).ok();
326
327            None
328        } else {
329            let constructor_val = *constructor_ptr;
330
331            RUST_PANIC_EXCEPTION_CONSTRUCTOR
332                .set(Some(constructor_val))
333                .ok();
334
335            Some(constructor_val)
336        }
337    }
338
339    /// # Safety
340    /// This function is intended to be called from the `#[export]` macro when a Rust panic is caught.
341    /// It attempts to raise a custom OCaml exception (e.g., `RustPanic of string`) with the provided message.
342    /// If the custom exception is not registered on the OCaml side, it falls back to `caml_failwith`.
343    /// This function will likely not return to the Rust caller in the traditional sense,
344    /// as it transfers control to the OCaml runtime's exception handling mechanism.
345    /// The OCaml runtime must be initialized, and the current thread must hold the domain lock.
346    pub unsafe fn raise_rust_panic_exception(msg: &str) {
347        let c_msg = CString::new(msg).unwrap_or_else(|_| {
348            CString::new("Rust panic: Invalid message content (e.g., null bytes)").unwrap()
349        });
350
351        match get_rust_panic_exception_constructor() {
352            Some(rust_panic_exn_constructor) => {
353                // Raise the custom OCaml exception `RustPanic "message"`.
354                ocaml_sys::caml_raise_with_string(
355                    rust_panic_exn_constructor,
356                    c_msg.as_ptr() as *const ocaml_sys::Char,
357                );
358            }
359            None => {
360                // Fallback to caml_failwith if the custom exception is not found.
361                ocaml_sys::caml_failwith(c_msg.as_ptr() as *const ocaml_sys::Char);
362            }
363        }
364
365        // caml_raise_with_string or caml_failwith should not return. If they do, it's an issue.
366        std::process::abort(); // As a last resort if OCaml exception raising returns.
367    }
368
369    pub unsafe fn process_panic_payload_and_raise_ocaml_exception(
370        panic_payload: Box<dyn ::std::any::Any + Send>,
371    ) {
372        let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {
373            *s
374        } else if let Some(s) = panic_payload.downcast_ref::<String>() {
375            s.as_str()
376        } else {
377            "Rust panic occurred, but unable to extract panic message."
378        };
379        raise_rust_panic_exception(msg);
380        unreachable!(
381            "raise_rust_panic_exception should have already transferred control or aborted."
382        );
383    }
384}
385
386#[doc(hidden)]
387#[cfg(doctest)]
388pub mod compile_fail_tests;
389
390#[doc(hidden)]
391#[cfg(test)]
392mod compile_ok_tests;