Skip to content

Commit 9e941f1

Browse files
authored
Major overhaul, work toward 1.0 release (#19)
* Convert more macros into functions * Add ocaml::func proc-macro * Put derive behind feature * Split out ocaml-sys * Tuple, FromValue return types * Cleanup Array/List implementations * Fix example on macOS * update * More examples * Work API consistency * Move named_value to Value::named * Get example/test working on stable * update * named_value should return a const pointer * More api cleanup * update * Add version to path deps * update ci * ci * ci/windows * Remove windows for now * format * Add example using ocaml::body * Fix readme example * bare_func -> native_func, add bytecode_func * Handle functions with more than 5 parameters * Make ToValue/FromValue unsafe traits * Fix stable toolchain * Fix caml_frame to match other macros * Remove ocaml_frame, move it to ocaml::frame * Prevent mixing OCaml func macros * unify function validation * Add hash_variant test * Add custom values * Add more documentation for custom stuff * Add Pointer::alloc_custom * update * clippy * Work on making OCaml exceptions a little safer * Add caml_main * Cleanup custom params * Fix Value -> LinkedList * Provide inner and outer options for custom macro * docs * Allow any error type in return values * Address issues with registering global roots * More clarifications * Fix commas in custom macro * update * Make ToValue take ownership * readme * Naming consistency * Fix custom pointer issue * update * Explain core -> sys rename * update * Fix string argument types * Convert ocaml-sys to no_std
1 parent f0393ae commit 9e941f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3261
-1849
lines changed

.clippy.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
single-char-binding-names-threshold = 50

.github/workflows/clippy.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Clippy
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
clippy:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
15+
- name: Nightly
16+
run: rustup toolchain install nightly --profile=default
17+
18+
- name: Run tests
19+
run: cargo +nightly clippy --all

.github/workflows/test.yml

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
name: ocaml-rs build
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
28

3-
on: [push, pull_request]
49
jobs:
510
run:
611
name: Build
712
runs-on: ${{ matrix.os }}
813
strategy:
9-
fail-fast: false
14+
fail-fast: true
1015
matrix:
11-
os: [macos-latest, ubuntu-latest, windows-latest]
12-
ocaml-version: ["4.10.0", "4.09.0", "4.08.1", "4.07.0", "4.06.0"]
16+
os: [macos-latest, ubuntu-latest]
17+
ocaml-version: ["4.10.0", "4.09.1", "4.08.1", "4.07.0", "4.06.0"]
1318
steps:
1419
- name: Checkout code
1520
uses: actions/[email protected]
@@ -18,7 +23,8 @@ jobs:
1823
with:
1924
ocaml-version: ${{ matrix.ocaml-version }}
2025
- run: opam install dune
26+
- run: opam exec -- cargo clean
2127
- name: Build
22-
run: cargo build --test
28+
run: opam exec -- cargo build --tests
2329
- name: Run tests
24-
run: make test
30+
run: opam exec -- make test

Cargo.toml

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ocaml"
3-
version = "0.9.3"
3+
version = "0.10.0"
44
authors = ["Zach Shipko <[email protected]>"]
55
readme = "README.md"
66
keywords = ["ocaml", "rust", "ffi"]
@@ -11,3 +11,17 @@ documentation = "https://docs.rs/ocaml"
1111
edition = "2018"
1212

1313
[dependencies]
14+
ocaml-sys = {path = "./sys"} # , version = "0.10"
15+
ocaml-derive = {path = "./derive", optional = true} # version = "0.10"
16+
17+
[features]
18+
default = ["derive"]
19+
derive = ["ocaml-derive"]
20+
deep-clone = []
21+
22+
[workspace]
23+
members = [
24+
"derive",
25+
"sys",
26+
"example"
27+
]

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
test:
22
@cargo test
3-
@cd example && dune clean && dune exec bin/main.exe
3+
@cd example && dune clean && dune runtest
4+
5+
utop:
6+
@cargo test
7+
@cd example && dune clean && dune utop
48

59
clean:
610
cargo clean

README.md

+149-47
Original file line numberDiff line numberDiff line change
@@ -4,76 +4,178 @@
44
<img src="https://img.shields.io/crates/v/ocaml.svg">
55
</a>
66

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/).
108

119
Works with OCaml versions `4.06.0` and up
1210

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)
1612

17-
### Examples:
13+
### Getting started
1814

19-
```rust
20-
use ocaml::*;
15+
**OCaml**:
2116

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.
2618

27-
caml!(average(arr) {
28-
let arr = Array::from(arr);
29-
let len = arr.len();
30-
let sum = 0f64;
19+
**Rust**
3120

32-
for i in 0..len {
33-
sum += arr.get_double_unchecked(i);
34-
}
21+
Typically just include:
3522

36-
Value::f64(sum / len as f64)
37-
})
23+
```toml
24+
ocaml = ...
3825
```
3926

40-
This will take care of all the OCaml garbage collector related bookkeeping (CAMLparam, CAMLlocal and CAMLreturn).
27+
in your `Cargo.toml`.
4128

42-
In OCaml the stubs for these functions looks like this:
4329

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"]
4735
```
4836

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.
5038

51-
### `caml!` macro
39+
### Documentation
5240

53-
The old style `caml!` macro has been replaced with a much simpler new format.
41+
[https://docs.rs/ocaml](https://docs.rs/ocaml)
5442

55-
Instead of:
43+
### Examples
5644

5745
```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+
}
61110
```
62111

63-
you can now write:
112+
The OCaml stubs would look like this:
64113

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]
71126
```
72127

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
74181

75-
```rust
76-
caml!(example(a, b, c){
77-
List::from(&[a, b, c])
78-
});
79-
```

derive/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "ocaml-derive"
3+
version = "0.10.0"
4+
authors = ["Zach Shipko <[email protected]>"]
5+
edition = "2018"
6+
license = "ISC"
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
syn = {version = "1", features = ["full"]}
13+
proc-macro2 = {version = "1"}
14+
synstructure = "0.12"
15+
quote = "1"

derive/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# ocaml-derive
2+
3+
Provides `ocaml_func`, `ocaml_bare_func`, `derive(ToValue)` and `derive(FromValue)`
4+
5+
- `src/lib.rs` contains definitions `ocaml_func` and `ocaml_bare_func`
6+
- `src/derive.rs` is forked from [rust-ocaml-derive](https://github.com/rust-ocaml-derive) to remain in parity with `ocaml-rs`

0 commit comments

Comments
 (0)