Skip to content

Commit a0c7383

Browse files
authored
rust: Revert back to historical way of exporting functions (bytecodealliance#871)
* rust: Revert back to historical way of exporting functions This commit is unfortunately a bit of whiplash for Rust users as it transitions the `wit_bindgen::generate!` macro to what it was a few months ago in terms of how exports work. Before describing that, though, let's first motivate this commit. Today exports are not easy to work with in the Rust generator. To be clear this is no one's fault (either that or it's mostly mine), it's just the best we could think of at the time to get resources working. Whenever the `generate!` macro is invoked it's required to specify all exports to the macro via an `exports: { ... }` map. This is required for exported resources so generated bindings know what types to refer to. The problems with this approach are: * You can't generate bindings ahead-of-time, bindings can only be generated when everything is "done". This means crates like `wasi` have to do weird things to bind exports and it wouldn't even work if there were exported resource types. * There's a circular nature between user code and generated code. Generated code uses user types, but user types also use generated code. While this can work it has a very high risk of generating confusing error messages that are difficult to debug. The upside of today's `exports` approach is that it works! These downsides were well-known before they were implemented, but at the time we couldn't think of anything better. Yesterday, however, I had an epiphany that I think we can do better. This PR removes all the `exports: { ... }` bits entirely. Instead a completely different system is now in place for managing the exports of a crate. This system is basically the same as what everything was before `exports: { ... }` with a few cosmetic tweaks: 1. The `generate!` macro generates everything but does not export anything. This means that from a build process perspective `generate!` should be a noop. (e.g. it can be gc'd away) 2. The `generate!` macro does not refer to any user-defined types, so it should be a little silo which is much nicer from a composability point of view. 3. Resource exports still have concrete types generated. Generated bindings look very similar to before. The main difference is that instead of `OwnT` for `own<T>` exports and `&MyT` for `borrow<T>` exports they generate `T` and `TBorrow<'_>` where both of those types are bindings-generated. 4. The `generate!` macro itself generates a macro: `export_{name}!`. Here `{name}` is based on the world being bound. This macro is what actually exports an implementation and generates `#[no_mangle]` functions. Overall this is basically what we had before (if I'm remembering correctly). The cosmetic tweaks come in the implementation. For example the `export_*!` macro has two forms instead of one: export_foo!(MyWorldImplementation); // .. is the same as ... export_foo!(MyWorldImplementation with_types_in self); Here the second form solves the problem where `export_foo!` doesn't know the crate path back to itself, so it's optionally specified after `with_types_in`. By default this is `self` meaning that it's expected that `export_foo!` is located adjacent to the call to `generate!`. This can be configured though with a new Rust macro option `default_bindings_module: "..."`. Additionally the `export_foo!` macro is not exported from the crate by default, but that can also be configured with `pub_export_macros: true` now. Included in this commit are other changes such as: * Improved documentation for `generate!`, including documenting the state of the world after this commit. * A new test for cross-crate behavior of the `generate!` macro. * The Rust macro `export` option and `--export` CLI flag are both removed. * Generated bindings for exported resource types are different and require manual method calls like `.get()` with an annotation of what the destination type is. * Guests may need to implement more traits now as an `interface` with `resource`s but no functions must be implemented to configured resource types as associated types. * The `WasmResource` trait, `Resource<T>` type, and `RustResource` trait are all now purely internal to the bindings and shouldn't show up in the public API. * A new `default_bindings_module` option configures the default location to find where bindings are generated in the `export_*!` macro. * A new `pub_export_macros` option configures whether macros are exported out of the current crate. And finally, a good way to see the impact of this change is to browse the changes in the `tests/runtime/*` directory to all the `wasm.rs` files. * Flag new crate as not-published * Apply `#[doc(hidden)]` to internal methods * Add more documentation for the `wit-bindgen` Rust crate * Add a README * Add documentation to get rendered on docs.rs with pre-expanded versions of WIT documents. * Change the default export macro * Name it `export!` instead of `export_{name}!` * Add an option to configure the name. * Try to fix tests * Fix example build * Fix typo * Add some resources to the xcrate-test
1 parent 4a2bf63 commit a0c7383

File tree

52 files changed

+2000
-869
lines changed

Some content is hidden

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

52 files changed

+2000
-869
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ jobs:
137137
- run: cargo build --no-default-features --features csharp
138138
- run: cargo build --no-default-features --features markdown
139139

140+
# Verity that documentation can be generated for the rust bindings crate.
141+
- run: cargo doc -p wit-bindgen --no-deps
142+
env:
143+
RUSTDOCFLAGS: --cfg=docsrs
144+
140145
rustfmt:
141146
name: Rustfmt
142147
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/guest-rust/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<div align="center">
2+
<h1><code>wit-bindgen</code></h1>
3+
4+
<p>
5+
<strong>Guest Rust language bindings generator for
6+
<a href="https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md">WIT</a>
7+
and the
8+
<a href="https://github.com/WebAssembly/component-model">Component Model</a>
9+
</strong>
10+
</p>
11+
12+
<strong>A <a href="https://bytecodealliance.org/">Bytecode Alliance</a> project</strong>
13+
14+
<p>
15+
<img src="https://img.shields.io/badge/rustc-stable+-green.svg" alt="supported rustc stable" />
16+
<a href="https://docs.rs/wit-bindgen"><img src="https://docs.rs/wit-bindgen/badge.svg" alt="Documentation Status" /></a>
17+
</p>
18+
</div>
19+
20+
# About
21+
22+
This crate provides a macro, [`generate!`], to automatically generate Rust
23+
bindings for a [WIT] [world]. For more information about this crate see the
24+
[online documentation] which includes some examples and longer form reference
25+
documentation as well.
26+
27+
This crate is developed as a portion of the [`wit-bindgen` repository] which
28+
also contains a CLI and generators for other languages.
29+
30+
[`generate!`]: https://docs.rs/wit-bindgen/latest/wit_bindgen/macro.generate.html
31+
[WIT]: https://component-model.bytecodealliance.org/design/wit.html
32+
[world]: https://component-model.bytecodealliance.org/design/worlds.html
33+
[online documentation]: https://docs.rs/wit-bindgen
34+
[`wit-bindgen` repository]: https://github.com/bytecodealliance/wit-bindgen
35+
36+
# License
37+
38+
This project is licensed under the Apache 2.0 license with the LLVM exception.
39+
See [LICENSE](LICENSE) for more details.
40+
41+
### Contribution
42+
43+
Unless you explicitly state otherwise, any contribution intentionally submitted
44+
for inclusion in this project by you, as defined in the Apache-2.0 license,
45+
shall be licensed as above, without any additional terms or conditions.

crates/guest-rust/src/examples.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Examples of output of the [`generate!`] macro.
2+
//!
3+
//! This module is only included in docs.rs documentation and is not present in
4+
//! the actual crate when compiling from crates.io. The purpose of this module
5+
//! is to showcase what the output of the [`generate!`] macro looks like.
6+
//!
7+
//! [`generate!`]: crate::generate
8+
9+
/// An example of generated bindings for top-level imported functions and
10+
/// interfaces into a world.
11+
///
12+
/// The code used to generate this module is:
13+
///
14+
/// ```rust
15+
#[doc = include_str!("./examples/_0_world_imports.rs")]
16+
/// ```
17+
pub mod _0_world_imports;
18+
19+
/// An example of importing interfaces into a world.
20+
///
21+
/// The code used to generate this module is:
22+
///
23+
/// ```rust
24+
#[doc = include_str!("./examples/_1_interface_imports.rs")]
25+
/// ```
26+
pub mod _1_interface_imports;
27+
28+
/// An example of importing resources into a world.
29+
///
30+
/// The code used to generate this module is:
31+
///
32+
/// ```rust
33+
#[doc = include_str!("./examples/_2_imported_resources.rs")]
34+
/// ```
35+
pub mod _2_imported_resources;
36+
37+
/// An example of exporting items from a world and the traits that they
38+
/// generate.
39+
///
40+
/// The code used to generate this module is:
41+
///
42+
/// ```rust
43+
#[doc = include_str!("./examples/_3_world_exports.rs")]
44+
/// ```
45+
pub mod _3_world_exports;
46+
47+
/// An example of exporting resources from a world and the traits that they
48+
/// generate.
49+
///
50+
/// The code used to generate this module is:
51+
///
52+
/// ```rust
53+
#[doc = include_str!("./examples/_4_exported_resources.rs")]
54+
/// ```
55+
pub mod _4_exported_resources;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
crate::generate!({
2+
inline: r#"
3+
package example:world-imports;
4+
5+
world with-imports {
6+
/// Fetch a greeting to present.
7+
import greet: func() -> string;
8+
9+
/// Log a message to the host.
10+
import log: func(msg: string);
11+
12+
import my-custom-host: interface {
13+
tick: func();
14+
}
15+
}
16+
"#,
17+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
crate::generate!({
2+
inline: r#"
3+
package example:interface-imports;
4+
5+
interface logging {
6+
enum level {
7+
debug,
8+
info,
9+
warn,
10+
error,
11+
}
12+
13+
log: func(level: level, msg: string);
14+
}
15+
16+
world with-imports {
17+
// Local interfaces can be imported.
18+
import logging;
19+
20+
// Dependencies can also be referenced, and they're loaded from the
21+
// `path` directive specified below.
22+
import wasi:cli/[email protected];
23+
}
24+
"#,
25+
26+
27+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
crate::generate!({
2+
inline: r#"
3+
package example:imported-resources;
4+
5+
world import-some-resources {
6+
enum level {
7+
debug,
8+
info,
9+
warn,
10+
error,
11+
}
12+
resource logger {
13+
constructor(max-level: level);
14+
15+
get-max-level: func() -> level;
16+
set-max-level: func(level: level);
17+
18+
log: func(level: level, msg: string);
19+
}
20+
}
21+
"#,
22+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
crate::generate!({
2+
inline: r#"
3+
package example:world-exports;
4+
5+
world with-exports {
6+
import log: func(msg: string);
7+
8+
export run: func();
9+
10+
/// An example of exporting an interface inline naming it directly.
11+
export environment: interface {
12+
get: func(var: string) -> string;
13+
set: func(var: string, val: string);
14+
}
15+
16+
/// An example of exporting an interface defined in this file.
17+
export units;
18+
19+
/// An example of exporting an interface defined in a dependency.
20+
export wasi:random/[email protected];
21+
}
22+
23+
interface units {
24+
use wasi:clocks/[email protected].{duration};
25+
26+
/// Renders the number of bytes as a human readable string.
27+
bytes-to-string: func(bytes: u64) -> string;
28+
29+
/// Renders the provided duration as a human readable string.
30+
duration-to-string: func(dur: duration) -> string;
31+
}
32+
"#,
33+
34+
// provided here to get the export macro rendered in documentation, not
35+
// required for external use.
36+
pub_export_macro: true,
37+
38+
// provided to specify the path to `wasi:*` dependencies referenced above.
39+
40+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
crate::generate!({
2+
inline: r#"
3+
package example:exported-resources;
4+
5+
world import-some-resources {
6+
export logging;
7+
}
8+
9+
interface logging {
10+
enum level {
11+
debug,
12+
info,
13+
warn,
14+
error,
15+
}
16+
resource logger {
17+
constructor(max-level: level);
18+
19+
get-max-level: func() -> level;
20+
set-max-level: func(level: level);
21+
22+
log: func(level: level, msg: string);
23+
}
24+
}
25+
"#,
26+
});

0 commit comments

Comments
 (0)