Skip to content

Commit

Permalink
Updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
Lupus committed Oct 12, 2024
1 parent 3fef529 commit 5b40d07
Showing 1 changed file with 244 additions and 34 deletions.
278 changes: 244 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,25 @@

## Overview

This project provides a smart pointer implementation for `ocaml-rs`, allowing for safe interaction between Rust and OCaml. The library includes various utilities and extensions for generating OCaml bindings, handling OCaml functions, and managing OCaml values in Rust.

## Features

- **OCaml Function Wrappers**: Safe wrappers around OCaml functions.
- **Type Registration**: Mechanisms to register Rust types and traits for use in OCaml.
- **Callable Trait**: A trait representing functions or closures that can be called with a set of arguments to produce a return value.
- **Type Name Utilities**: Helpers for extracting and converting type names.
This project provides a smart pointer implementation for `ocaml-rs`, allowing
for safe interaction between Rust and OCaml. The library includes various
utilities and extensions for generating OCaml bindings, handling OCaml
functions, and managing OCaml values in Rust.

## Modules

### `src/func.rs`
### `src/ptr.rs`

- **OCamlFunc**: A wrapper around `MlBox` representing an OCaml function. It ensures safe calls from Rust.
- **OCamlDesc Implementation**: Provides OCaml type descriptions for functions.
- **DynBox**: A smart pointer type for safe and flexible interop between OCaml and Rust.

### `src/type_name.rs`
### `src/ml_box.rs`

- **extract_type_name**: Extracts the core type name from a type string.
- **capture_segments**: Captures segments of a type string until the core type.
- **convert_to_snake_case**: Converts a module path to snake_case.
- **capitalize_first_letter**: Capitalizes the first letter of a string.
- **get_type_name**: Returns the core type name.
- **snake_case_of_fully_qualified_name**: Converts a fully qualified name to snake_case with the first letter capitalized.
- **MlBox**: A wrapper around `ocaml::Value` that allows to safely pass it between threads from Rust.

### `src/callable.rs`
### `src/func.rs`

- **Callable Trait**: Represents a function or closure that can be called with a set of arguments to produce a return value.
- **Macro Implementations**: Macros to generate the `call_with` function for tuples of different sizes.
- **OCamlFunc**: A wrapper around `MlBox` representing an OCaml function. It ensures safe calls from Rust.
- **OCamlDesc Implementation**: Provides OCaml type descriptions for functions.

### `src/ocaml_gen_extras.rs`

Expand All @@ -43,6 +33,90 @@ This project provides a smart pointer implementation for `ocaml-rs`, allowing fo

## Usage

### Write Rust bindings to OCaml

Our bindings would rely on wonderful crates [ocaml-rs](https://github.com/zshipko/ocaml-rs) and [ocaml-gen](https://github.com/o1-labs/ocaml-gen).

```rust
// Bindings use object-safe part of animals::Animal
// see test/src/stubs.rs for complete sources
pub type Animal = dyn AnimalProxy + Send;

#[ocaml_gen::func]
#[ocaml::func]
pub fn animal_name(animal: DynBox<Animal>) -> String {
let animal = animal.coerce();
animal.name()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn animal_noise(animal: DynBox<Animal>) -> String {
let animal = animal.coerce();
animal.noise()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn animal_talk(animal: DynBox<Animal>) {
let animal = animal.coerce();
animal.talk()
}

// Sheep bindings
pub type Sheep = animals::Sheep;

#[ocaml_gen::func]
#[ocaml::func]
pub fn sheep_create(name: String) -> DynBox<Sheep> {
let sheep: Sheep = animals::Animal::new(name);
sheep.into()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn sheep_is_naked(sheep: DynBox<Sheep>) -> bool {
let sheep = sheep.coerce();
sheep.is_naked()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn sheep_sheer(sheep: DynBox<Sheep>) {
let mut sheep = sheep.coerce_mut();
sheep.shear()
}

// Wolf bindings
pub type Wolf = animals::Wolf;

#[ocaml_gen::func]
#[ocaml::func]
pub fn wolf_create(name: String) -> DynBox<Wolf> {
let wolf: Wolf = animals::Animal::new(name);
wolf.into()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn wolf_set_hungry(wolf: DynBox<Wolf>, hungry: bool) {
let mut wolf = wolf.coerce_mut();
wolf.set_hungry(hungry);
}

// OCamlFunc bindings

#[ocaml_gen::func]
#[ocaml::func]
pub fn call_cb(
wolf: DynBox<Wolf>,
cb: OCamlFunc<(DynBox<Wolf>,), DynBox<Animal>>,
) -> DynBox<Animal> {
let res = cb.call(gc, (wolf,));
res
}
```

### Registering Types and Traits

Use the provided macros to register types and traits for OCaml:
Expand Down Expand Up @@ -72,9 +146,17 @@ register_rtti! {
}
```

### Generating OCaml Bindings
`register_trait` registeres an object-safe trait within the type registry, along
with all its combinations when "multiplied" by marker traits.

`register_type` registeres type, and coercions from that type to combinations of object-safe traits, "multiplied" by marker traits.

All this is required to force Rust to generate vtables and record convertion
functions between original type and a combination of traits.

Use the `ocaml_gen_bindings` macro to generate OCaml bindings:
### Declare OCaml Bindings

Use the `ocaml_gen_bindings` macro to declare OCaml bindings:

```rust
ocaml_gen_bindings! {
Expand Down Expand Up @@ -104,20 +186,148 @@ ocaml_gen_bindings! {
}
```

## Test Project
`ocaml_gen_bindings` declares more convenient aliases for
`ocaml_gen::decl_module` & co without extra boilerplate params (writeable string
and environment are managed by `ocaml_gen_bindings` internally). `DynBox<T>`
supports `ocaml_gen` infrastructure as long as `T` supports it.

### Generating OCaml bindings

You need a binary like this to generate the bindings:

```rust
#[allow(clippy::single_component_path_imports)]
#[allow(unused_imports)]
use ocaml_rs_smartptr_test;

fn main() -> std::io::Result<()> {
ocaml_rs_smartptr::ocaml_gen_extras::stubs_gen_main()
}
```

Unused import is required for Cargo/Rust to actually link in the
`ocaml_rs_smartptr_test` library, allowing the plugin system (on top of
`inventory` crate) to register itself.

You can run this binary from a dune rule:

```
(rule
(alias runtest)
(targets Ocaml_rs_smartptr_test.ml)
(deps stubs-gen.rs)
(locks cargo-build)
(action
(run cargo run --offline --package stubs-gen --bin stubs-gen)))
```

This binary will generate one .ml file for each crate that declared the bindings
(and was linked in...).

### How bindings look like

DynBox and type registration allows to expose some information about what traits
certain types in Rust implement down to OCaml side, encoding those constraints
with polymorphic variants:

```ocaml
module Animal = struct
type nonrec t =
[ `Ocaml_rs_smartptr_test_stubs_animal_proxy | `Core_marker_send ]
Ocaml_rs_smartptr.Rusty_obj.t
A test project is included to demonstrate the usage of the `ocaml-rs-smartptr` library. It includes examples of creating and manipulating Rust objects in OCaml, coercing `Sheep` and `Wolf` to `Animal`, and interacting with these objects through the `Animal` trait methods.
external name : t -> string = "animal_name"
external noise : t -> string = "animal_noise"
external talk : t -> unit = "animal_talk"
end
### Structure
module Sheep = struct
type nonrec t =
[ `Ocaml_rs_smartptr_test_stubs_sheep
| `Core_marker_sync
| `Core_marker_send
| `Ocaml_rs_smartptr_test_stubs_animal_proxy
]
Ocaml_rs_smartptr.Rusty_obj.t
- **Rust Code**:
- `src/animals.rs`: Defines the `Animal` trait and its implementations for `Sheep` and `Wolf`.
- `src/stubs.rs`: Provides stubs for OCaml (`extern "C"` functions) and registers the types and traits.
external create : string -> t = "sheep_create"
external is_naked : t -> bool = "sheep_is_naked"
external sheer : t -> unit = "sheep_sheer"
end
- **OCaml Code**:
- `test/Stubs.ml`: Defines OCaml modules and external functions for `Animal`, `Sheep`, and `Wolf`.
- `test/test.ml`: Contains test cases for `Sheep` and `Wolf`, demonstrating their creation, coercion to `Animal`, and interaction through the `Animal` trait methods.
module Wolf = struct
type nonrec t =
[ `Ocaml_rs_smartptr_test_stubs_wolf
| `Core_marker_sync
| `Core_marker_send
| `Ocaml_rs_smartptr_test_stubs_animal_proxy
]
Ocaml_rs_smartptr.Rusty_obj.t
external create : string -> t = "wolf_create"
external set_hungry : t -> bool -> unit = "wolf_set_hungry"
end
module Test_callback = struct
external call_cb : Wolf.t -> (Wolf.t -> Animal.t) -> Animal.t = "call_cb"
end
```

### Using the generated OCaml bindings

Using the binginds in OCaml is pretty straightforward:

```ocaml
open Stubs
let sheep_test () =
print_endline "\n*** Sheep test";
let sheep = Sheep.create "dolly" in
Animal.talk (sheep :> Animal.t);
Sheep.sheer sheep;
Animal.talk (sheep :> Animal.t)
;;
let wolf_test () =
print_endline "\n*** Wolf test";
let wolf = Wolf.create "big bad wolf" in
Animal.talk (wolf :> Animal.t);
let animal =
Test_callback.call_cb wolf (fun wolf ->
print_endline "(wolf gets modified inside a callback!)";
Wolf.set_hungry wolf true;
(wolf :> Animal.t))
in
Animal.talk animal
;;
let main () =
sheep_test ();
wolf_test ()
;;
let () = main ()
```

Important note: when your project relies on DynBox type registry, it is
important to depend on `ocaml-rs-smartptr` OCaml library:

```
(library
(name test_lib)
(libraries ocaml-rs-smartptr))
```

Type registration is also decentralized and is based on `inventory` crate, so
during program initialization, all type conversions need to be registered. This
is done in `ocaml-rs-smartptr` OCaml library, which is linked with `-linkall`
flag, so initialization code will run whenever you link to this library.

## Test Project

## License
A test project is included to demonstrate the usage of the `ocaml-rs-smartptr`
library. It includes examples of creating and manipulating Rust objects in
OCaml, coercing `Sheep` and `Wolf` to `Animal`, and interacting with these
objects through the `Animal` trait methods.

This project is licensed under the MIT License.
You can find it in [test subdirectory](./test)

0 comments on commit 5b40d07

Please sign in to comment.