6 releases
0.1.5 | Jun 21, 2023 |
---|---|
0.1.4 | Dec 16, 2022 |
0.1.0 | Sep 12, 2022 |
#120 in Programming languages
759 downloads per month
27KB
530 lines
OCaml-gen
This crate provides automatic generation of OCaml bindings. Refer to the rustdoc for more information.
Example
Here's an example of generating some bindings. Create a main.rs
as:
use ocaml_gen::prelude::*;
// Some Rust code you have:
#[derive(ocaml::FromValue, ocaml::IntoValue, ocaml_gen::CustomType)]
pub struct SomeType {
pub a: u8,
}
#[ocaml_gen::func]
#[ocaml::func]
fn create_some_type() -> SomeType {
SomeType { a: 42 }
}
fn main() {
// initialize your environment
let env = &mut Env::default();
// choose where you want to write the bindings
let w = &mut std::io::stdout();
// you can declare modules
decl_module!(w, env, "Types", {
// and types
decl_type!(w, env, SomeType => "t");
});
decl_module!(w, env, "Functions", {
// and functions
decl_func!(w, env, create_some_type => "create");
});
}
Note that the underlying function imported by decl_func!
is actually caml_of_numeral_to_ocaml
, which is created by the annotated macro ocaml_gen::func
.
So either your function is in scope, or you import everything (e.g. use path::*
), or you import the derived function directly (e.g. use path::caml_of_numeral_to_ocaml
).
The OCaml bindings generated should look like this:
module Types = struct
type nonrec t
end
module Functions = struct
val create : unit -> Types.t
end
Usage
In general, you can use this library by following these steps:
- annotate your types and functions with
ocaml-rs
andocaml_gen
macros. - create a
main.rs
file to generate your binding filebindings.ml
as shown above. - use the
ocaml-rs
crate to export the static library that OCaml will use. - add a
dune
file to your crate to build the Rust static library as well as the OCaml bindings. (protip: use(mode promote)
in the dune file to have the resulting binding file live within the folder.) - optionally enforce in CI that the promoted binding file is correct.
You can see an example of these steps in the test/ folder. (Although we don't "promote" the file with dune for testing purposes.)
Annotations
To allow ocaml-gen to understand how to generate OCaml bindings from your types and functions, you must annotate them using ocaml-gen's macros.
To allow generation of bindings on structs, use ocaml_gen::Struct
:
#[ocaml_gen::Struct]
struct MyType {
// ...
}
To allow generation of bindings on enums, use ocaml_gen::Enum
:
#[ocaml_gen::Enum]
enum MyType {
// ...
}
To allow generation of bindings on functions, use ocaml_gen::func
:
#[ocaml_gen::func]
#[ocaml::func] // if you use the crate ocaml-rs' macro, it must appear after
pub fn your_function(arg1: String) {
//...
}
To allow generation of bindings on custom types, use ocaml_gen::CustomType
:
#[ocaml_gen::CustomType]
struct MyCustomType {
// ...
}
Binding generations
To generate bindings, you must create a main.rs
file that uses the ocaml_gen
crate functions to layout what the bindings .ml
file will look like.
The first thing to do is to import the types and functions you want to generate bindings for, as well as the ocaml_gen
macros:
use ocaml_gen::prelude::*;
use your_crate::*;
You can then use decl_module!
to declare modules:
let env = &mut Env::default();
let w = &mut std::io::stdout();
decl_module!(w, env, "T1", {
decl_module!(w, env, "T2", {
decl_type!(w, env, SomeType);
});
});
decl_module!(w, env, "T3", {
decl_type!(w, env, SomeOtherType);
});
You can rename types and functions by simply adding an arrow:
decl_type!(w, env, SomeType => "t");
You can also declare generic types by first declaring the generic type parameters (that you must reuse for all generic types):
decl_fake_generic!(T1, 0); // danger:
decl_fake_generic!(T2, 1); // make sure you
decl_fake_generic!(T3, 2); // increment these correctly
decl_type!(w, env, TypeWithOneGenericType<T1>);
decl_type!(w, env, ThisOneHasTwo<T1, T2>);
decl_type!(w, env, ThisOneHasThreeOfThem<T1, T2, T3>);
You can also create type aliases with the decl_type_alias!
macro but it is highly experimental.
It has a number of issues:
- the alias doesn't work outside of the module it is declared current scope (which is usually what you want)
- the alias is ignoring the instantiation of type parameters. This means that it might rename
Thing<usize>
tot1
, eventhought1
was an alias toThing<String>
(this is the main danger, see this tracking issue) - it won't work (binding generation will throw an error) if you try to alias two instantiations of the same generic type (for example,
t1
is the alias ofThing<usize>
andt2
is the alias ofThing<String>
)
Dependencies
~3MB
~60K SLoC