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;