fp_bindgen/
lib.rs

1/*!
2# fp-bindgen
3
4[![Crates.io](https://img.shields.io/crates/v/fp-bindgen.svg)](https://crates.io/crates/fp-bindgen)
5[![Discord Shield](https://discordapp.com/api/guilds/950489382626951178/widget.png?style=shield)](https://discord.gg/fAt2xgMSGS)
6
7Bindings generator for full-stack WASM plugins.
8
9## Comparison to other "bindgen" tools
10
11`fp-bindgen` is not the only tool for generating Wasm bindings. The most well-known tool for this
12is probably `wasm-bindgen`, though it is limited to Rust modules running inside browser
13environments. A more generic alternative, based on the Wasm
14[interface types proposal](https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md),
15is `wit-bindgen`. We do believe interface types to be the future of Wasm bindings, but for the
16short-term, `fp-bindgen` provides bindings that work with a stable serialization format, which helps
17us to avoid versioning issues and opens up compatibility with tools such as Serde.
18
19It is worth mentioning that, though we have a [specification](#specification) for our communication
20primitives that allows generators for other languages to be contributed, `fp-bindgen` is opinionated
21towards Rust. It uses Rust data structures and function signatures as its "protocol format",
22enabling tight integration with existing crates from the Rust ecosystem.
23
24The following table is intended to highlight the major differences between the different tools:
25
26| Feature                                                   |         `fp-bindgen`        | `wasm-bindgen` |         `wit-bindgen`           |
27| --------------------------------------------------------- | :-------------------------: | :------------: | :-----------------------------: |
28| Host environments                                         | Rust (Wasmer), TypeScript\* |     JS/TS      | Rust/Python (Wasmtime), JS/TS\* |
29| Guest languages                                           |            Rust\*           |      Rust      |           Rust, C\*             |
30| Protocol format                                           |     Rust (using macros)     |      N/A       |              .wit               |
31| Serialization format                                      |         MessagePack         |      JSON      |             Custom              |
32| [Can use existing Rust types](#using-existing-rust-types) |           ✅           |    ❌    |            ❌             |
33
34\*) These are only the _currently supported_ options. More may be added in the future.
35
36## Quickstart
37
38* Check out the repository, using `git clone`. We use symlinks in the repo, so on Windows use
39  `git clone -c core.symlinks=true` instead.
40* To quickly build an example protocol and plugin and run all available tests use:
41  `cargo xtask test`
42
43## Usage
44
45Using `fp-bindgen` is a three-step process:
46
47- First you [define a protocol](#defining-a-protocol) that specifies the functions and data
48  structures available for communication across the Wasm bridge.
49- Then you [generate the bindings](#generating-bindings) for the hosts and plugin language that are
50  relevant to you.
51- Finally, you can start [implementing plugins and runtimes](#using-the-bindings) using the
52  generated bindings.
53
54## Defining a protocol
55
56Before you can generate bindings using this library, you first define a protocol of functions that
57can be called by the _runtime_ (the Wasm host) and functions that can be called by the _plugin_ (the
58Wasm guest module). The protocol specifies the function declarations, which are placed inside two
59macros: `fp_import!` and `fp_export!`. These macros specify which functions can be imported and
60which can be exported, _from the perspective of the plugin_. In other words, `fp_import!` functions
61can be called by the plugin and must be implemented by the runtime, while `fp_export!` functions can
62be called by the runtime and _may_ be implemented by the plugin.
63
64**Example:**
65
66```ignore
67fp_bindgen::prelude::fp_import! {
68    fn my_imported_function(a: u32, b: u32) -> u32;
69}
70
71fp_bindgen::prelude::fp_export! {
72    fn my_exported_function(a: u32, b: u32) -> u32;
73}
74```
75
76**Important caveat:** There must be exactly one `fp_import!` block and one `fp_export!` block in the
77same module as where you invoke `fp_bindgen!()`. If you only have imports, or only have exports, you
78should create an empty block for the other.
79
80### Data structures
81
82Besides primitives, functions can pass Rust `struct`s and `enum`s as their arguments and return
83value, but only by value (passing a reference across the Wasm bridge is currently not supported) and
84only for types that implement `Serializable`.
85
86**Example:**
87
88```ignore
89#[derive(fp_bindgen::prelude::Serializable)]
90pub struct MyStruct {
91    pub foo: i32,
92    pub bar: String,
93}
94
95fp_bindgen::prelude::fp_import! {
96    fn my_function(data: MyStruct) -> MyStruct;
97}
98```
99
100Note that `Serializable` is implemented by default for some common standard types, such as
101`Option`, `Vec`, and other container types.
102
103### Async functions
104
105Functions can also be `async`, which works as you would expect:
106
107**Example:**
108
109```ignore
110fp_bindgen::prelude::fp_import! {
111    async fn my_async_function(data: MyStruct) -> Result<MyStruct, MyError>;
112}
113```
114
115### Using existing Rust types
116
117Sometimes you may wish to use Rust types for your protocol that you also want to use directly in the
118generated runtime or plugin implementation. In such a case, generation of the data types might force
119you to perform unnecessary copies, so we allow explicit annotations to import the existing
120definition instead of generating a new one:
121
122**Example:**
123
124```
125use fp_bindgen::prelude::Serializable;
126
127#[derive(Serializable)]
128#[fp(rust_module = "my_crate::prelude")]
129pub struct MyStruct {
130    pub foo: i32,
131    pub bar_qux: String,
132}
133```
134
135In this example, `MyStruct` has a double function: it acts both as a type definition for the
136protocol (through `fp-bindgen`'s `Serializable` trait), which can still be used for generating a
137TypeScript type definition, for instance. _And_ it acts as a type that can be directly used by the
138Rust Wasmer runtime, under the assumption the runtime can import it from `my_crate::prelude`.
139
140Please note that in this case, you do have a bigger responsibility to make sure the definition
141fulfills the requirements of the code generator, hence why Serde's trait derives and annotations
142have to be added manually here, in accordance with how the generator would otherwise generate them.
143
144For now, this feature is limited to the Rust generators through the
145`rust_module` annotation. For us, this makes sense given the
146protocol itself is specified using Rust syntax as well. If desired, we could extend this to the
147TypeScript generator as well, though that would imply an even bigger responsibility for the user to
148keep their TypeScript types in sync with the protocol.
149
150### Cargo features
151
152The `fp-bindgen` crate supports optional Cargo features for compatibility with some common types
153from the crate ecosystem:
154
155- `bytes-compat`: Enables compatibility with the `bytes::Bytes` type.
156- `http-compat`: Enables compatibility with various types from the `http` crate.
157- `rmpv-compat`: Enables compatibility with the `rmpv::Value` type.
158- `serde-bytes-compat`: Enables compatibility with the `serde_bytes::ByteBuf` type (the `Bytes` type
159  is a reference type, which `fp-bindgen` doesn't support in general).
160- `serde-json-compat`: Enables compatibility with `serde_json::Map` and `serde_json::Value` types.
161- `time-compat`: Enables compatibility with `time`'s `PrimitiveDateTime` and `OffsetDateTime` types.
162
163## Generating bindings
164
165To generate bindings based on your protocol, you first need to create a function that will generate
166them for you. Creating this function is easy, because its implementation can be created for you
167using the `fp_bindgen` macro:
168
169```ignore
170let bindings_type = fp_bindgen::BindingsType::RustWasmerRuntime;
171
172fp_bindgen::prelude::fp_bindgen!(fp_bindgen::BindingConfig {
173    bindings_type,
174    path: &format!("bindings/{}", bindings_type)
175});
176```
177
178Currently, we support the following binding types:
179
180- `BindingsType::RustPlugin`: Generates bindings for a Rust plugin.
181- `BindingsType::RustWasmerRuntime`: Generates runtime bindings for use with Wasmer.
182- `BindingsType::TsRuntimeWithExtendedConfig`: Generates bindings for a TypeScript runtime.
183
184Note that some binding types take an additional config argument.
185
186## Using the bindings
187
188How to use the generated bindings differs between the various types.
189
190### Using the Rust plugin bindings
191
192The generator for our Rust plugin bindings generates a complete crate that allows to be linked
193against by plugins. The plugin can import all the functions from the `fp_import!` block from it,
194and call them like any other functions.
195
196In order to export the functions that are defined in the `fp_export!` block, it can use the exported
197`fp_export_impl` macro, like so:
198
199```ignore
200#[fp_bindgen_macros::fp_export_impl(bindings_crate_path)]
201fn my_exported_function(a: u32, b: u32) -> u32 {
202    /* ... */
203}
204```
205
206`bindings_crate_path` is expected to match with the module path from which the bindings crate
207itself is imported. The function signature must match exactly with one of the `fp_export!`
208functions.
209
210When compiling a plugin, don't forget to compile against the "wasm32-unknown-unknown" target, or you
211will receive linker errors.
212
213See the `example-plugin/` directory for an example of a plugin that uses bindings generated from
214our `example-protocol/` (do note this plugin only builds after you've run `cargo run` inside the
215`example-protocol/` directory).
216
217### Using the Rust Wasmer runtime bindings
218
219The generator for our Rust Wasmer runtime works a bit differently. Instead of generating a crate,
220it generates two files: `bindings.rs` and `types.rs`. These can be placed in a module of your
221choosing (we chose a module named `spec` in the `example-rust-runtime/`).
222
223As the implementor of the runtime, it is then your responsibility to implement the `fp_import!`
224functions within the same module as you've placed the generated files. You can see an example of
225this in `example-rust-runtime/spec/mod.rs` (do note the example runtime only builds after you've run
226`cargo run` inside the `example-protocol/` directory).
227
228Finally, the `bindings.rs` file contains a constructor (`Runtime::new()`) that you can use to
229instantiate Wasmer runtimes with the Wasm module provided as a blob. The `fp_export!` functions are
230provided on the `Runtime` instance as methods. Please be aware that implementation of the
231`fp_export!` functions is always at the discretion of the plugin, and an attempt to invoke a missing
232implementation can fail with an `InvocationError::FunctionNotExported` error.
233
234### Using the TypeScript runtime bindings
235
236The TypeScript runtime generator can work with browsers, Node.js and Deno.
237
238It works similarly to that for the Wasmer runtime, but it generates an `index.ts` and a `types.ts`.
239`types.ts` contains the type definitions for all the data structures, while the `index.ts` exports a
240`createRuntime()` function that you can use for instantiating the runtime. Upon instantiation, you
241are expected to provide implementations for all the `fp_import!` functions, while the returned
242`Promise` will give you an object with all the `fp_export!` functions the provided plugin has
243implemented.
244
245## Examples
246
247Please have a look at [`examples/README.md`](examples/README.md) for various examples on how to use
248`fp-bindgen`.
249
250## Specification
251
252We have written down a specification that describes the primitives used by our bindings. This is
253aimed primarily at those that want to understand how the bindings work under the hood, and may be
254valuable if you want to implement bindings for your own favorite language.
255
256If that is you, please have a look at [`docs/SPEC.md`](docs/SPEC.md).
257
258## Known Limitations
259
260- Data types may only contain value types. References are currently unsupported.
261- Referencing types using their full module path is prone to cause mismatches during type
262  discovery. Please import types using a `use` statement and refer to them by their name only.
263- TypeScript bindings handle 64-bit integers somewhat inconsistently. When passed as primitives (as
264  plain function arguments or return values) they will be encoded using the `BigInt` type. But when
265  they're part of a MessagePack-encoded data type, they will be encoded using `number`, which
266  effectively limits them to a maximum size of `2^53 - 1`. For more information, see:
267  <https://github.com/msgpack/msgpack-javascript/issues/115>
268
269## FAQ
270
271### I added a `Serializable` derive to my type, why don't I see it included in the bindings?
272
273Are you using the type in one of the `fp_import!` or `fp_export!` functions? Deriving `Serializable`
274makes it possible to use the type as part of your protocol, but it won't become part of the
275generated bindings until it is actually referenced. Note that types can be either referenced
276directly by one of the `fp_import!` or `fp_export!` functions, or indirectly by another type that is
277already in use.
278
279If a type is not referenced either directly or indirectly by any of the functions that are part of
280your protocol, you can force inclusion by adding a `use` statement referencing the type to either
281the `fp_import!` or `fp_export!` section:
282
283```ignore
284fp_bindgen::prelude::fp_import! {
285    use MyType;
286}
287```
288
289Are you referencing the type and it is still not included in your bindings? Please
290[file an issue](https://github.com/fiberplane/fp-bindgen/issues).
291
292### Can I use aliases?
293
294Yes, but because aliases cannot have a derive macro, please repeat the alias in either the
295`fp_import!` or `fp_export!` section:
296
297```ignore
298fp_bindgen::prelude::fp_import! {
299  type MyType = SomeOtherType;
300}
301```
302
303### What about versioning?
304
305Generally, versioning is considered out-of-scope for this project. This means it is your own
306responsibility to verify a plugin you execute was compiled against a compatible version of the
307protocol your runtime provides.
308
309If your protocol ever needs to introduce breaking changes, we advise to include a `version() -> u32`
310export function in the protocol itself that you can call before invoking any other functions.
311
312As for what constitutes a breaking change, we offer the following guidelines:
313
314- All plugin exports are always optional. Because of this, new exports can always be added without
315  breaking existing plugins, unless your runtime performs an explicit check that mandates an
316  export's existence.
317- Adding new imports is always safe, as they will simply be ignored by existing plugins.
318- Adding fields to `struct`s is always safe, unless your runtime mandates the existence of such
319  fields in arguments or return values coming from the plugin.
320- Adding new types is always safe.
321- **Anything else should be considered a breaking change.**
322
323Note that, because of the above guidelines, you should never need to define a versioning function in
324your first iteration. Because plugin exports are optional, the absense of a versioning function can
325simply be interpreted as meaning the plugin is at version 1.
326
327## Getting Help
328
329Please see
330[COMMUNITY.md](https://github.com/fiberplane/fiberplane-rs/blob/main/COMMUNITY.md)
331for ways to reach out to us.
332
333## Contributing
334
335Please follow our [Contributing Guidelines](CONTRIBUTING.md) to learn how best
336to contribute to this project.
337
338## Code of Conduct
339
340See
341[CODE_OF_CONDUCT.md](https://github.com/fiberplane/fiberplane-rs/blob/main/CODE_OF_CONDUCT.md).
342
343## License
344
345This project is distributed under the terms of both the MIT license and the
346Apache License (Version 2.0).
347
348See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT).
349
350*/
351
352mod casing;
353mod docs;
354mod functions;
355#[cfg(feature = "generators")]
356mod generators;
357mod serializable;
358
359pub mod prelude;
360pub mod primitives;
361pub mod types;
362mod utils;
363
364use fp_bindgen_macros::primitive_impls;
365use prelude::*;
366
367primitive_impls!();
368
369#[cfg(feature = "generators")]
370pub use generators::{
371    generate_bindings, BindingConfig, BindingsType, RustPluginConfig, RustPluginConfigValue,
372    TsRuntimeConfig,
373};