Skip to content

Commit

Permalink
rust: Revert back to historical way of exporting functions (bytecodea…
Browse files Browse the repository at this point in the history
…lliance#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
  • Loading branch information
alexcrichton authored Feb 28, 2024
1 parent 4a2bf63 commit a0c7383
Show file tree
Hide file tree
Showing 52 changed files with 2,000 additions and 869 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ jobs:
- run: cargo build --no-default-features --features csharp
- run: cargo build --no-default-features --features markdown

# Verity that documentation can be generated for the rust bindings crate.
- run: cargo doc -p wit-bindgen --no-deps
env:
RUSTDOCFLAGS: --cfg=docsrs

rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions crates/guest-rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<div align="center">
<h1><code>wit-bindgen</code></h1>

<p>
<strong>Guest Rust language bindings generator for
<a href="https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md">WIT</a>
and the
<a href="https://github.com/WebAssembly/component-model">Component Model</a>
</strong>
</p>

<strong>A <a href="https://bytecodealliance.org/">Bytecode Alliance</a> project</strong>

<p>
<img src="https://img.shields.io/badge/rustc-stable+-green.svg" alt="supported rustc stable" />
<a href="https://docs.rs/wit-bindgen"><img src="https://docs.rs/wit-bindgen/badge.svg" alt="Documentation Status" /></a>
</p>
</div>

# About

This crate provides a macro, [`generate!`], to automatically generate Rust
bindings for a [WIT] [world]. For more information about this crate see the
[online documentation] which includes some examples and longer form reference
documentation as well.

This crate is developed as a portion of the [`wit-bindgen` repository] which
also contains a CLI and generators for other languages.

[`generate!`]: https://docs.rs/wit-bindgen/latest/wit_bindgen/macro.generate.html
[WIT]: https://component-model.bytecodealliance.org/design/wit.html
[world]: https://component-model.bytecodealliance.org/design/worlds.html
[online documentation]: https://docs.rs/wit-bindgen
[`wit-bindgen` repository]: https://github.com/bytecodealliance/wit-bindgen

# License

This project is licensed under the Apache 2.0 license with the LLVM exception.
See [LICENSE](LICENSE) for more details.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this project by you, as defined in the Apache-2.0 license,
shall be licensed as above, without any additional terms or conditions.
55 changes: 55 additions & 0 deletions crates/guest-rust/src/examples.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Examples of output of the [`generate!`] macro.
//!
//! This module is only included in docs.rs documentation and is not present in
//! the actual crate when compiling from crates.io. The purpose of this module
//! is to showcase what the output of the [`generate!`] macro looks like.
//!
//! [`generate!`]: crate::generate
/// An example of generated bindings for top-level imported functions and
/// interfaces into a world.
///
/// The code used to generate this module is:
///
/// ```rust
#[doc = include_str!("./examples/_0_world_imports.rs")]
/// ```
pub mod _0_world_imports;

/// An example of importing interfaces into a world.
///
/// The code used to generate this module is:
///
/// ```rust
#[doc = include_str!("./examples/_1_interface_imports.rs")]
/// ```
pub mod _1_interface_imports;

/// An example of importing resources into a world.
///
/// The code used to generate this module is:
///
/// ```rust
#[doc = include_str!("./examples/_2_imported_resources.rs")]
/// ```
pub mod _2_imported_resources;

/// An example of exporting items from a world and the traits that they
/// generate.
///
/// The code used to generate this module is:
///
/// ```rust
#[doc = include_str!("./examples/_3_world_exports.rs")]
/// ```
pub mod _3_world_exports;

/// An example of exporting resources from a world and the traits that they
/// generate.
///
/// The code used to generate this module is:
///
/// ```rust
#[doc = include_str!("./examples/_4_exported_resources.rs")]
/// ```
pub mod _4_exported_resources;
17 changes: 17 additions & 0 deletions crates/guest-rust/src/examples/_0_world_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
crate::generate!({
inline: r#"
package example:world-imports;
world with-imports {
/// Fetch a greeting to present.
import greet: func() -> string;
/// Log a message to the host.
import log: func(msg: string);
import my-custom-host: interface {
tick: func();
}
}
"#,
});
27 changes: 27 additions & 0 deletions crates/guest-rust/src/examples/_1_interface_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
crate::generate!({
inline: r#"
package example:interface-imports;
interface logging {
enum level {
debug,
info,
warn,
error,
}
log: func(level: level, msg: string);
}
world with-imports {
// Local interfaces can be imported.
import logging;
// Dependencies can also be referenced, and they're loaded from the
// `path` directive specified below.
import wasi:cli/[email protected];
}
"#,

path: "[email protected]",
});
22 changes: 22 additions & 0 deletions crates/guest-rust/src/examples/_2_imported_resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
crate::generate!({
inline: r#"
package example:imported-resources;
world import-some-resources {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
log: func(level: level, msg: string);
}
}
"#,
});
40 changes: 40 additions & 0 deletions crates/guest-rust/src/examples/_3_world_exports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
crate::generate!({
inline: r#"
package example:world-exports;
world with-exports {
import log: func(msg: string);
export run: func();
/// An example of exporting an interface inline naming it directly.
export environment: interface {
get: func(var: string) -> string;
set: func(var: string, val: string);
}
/// An example of exporting an interface defined in this file.
export units;
/// An example of exporting an interface defined in a dependency.
export wasi:random/[email protected];
}
interface units {
use wasi:clocks/[email protected].{duration};
/// Renders the number of bytes as a human readable string.
bytes-to-string: func(bytes: u64) -> string;
/// Renders the provided duration as a human readable string.
duration-to-string: func(dur: duration) -> string;
}
"#,

// provided here to get the export macro rendered in documentation, not
// required for external use.
pub_export_macro: true,

// provided to specify the path to `wasi:*` dependencies referenced above.
path: "[email protected]",
});
26 changes: 26 additions & 0 deletions crates/guest-rust/src/examples/_4_exported_resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
crate::generate!({
inline: r#"
package example:exported-resources;
world import-some-resources {
export logging;
}
interface logging {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
log: func(level: level, msg: string);
}
}
"#,
});
Loading

0 comments on commit a0c7383

Please sign in to comment.