ocaml_interop/conv/
to_ocaml.rs

1// Copyright (c) Viable Systems and TezEdge Contributors
2// SPDX-License-Identifier: MIT
3
4use core::{borrow::Borrow, str};
5
6use ocaml_sys::{caml_alloc_float_array, caml_sys_store_double_field};
7
8use crate::{
9    internal::{caml_alloc, store_field},
10    memory::{
11        alloc_bigarray1, alloc_bytes, alloc_cons, alloc_double, alloc_error, alloc_int32,
12        alloc_int64, alloc_ok, alloc_some, alloc_string, alloc_tuple, store_raw_field_at, OCamlRef,
13    },
14    mlvalues::{
15        bigarray::{Array1, BigarrayElt},
16        OCamlBytes, OCamlFloat, OCamlInt, OCamlInt32, OCamlInt64, OCamlList, RawOCaml, FALSE, NONE,
17        TRUE,
18    },
19    runtime::OCamlRuntime,
20    value::OCaml,
21    BoxRoot, OCamlFloatArray, OCamlUniformArray,
22};
23
24/// Implements conversion from Rust values into OCaml values.
25///
26/// # Safety
27///
28/// Implementing this trait involves unsafe code that interacts with the OCaml runtime.
29/// Implementors must ensure the following to uphold Rust's safety guarantees:
30///
31/// - **Memory Safety**: Returned OCaml values must be valid and correctly represent
32///   the memory layout expected by the OCaml runtime. Any misrepresentation can lead
33///   to undefined behavior, potentially causing segmentation faults or data corruption.
34///
35/// - **Handling of OCaml Exceptions**: If the OCaml code can raise exceptions, the implementor
36///   must ensure these are appropriately handled. Uncaught OCaml exceptions should not be allowed
37///   to propagate into the Rust code, as they are not compatible with Rust's error handling mechanisms.
38///
39/// Implementors of this trait must have a deep understanding of both Rust's and OCaml's
40/// memory models, garbage collection, and runtime behaviors to ensure safe interoperability.
41pub unsafe trait ToOCaml<T> {
42    /// Convert to OCaml value. Return an already rooted value as [`BoxRoot`]`<T>`.
43    fn to_boxroot(&self, cr: &mut OCamlRuntime) -> BoxRoot<T> {
44        BoxRoot::new(self.to_ocaml(cr))
45    }
46
47    /// Convert to OCaml value.
48    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, T>;
49}
50
51unsafe impl<T> ToOCaml<T> for OCamlRef<'_, T> {
52    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, T> {
53        unsafe { OCaml::new(cr, self.get_raw()) }
54    }
55}
56
57unsafe impl<T> ToOCaml<T> for BoxRoot<T> {
58    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, T> {
59        self.get(cr)
60    }
61}
62
63unsafe impl ToOCaml<()> for () {
64    fn to_ocaml(&self, _cr: &mut OCamlRuntime) -> OCaml<'static, ()> {
65        OCaml::unit()
66    }
67}
68
69unsafe impl ToOCaml<OCamlInt> for i64 {
70    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlInt> {
71        unsafe { OCaml::new(cr, ((self << 1) | 1i64) as RawOCaml) }
72    }
73}
74
75unsafe impl ToOCaml<OCamlInt> for i32 {
76    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlInt> {
77        (*self as i64).to_ocaml(cr)
78    }
79}
80
81unsafe impl ToOCaml<OCamlInt32> for i32 {
82    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlInt32> {
83        alloc_int32(cr, *self)
84    }
85}
86
87unsafe impl ToOCaml<OCamlInt64> for i64 {
88    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlInt64> {
89        alloc_int64(cr, *self)
90    }
91}
92
93unsafe impl ToOCaml<OCamlFloat> for f64 {
94    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlFloat> {
95        alloc_double(cr, *self)
96    }
97}
98
99unsafe impl ToOCaml<bool> for bool {
100    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, bool> {
101        unsafe { OCaml::new(cr, if *self { TRUE } else { FALSE }) }
102    }
103}
104
105// TODO: figure out how to implement all this without so much duplication
106// it is not as simple as implementing for Borrow<str/[u8]> because
107// of the Box<T> implementation bellow, which causes a trait implementation
108// conflict.
109
110unsafe impl ToOCaml<String> for &str {
111    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, String> {
112        alloc_string(cr, self)
113    }
114}
115
116unsafe impl ToOCaml<OCamlBytes> for &str {
117    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlBytes> {
118        alloc_bytes(cr, self.as_bytes())
119    }
120}
121
122unsafe impl ToOCaml<OCamlBytes> for &[u8] {
123    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlBytes> {
124        alloc_bytes(cr, self)
125    }
126}
127
128unsafe impl ToOCaml<String> for &[u8] {
129    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, String> {
130        alloc_string(cr, unsafe { str::from_utf8_unchecked(self) })
131    }
132}
133
134unsafe impl ToOCaml<String> for String {
135    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, String> {
136        self.as_str().to_ocaml(cr)
137    }
138}
139
140unsafe impl ToOCaml<OCamlBytes> for String {
141    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlBytes> {
142        self.as_str().to_ocaml(cr)
143    }
144}
145
146unsafe impl ToOCaml<String> for Vec<u8> {
147    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, String> {
148        self.as_slice().to_ocaml(cr)
149    }
150}
151
152unsafe impl ToOCaml<OCamlBytes> for Vec<u8> {
153    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlBytes> {
154        self.as_slice().to_ocaml(cr)
155    }
156}
157
158unsafe impl ToOCaml<OCamlBytes> for Box<[u8]> {
159    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlBytes> {
160        let slice: &[u8] = self;
161        slice.to_ocaml(cr)
162    }
163}
164
165unsafe impl ToOCaml<Array1<u8>> for Box<[u8]> {
166    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, Array1<u8>> {
167        let slice: &[u8] = self;
168        slice.to_ocaml(cr)
169    }
170}
171
172unsafe impl<A, OCamlA> ToOCaml<OCamlA> for Box<A>
173where
174    A: ToOCaml<OCamlA>,
175{
176    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlA> {
177        self.as_ref().to_ocaml(cr)
178    }
179}
180
181unsafe impl<A, OCamlA: 'static> ToOCaml<Option<OCamlA>> for Option<A>
182where
183    A: ToOCaml<OCamlA>,
184{
185    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, Option<OCamlA>> {
186        if let Some(value) = self {
187            let ocaml_value = value.to_boxroot(cr);
188            alloc_some(cr, &ocaml_value)
189        } else {
190            unsafe { OCaml::new(cr, NONE) }
191        }
192    }
193}
194
195unsafe impl<A, OCamlA: 'static, Err, OCamlErr: 'static> ToOCaml<Result<OCamlA, OCamlErr>>
196    for Result<A, Err>
197where
198    A: ToOCaml<OCamlA>,
199    Err: ToOCaml<OCamlErr>,
200{
201    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, Result<OCamlA, OCamlErr>> {
202        match self {
203            Ok(value) => {
204                let ocaml_value = value.to_boxroot(cr);
205                alloc_ok(cr, &ocaml_value)
206            }
207            Err(error) => {
208                let ocaml_error = error.to_boxroot(cr);
209                alloc_error(cr, &ocaml_error)
210            }
211        }
212    }
213}
214
215unsafe impl<A, OCamlA: 'static> ToOCaml<OCamlList<OCamlA>> for Vec<A>
216where
217    A: ToOCaml<OCamlA>,
218{
219    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlList<OCamlA>> {
220        let mut result = BoxRoot::new(OCaml::nil(cr));
221        for elt in self.iter().rev() {
222            let ov = elt.to_boxroot(cr);
223            let cons = alloc_cons(cr, &ov, &result);
224            result.keep(cons);
225        }
226        cr.get(&result)
227    }
228}
229
230unsafe impl<A, OCamlA: 'static> ToOCaml<OCamlUniformArray<OCamlA>> for Vec<A>
231where
232    A: ToOCaml<OCamlA>,
233{
234    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlUniformArray<OCamlA>> {
235        let result = BoxRoot::new(unsafe { OCaml::new(cr, caml_alloc(self.len(), 0)) });
236
237        for (i, elt) in self.iter().enumerate() {
238            let ov = elt.to_ocaml(cr);
239            unsafe { store_field(result.get_raw(), i, ov.raw()) };
240        }
241
242        result.get(cr)
243    }
244}
245
246unsafe impl ToOCaml<OCamlFloatArray> for Vec<f64> {
247    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlFloatArray> {
248        let result = unsafe { OCaml::new(cr, caml_alloc_float_array(self.len())) };
249
250        for (i, elt) in self.iter().enumerate() {
251            unsafe { caml_sys_store_double_field(result.raw(), i, *elt) };
252        }
253
254        result
255    }
256}
257
258// Tuples
259
260macro_rules! tuple_to_ocaml {
261    ($($n:tt: $t:ident => $ot:ident),+) => {
262        unsafe impl<$($t),+, $($ot: 'static),+> ToOCaml<($($ot),+)> for ($($t),+)
263        where
264            $($t: ToOCaml<$ot>),+
265        {
266            fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, ($($ot),+)> {
267                let len = $crate::count_fields!($($t)*);
268
269                    let ocaml_tuple: BoxRoot<($($ot),+)> = BoxRoot::new(unsafe { alloc_tuple(cr, len) });
270                    $(
271                        unsafe {
272                            let field_val = self.$n.to_ocaml(cr).get_raw();
273                            store_raw_field_at(cr, &ocaml_tuple, $n, field_val);
274                        }
275                    )+
276
277                    cr.get(&ocaml_tuple)
278            }
279        }
280    };
281}
282
283tuple_to_ocaml!(
284    0: A => OCamlA,
285    1: B => OCamlB);
286tuple_to_ocaml!(
287    0: A => OCamlA,
288    1: B => OCamlB,
289    2: C => OCamlC);
290tuple_to_ocaml!(
291    0: A => OCamlA,
292    1: B => OCamlB,
293    2: C => OCamlC,
294    3: D => OCamlD);
295tuple_to_ocaml!(
296    0: A => OCamlA,
297    1: B => OCamlB,
298    2: C => OCamlC,
299    3: D => OCamlD,
300    4: E => OCamlE);
301tuple_to_ocaml!(
302    0: A => OCamlA,
303    1: B => OCamlB,
304    2: C => OCamlC,
305    3: D => OCamlD,
306    4: E => OCamlE,
307    5: F => OCamlF);
308tuple_to_ocaml!(
309    0: A => OCamlA,
310    1: B => OCamlB,
311    2: C => OCamlC,
312    3: D => OCamlD,
313    4: E => OCamlE,
314    5: F => OCamlF,
315    6: G => OCamlG);
316tuple_to_ocaml!(
317    0: A => OCamlA,
318    1: B => OCamlB,
319    2: C => OCamlC,
320    3: D => OCamlD,
321    4: E => OCamlE,
322    5: F => OCamlF,
323    6: G => OCamlG,
324    7: H => OCamlH);
325tuple_to_ocaml!(
326    0: A => OCamlA,
327    1: B => OCamlB,
328    2: C => OCamlC,
329    3: D => OCamlD,
330    4: E => OCamlE,
331    5: F => OCamlF,
332    6: G => OCamlG,
333    7: H => OCamlH,
334    8: I => OCamlI);
335tuple_to_ocaml!(
336    0: A => OCamlA,
337    1: B => OCamlB,
338    2: C => OCamlC,
339    3: D => OCamlD,
340    4: E => OCamlE,
341    5: F => OCamlF,
342    6: G => OCamlG,
343    7: H => OCamlH,
344    8: I => OCamlI,
345    9: J => OCamlJ);
346
347// This copies
348unsafe impl<A: BigarrayElt> ToOCaml<Array1<A>> for &[A] {
349    fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, Array1<A>> {
350        alloc_bigarray1(cr, self)
351    }
352}
353
354// Note: we deliberately don't implement FromOCaml<Array1<A>>,
355// because this trait doesn't have a lifetime parameter
356// and implementing would force a copy.
357impl<A: BigarrayElt> Borrow<[A]> for OCaml<'_, Array1<A>> {
358    fn borrow(&self) -> &[A] {
359        unsafe {
360            let ba = self.custom_ptr_val::<ocaml_sys::bigarray::Bigarray>();
361            core::slice::from_raw_parts((*ba).data as *const A, self.len())
362        }
363    }
364}