Skip to content

Commit

Permalink
Remove examples from README
Browse files Browse the repository at this point in the history
  • Loading branch information
zshipko committed Jun 3, 2022
1 parent 7dced4a commit 3bb07b1
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 204 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ test-ocaml:
@dune runtest --root=test --force --no-buffer

test-book:
@cargo clean
@cargo build
@mdbook test doc -L ./target/debug/deps

build-book:
Expand Down
207 changes: 6 additions & 201 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ Works with OCaml versions `4.06.0` and up

Please report any issues on [github](https://github.com/zshipko/ocaml-rs/issues)

NOTE: While `ocaml-rs` *can* be used safely, it does not prevent a wide range of potential errors or mistakes. It should be thought of as a Rust implementation of the existing C API. [ocaml-interop](https://github.com/simplestaking/ocaml-interop) can be used to perform safe OCaml/Rust interop. The latest version of `ocaml-rs` actually uses `ocaml-interop` behind the scenes to interact with the garbage collector. `ocaml-rs` also exports an `interop` module, which is an alias for `ocaml_interop` and the two interfaces can be combined if desired.
NOTE: While `ocaml-rs` *can* be used safely, it does not prevent a wide range of potential errors or mistakes. It should be thought of as a Rust implementation of the existing C API. [ocaml-interop](https://github.com/simplestaking/ocaml-interop) can be used to perform safe OCaml/Rust interop. `ocaml-rs` uses `ocaml-interop` behind the scenes to interact with the garbage collector. `ocaml-rs` also exports an `interop` module, which is an alias for `ocaml_interop` and the two interfaces can be combined if desired.

### Documentation

- [ocaml-rs Book](https://zshipko.github.io/ocaml-rs)
- [docs.rs](https://docs.rs/ocaml)

### Getting started

Expand Down Expand Up @@ -68,204 +73,4 @@ Defining the `OCAML_VERSION` and `OCAML_WHERE_PATH` variables is useful for savi
- `no-std`
* Allows `ocaml` to be used in `#![no_std]` environments like MirageOS

### Documentation

[https://docs.rs/ocaml](https://docs.rs/ocaml)

### Examples

```rust
// Automatically derive `ToValue` and `FromValue`
#[derive(ocaml::ToValue, ocaml::FromValue)]
#[ocaml::sig("{name: string; i: int}")]
struct Example<'a> {
name: &'a str,
i: ocaml::Int,
}


#[ocaml::func]
#[ocaml::sig("example -> example")]
pub fn incr_example(mut e: Example) -> Example {
e.i += 1;
e
}

#[ocaml::func]
#[ocaml::sig("int -> int * int * int")]
pub fn build_tuple(i: ocaml::Int) -> (ocaml::Int, ocaml::Int, ocaml::Int) {
(i + 1, i + 2, i + 3)
}

#[ocaml::func]
#[ocaml::sig("float array -> f64")]
pub fn average(arr: ocaml::Array<f64>) -> Result<f64, ocaml::Error> {
let mut sum = 0f64;

for i in 0..arr.len() {
sum += arr.get_f64(i)?;
}

Ok(sum / arr.len() as f64)
}

// A `native_func` must take `ocaml::Value` for every argument and return an `ocaml::Value`
// these functions have minimal overhead compared to wrapping with `func`
#[ocaml::native_func]
pub fn incr(value: ocaml::Value) -> ocaml::Value {
let i = value.int_val();
ocaml::Value::int(i + 1)
}

// This is equivalent to:
#[no_mangle]
pub extern "C" fn incr2(value: ocaml::Value) -> ocaml::Value {
ocaml::body!(gc: (value) {
let i = value.int_val();
ocaml::Value::int( i + 1)
})
}

// `ocaml::native_func` is responsible for:
// - Ensures that #[no_mangle] and extern "C" are added, in addition to wrapping
// - Wraps the function body using `ocaml::body!`

// Finally, if your function is marked [@@unboxed] and [@@noalloc] in OCaml then you can avoid
// boxing altogether for f64 arguments using a plain C function and a bytecode function
// definition:
#[no_mangle]
pub extern "C" fn incrf(input: f64) -> f64 {
input + 1.0
}

#[cfg(feature = "derive")]
#[ocaml::bytecode_func]
pub fn incrf_bytecode(input: f64) -> f64 {
incrf(input)
}
```

Note: By default the `func` macro will create a bytecode wrapper (using `bytecode_func`) for functions with more than 5 arguments.

The OCaml stubs would look like this:

```ocaml
type example = {
name: string;
i: int;
}
external incr_example: example -> example = "incr_example"
external build_tuple: int -> int * int * int = "build_tuple"
external average: float array -> float = "average"
external incr: int -> int = "incr"
external incr2: int -> int = "incr2"
external incrf: float -> float = "incrf_bytecode" "incrf" [@@unboxed] [@@noalloc]
```

Excluding the `incrf` example, these can also be generated using [ocaml-build](https://github.com/zshipko/ocaml-rs/blob/master/build/README.md)

For more examples see [test/src](https://github.com/zshipko/ocaml-rs/blob/master/test/src) or [ocaml-vec](https://github.com/zshipko/ocaml-vec).

### Type conversion

This chart contains the mapping between Rust and OCaml types used by `ocaml::func`

| Rust type | OCaml type |
| ------------------------- | -------------------- |
| `()` | `unit` |
| `isize` | `int` |
| `usize` | `int` |
| `i8` | `int` |
| `u8` | `int` |
| `i16` | `int` |
| `u16` | `int` |
| `i32` | `int32` |
| `u32` | `int32` |
| `i64` | `int64` |
| `u64` | `int64` |
| `f32` | `float` |
| `f64` | `float` |
| `str` | `string` |
| `[u8]` | `bytes` |
| `String` | `string` |
| `Option<A>` | `'a option` |
| `Result<A, ocaml::Error>` | `exception` |
| `Result<A, B>` | `('a, 'b) Result.t` |
| `(A, B, C)` | `'a * 'b * 'c` |
| `&[Value]` | `'a array` (no copy) |
| `Vec<A>`, `&[A]` | `'a array` |
| `BTreeMap<A, B>` | `('a, 'b) list` |
| `LinkedList<A>` | `'a list` |

NOTE: Even though `&[Value]` is specifically marked as no copy, any 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` and `&[u8]` is zero-copy, however `ToValue` for `str` and `&[u8]` creates a new value - this is necessary to ensure the string is registered with the OCaml runtime.

If you're concerned with minimizing allocations/conversions you should use `Value` type directly.

#### Pointers to Rust values on the OCaml heap

`Pointer<T>` can be used to create and access Rust types on the OCaml heap.

For example, for a type that implements `Custom`:

```rust
use ocaml::FromValue;

struct MyType;

unsafe extern "C" fn mytype_finalizer(v: ocaml::Raw) {
let ptr = v.as_pointer::<MyType>();
ptr.drop_in_place()
}

ocaml::custom_finalize!(MyType, mytype_finalizer);

#[ocaml::func]
pub fn new_my_type() -> ocaml::Pointer<MyType> {
ocaml::Pointer::alloc_custom(gc, MyType)
// ocaml::Pointer::alloc_final(gc, MyType, finalizer, None) can also be used
// if you don't intend to implement `Custom`
}

#[ocaml::func]
pub fn my_type_example(t: ocaml::Pointer<MyType>) {
let my_type = t.as_mut();
// MyType has no fields, but normally you
// would do something with MyType here
}
```

#### Custom exception type

When a Rust `panic` or `Err` is encountered it will be raised as a `Failure` on the OCaml side, to configure a custom exception type you can register it with the OCaml runtime using the name `Rust_exception`:

```ocaml
exception Rust
let () = Callback.register_exception "Rust_error" (Rust "")
```

It must take a single `string` argument.

#### Using `import!` macro

The `ocaml::import!` macro can be used to call OCaml functions that have been registered using `Callback.register` from Rust:

```rust
ocaml::import! {
fn my_function(i: ocaml::Int) -> ocaml::Int;
}
```

Which can then be called directly:

```rust
my_function(&rt, 123)
```

### `ocaml::sig` macro

The `ocaml::sig` macro in combination with `ocaml-build` can be used to write signatures for OCaml functions and types in-line.

See [build/README.md](https://github.com/zshipko/ocaml-rs/blob/master/build/README.md)

35 changes: 35 additions & 0 deletions doc/src/01_introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Additionally, on macOS you may need to add a `.cargo/config` with the following:
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"]
```

This is because macOS doesn't allow undefined symbols in dynamic libraries by default.

If you plan on using `ocaml-build`:

```toml
Expand Down Expand Up @@ -67,8 +69,41 @@ Next you will need to add setup a [dune](https://dune.build) project to handle c

It can take a little trial and error to get this right depending on the specifics of your project!

Additionally, if you plan on releasing to [opam](https://github.com/ocaml/opam), you will need to vendor your Rust dependencies to avoid making network requests during the build phase, since reaching out to crates.io/github will be blocked by the opam sandbox. To do this you should run:

```shell
cargo vendor
```

then follow the instructions for editing `.cargo/config`


To simplify the full setup process, take a look at [ocaml-rust-starter](https://github.com/zshipko/ocaml-rust-starter).

## Build options

By default, building `ocaml-sys` will invoke the `ocamlopt` command to figure out the version and location of the OCaml compiler. There are a few environment variables to control this.

- `OCAMLOPT` (default: `ocamlopt`) is the command that will invoke `ocamlopt`
- `OCAML_VERSION` (default: result of `$OCAMLOPT -version`) is the target runtime OCaml version.
- `OCAML_WHERE_PATH` (default: result of `$OCAMLOPT -where`) is the path of the OCaml standard library.
- `OCAML_INTEROP_NO_CAML_STARTUP` (default: unset) can be set when loading an `ocaml-rs` library into an OCaml
bytecode runtime (such as `utop`) to avoid linking issues with `caml_startup`

If both `OCAML_VERSION` and `OCAML_WHERE_PATH` are present, their values are used without invoking `ocamlopt`. If any of those two env variables is undefined, then `ocamlopt` will be invoked to obtain both values.

Defining the `OCAML_VERSION` and `OCAML_WHERE_PATH` variables is useful for saving time in CI environments where an OCaml install is not really required (to run `clippy` for example).

### Features

- `derive`
* enabled by default, adds `#[ocaml::func]` and friends and `derive` implementations for `FromValue` and `ToValue`
- `link`
* link the native OCaml runtime, this should only be used when no OCaml code will be linked statically
- `no-std`
* Allows `ocaml` to be used in `#![no_std]` environments like MirageOS


## Writing your first `ocaml::func`

`ocaml::func` is the highest-level macro that can be used to generate OCaml functions. It's built on `ocaml::native_func` which only works on `Value` parameters and `ocaml::bytecode_func` which is used for generating bytecode functions. `ocaml::func` will take care of generating bytecode bindings for functions with more than five parameters as required by the OCaml runtime. `ocaml::func` handles using `CAMLparam`/`CAMLlocal`/`CAMLreturn` correctly for you, often making it much easier to write bindings than using the C API directly, particularly for those who haven't used the OCaml C API before.
Expand Down
5 changes: 5 additions & 0 deletions doc/src/02_type_conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Below is a list of types that implement these traits in `ocaml-rs` and their cor
| `BTreeMap<A, B>` | `('a, 'b) list` |
| `LinkedList<A>` | `'a list` |

NOTE: Even though `&[Value]` is specifically marked as no copy, any 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` and `&[u8]` is zero-copy, however `ToValue` for `str` and `&[u8]` creates a new value - this is necessary to ensure the string is registered with the OCaml runtime.

If you're concerned with minimizing allocations/conversions you should use `Value` type directly.

## Implementing `ToValue` and `FromValue`

Expand Down Expand Up @@ -63,6 +66,8 @@ This can also be accomplished using the derive macros:
pub struct MyType(i32);
```

`derive(ToValue, FromValue)` will work on any struct or enum that are comprised of types that also implement `ToValue` and `FromValue`

## Types that work directly on OCaml values

There are several types that work directly on OCaml values, these don't perform any copies when converting to and from `Value`.
Expand Down
Loading

0 comments on commit 3bb07b1

Please sign in to comment.