|
4 | 4 | <img src="https://img.shields.io/crates/v/ocaml.svg">
|
5 | 5 | </a>
|
6 | 6 |
|
7 |
| -**Note:** `ocaml-rs` is still experimental, please report any issues on [github](https://github.com/zshipko/ocaml-rs/issues) |
8 |
| - |
9 |
| -`ocaml-rs` allows for OCaml extensions to be written directly in Rust with no C stubs. It was forked from [raml](https://crates.io/crates/raml) with the goal of creating a safer, high-level interface. |
| 7 | +`ocaml-rs` allows for OCaml extensions to be written directly in Rust with no C stubs. It was originally forked from [raml](https://crates.io/crates/raml), but has been almost entirely re-written thanks to support from the [OCaml Software Foundation](http://ocaml-sf.org/). |
10 | 8 |
|
11 | 9 | Works with OCaml versions `4.06.0` and up
|
12 | 10 |
|
13 |
| -### Documentation |
14 |
| - |
15 |
| -[https://docs.rs/ocaml](https://docs.rs/ocaml) |
| 11 | +Please report any issues on [github](https://github.com/zshipko/ocaml-rs/issues) |
16 | 12 |
|
17 |
| -### Examples: |
| 13 | +### Getting started |
18 | 14 |
|
19 |
| -```rust |
20 |
| -use ocaml::*; |
| 15 | +**OCaml**: |
21 | 16 |
|
22 |
| -caml!(build_tuple(i) { |
23 |
| - let i = i.val_i32(); |
24 |
| - Tuple::from(&[i + 1, i + 2, i + 3]) |
25 |
| -}); |
| 17 | +Take a look at [example/src/dune](https://github.com/zshipko/ocaml-rs/blob/master/example/src/dune) for an example `dune` file to get you started. |
26 | 18 |
|
27 |
| -caml!(average(arr) { |
28 |
| - let arr = Array::from(arr); |
29 |
| - let len = arr.len(); |
30 |
| - let sum = 0f64; |
| 19 | +**Rust** |
31 | 20 |
|
32 |
| - for i in 0..len { |
33 |
| - sum += arr.get_double_unchecked(i); |
34 |
| - } |
| 21 | +Typically just include: |
35 | 22 |
|
36 |
| - Value::f64(sum / len as f64) |
37 |
| -}) |
| 23 | +```toml |
| 24 | +ocaml = ... |
38 | 25 | ```
|
39 | 26 |
|
40 |
| -This will take care of all the OCaml garbage collector related bookkeeping (CAMLparam, CAMLlocal and CAMLreturn). |
| 27 | +in your `Cargo.toml`. |
41 | 28 |
|
42 |
| -In OCaml the stubs for these functions looks like this: |
43 | 29 |
|
44 |
| -```ocaml |
45 |
| -external build_tuple: int -> int * int * int = "build_tuple" |
46 |
| -external average: float array -> float = "average" |
| 30 | +On macOS you will need also to add the following to your project's `.cargo/config` file: |
| 31 | + |
| 32 | +```toml |
| 33 | +[build] |
| 34 | +rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"] |
47 | 35 | ```
|
48 | 36 |
|
49 |
| -For more examples see [./example](https://github.com/zshipko/ocaml-rs/blob/master/example) or [ocaml-vec](https://github.com/zshipko/ocaml-vec). |
| 37 | +This is because macOS doesn't allow undefined symbols in dynamic libraries by default. |
50 | 38 |
|
51 |
| -### `caml!` macro |
| 39 | +### Documentation |
52 | 40 |
|
53 |
| -The old style `caml!` macro has been replaced with a much simpler new format. |
| 41 | +[https://docs.rs/ocaml](https://docs.rs/ocaml) |
54 | 42 |
|
55 |
| -Instead of: |
| 43 | +### Examples |
56 | 44 |
|
57 | 45 | ```rust
|
58 |
| -caml!(function_name, |a, b, c|, <local> { |
59 |
| - ... |
60 |
| -} -> local); |
| 46 | +// Automatically derive `ToValue` and `FromValue` |
| 47 | +#[derive(ocaml::ToValue, ocaml::FromValue)] |
| 48 | +struct Example<'a> { |
| 49 | + name: &'a str, |
| 50 | + i: ocaml::Int, |
| 51 | +} |
| 52 | + |
| 53 | + |
| 54 | +#[ocaml::func] |
| 55 | +pub fn incr_example(mut e: Example) -> Example { |
| 56 | + e.i += 1; |
| 57 | + e |
| 58 | +} |
| 59 | + |
| 60 | +#[ocaml::func] |
| 61 | +pub fn build_tuple(i: ocaml::Int) -> (ocaml::Int, ocaml::Int, ocaml::Int) { |
| 62 | + (i + 1, i + 2, i + 3) |
| 63 | +} |
| 64 | + |
| 65 | +#[ocaml::func] |
| 66 | +pub fn average(arr: ocaml::Array<f64>) -> Result<f64, ocaml::Error> { |
| 67 | + let mut sum = 0f64; |
| 68 | + |
| 69 | + for i in 0..arr.len() { |
| 70 | + sum += arr.get_double(i)?; |
| 71 | + } |
| 72 | + |
| 73 | + Ok(sum / arr.len() as f64) |
| 74 | +} |
| 75 | + |
| 76 | +// A `native_func` must take `ocaml::Value` for every argument and return an `ocaml::Value` |
| 77 | +// these functions have minimal overhead compared to wrapping with `func` |
| 78 | +#[ocaml::native_func] |
| 79 | +pub fn incr(value: ocaml::Value) -> ocaml::Value { |
| 80 | + let i = value.int_val(); |
| 81 | + Value::int(i + 1) |
| 82 | +} |
| 83 | + |
| 84 | +// This is equivalent to: |
| 85 | +#[no_mangle] |
| 86 | +pub extern "C" fn incr2(value: ocaml::Value) -> ocaml::Value { |
| 87 | + ocaml::body!((value) { |
| 88 | + let i = value.int_val(); |
| 89 | + ocaml::Value::int( i + 1) |
| 90 | + }) |
| 91 | +} |
| 92 | + |
| 93 | +// `ocaml::native_func` is responsible for: |
| 94 | +// - Ensures that #[no_mangle] and extern "C" are added, in addition to wrapping |
| 95 | +// - Wraps the function body using `ocaml::body!` |
| 96 | + |
| 97 | +// Finally, if your function is marked [@@unboxed] and [@@noalloc] in OCaml then you can avoid |
| 98 | +// boxing altogether for f64 arguments using a plain C function and a bytecode function |
| 99 | +// definition: |
| 100 | +#[no_mangle] |
| 101 | +pub extern "C" fn incrf(input: f64) -> f64 { |
| 102 | + input + 1.0 |
| 103 | +} |
| 104 | + |
| 105 | +#[cfg(feature = "derive")] |
| 106 | +#[ocaml::bytecode_func] |
| 107 | +pub fn incrf_bytecode(input: f64) -> f64 { |
| 108 | + incrf(input) |
| 109 | +} |
61 | 110 | ```
|
62 | 111 |
|
63 |
| -you can now write: |
| 112 | +The OCaml stubs would look like this: |
64 | 113 |
|
65 |
| -```rust |
66 |
| -caml!(function_name(a, b, c) { |
67 |
| - caml_local!(local); |
68 |
| - ... |
69 |
| - return local; |
70 |
| -}); |
| 114 | +```ocaml |
| 115 | +type example = { |
| 116 | + name: string; |
| 117 | + i: int; |
| 118 | +} |
| 119 | +
|
| 120 | +external incr_example: example -> example = "incr_example" |
| 121 | +external build_tuple: int -> int * int * int = "build_tuple" |
| 122 | +external average: float array -> float = "average" |
| 123 | +external incr: int -> int = "incr" |
| 124 | +external incr2: int -> int = "incr2" |
| 125 | +external incrf: float -> float = "incrf_bytecode" "incrf" [@@unboxed] [@@noalloc] |
71 | 126 | ```
|
72 | 127 |
|
73 |
| -However, when using the type wrappers provided by `ocaml-rs` (`Array`, `List`, `Tuple`, `Str`, `Array1`, ...), `caml_local!` is already called internally. This means that the following is valid without having to declare a local value for the result: |
| 128 | +For more examples see [./example](https://github.com/zshipko/ocaml-rs/blob/master/example) or [ocaml-vec](https://github.com/zshipko/ocaml-vec) for an example project using `ocaml-rs`. |
| 129 | + |
| 130 | +### Type conversion |
| 131 | + |
| 132 | +This chart contains the mapping between Rust and OCaml types used by `ocaml::func` |
| 133 | + |
| 134 | +| Rust type | OCaml type | |
| 135 | +| ---------------- | -------------------- | |
| 136 | +| `()` | `unit` | |
| 137 | +| `isize` | `int` | |
| 138 | +| `usize` | `int` | |
| 139 | +| `i8` | `int` | |
| 140 | +| `u8` | `int` | |
| 141 | +| `i16` | `int` | |
| 142 | +| `u16` | `int` | |
| 143 | +| `i32` | `int32` | |
| 144 | +| `u32` | `int32` | |
| 145 | +| `i64` | `int64` | |
| 146 | +| `u64` | `int64` | |
| 147 | +| `f32` | `float` | |
| 148 | +| `f64` | `float` | |
| 149 | +| `str` | `string` | |
| 150 | +| `String` | `string` | |
| 151 | +| `Option<A>` | `'a option` | |
| 152 | +| `Result<A, B>` | `exception` | |
| 153 | +| `(A, B, C)` | `'a * 'b * 'c` | |
| 154 | +| `&[Value]` | `'a array` (no copy) | |
| 155 | +| `Vec<A>`, `&[A]` | `'a array` | |
| 156 | +| `BTreeMap<A, B>` | `('a, 'b) list` | |
| 157 | +| `LinkedList<A>` | `'a list` | |
| 158 | + |
| 159 | +Even though `&[Value]` is specifically marked as no copy, a type like `Option<Value>` would also qualify since the inner value is not converted to a Rust type. However, `Option<String>` will do full unmarshaling into Rust types. Another thing to note: `FromValue` for `str` is zero-copy, however `ToValue` for `str` creates a new value - this is necessary to ensure the string lives long enough. |
| 160 | + |
| 161 | +If you're concerned with minimizing allocations/conversions you should prefer to use `Value` type directly. |
| 162 | + |
| 163 | +## Upgrading |
| 164 | + |
| 165 | +Since 0.10 and later have a much different API compared to earlier version, here is are some major differences that should be considered when upgrading: |
| 166 | + |
| 167 | +- `FromValue` and `ToValue` have been marked `unsafe` because converting OCaml values to Rust and back also depends on the OCaml type signature. |
| 168 | + * A possible solution to this would be a `cbindgen` like tool that generates the correct OCaml types from the Rust code |
| 169 | +- `ToValue` now takes ownership of the value being converted |
| 170 | +- The `caml!` macro has been rewritten as a procedural macro called `ocaml::func`, which performs automatic type conversion |
| 171 | + * `ocaml::native_func` and `ocaml::bytecode_func` were also added to create functions at a slightly lower level |
| 172 | + * `derive` feature required |
| 173 | +- Added `derive` implementations for `ToValue` and `FromValue` for stucts and enums |
| 174 | + * `derive` feature required |
| 175 | +- `i32` and `u32` now map to OCaml's `int32` type rather than the `int` type |
| 176 | + * Use `ocaml::Int`/`ocaml::Uint` to refer to the OCaml's `int` types now |
| 177 | +- `Array` and `List` now take generic types |
| 178 | +- Strings are converted to `str` or `String`, rather than using the `Str` type |
| 179 | +- Tuples are converted to Rust tuples (up to 20 items), rather than using the `Tuple` type |
| 180 | +- The `core` module has been renamed to `sys` and is now just an alias for the `ocaml-sys` crate |
74 | 181 |
|
75 |
| -```rust |
76 |
| -caml!(example(a, b, c){ |
77 |
| - List::from(&[a, b, c]) |
78 |
| -}); |
79 |
| -``` |
|
0 commit comments