cargo watt
Watt is a runtime for executing Rust procedural macros compiled as WebAssembly.
I assume you are familiar with watt, dtolnay's crate for executing procedural macros in a web assembly interpreter.
There, tooling improvements are listed as remaining work, and this cargo subcommand aims to achieve that. Its purposes are
- compile existing proc-macro crates without manual intervention for the watt runtime
- verify that a wasm file is compiled from a particular source
A list of some popular procedural macros compiled with cargo watt is available here.
Prerequisites
To use cargo watt you will need to install rustc's wasm32 toolchain:
Also, for optimizing the size of the wasm-file, wasm-strip (wabt) and wasm-opt (binaryen) will be used. To opt out, use --no-wasm-opt or --no-wasm-strip.
Building proc-macro crates (cargo watt build)
Building works by first copying a crate (either from a local directory, a git repository or crates.io) into /tmp.
The crate type is then changed to cdylib, proc-macro2 is being patched to dtolnay's proc_macro2.
Next, all procedural macros in it are being replaced with pub #[no_mangle] extern "C" fns and proc_macro is replaced with proc_macro2, see this.
At this point, simple crates already compile, but there is more to be done to support a wider range of crates. Since we just change some signatures and hope for the best, sometimes stuff stops working. To 'fix' that (altough it's more of a hack), we do the following:
- replace
synwith this syn patch, which basically has all instances ofproc_macroreplaced withproc_macro2(see here) and the conditional compilation forwasm32-unknown-unknownis removed - do a literal search and replace of
proc_macrotoproc_macro2. This may sound stupid, but in my testing this works alright.
Of course, some crates still don't compile, in that case you need tweak things yourself.
Notably, anything depending on synstructure or proc_macro_error won't work, maybe patches for those will be provided in the future aswell.
Lastly, a shim crate is generated which calls into the generated web assembly file and executes the token tree transformation.
As a user, all you need to do is
)
)
)
Alternatively you can fetch a git repository (cargo watt build --git https://github.com/idanarye/rust-typed-builder) or use a local path (cargo watt build ./path/to/crate).
By default, cargo watt will include all files of original crate (i.e. tests, documentation etc.) in the newly generated one.
If you'd like to only have Cargo.toml, src/lib.rs and src/the-macro.wasm there is the --only-copy-essential option.
Caveats
Some proc-macro crates need to export other things then the actual macros, so they are split into a regular rust crate exporting some Traits/Functions, which then reexports the macros from another crate.
This is why for example cargo watt --crate thiserror will tell you that thiserror is not a proc macro crate. Instead, what you need to do is run cargo watt --crate thiserror-impl and [patch] thiserror-impl to your generated crate.
Also, if the syn version specified in the Cargo.lock file is newer than my patched version, that will result in a compilation error.
In most cases updating is as easy as rebasing the fork on upstream, but there should be some kind of automation for that which there isn't so far.
Verifying compilation (cargo watt verify)
The isolation properties of running the macro inside web assembly ensure that it doesn't have unwanted access to files or the network, but the code it generates can still be mailcious. Therefor it is important to be able to verify that a compiled binary wasm file was indeed compiled by some source, which can then be audited manually.
Just as the build subcommand, cargo watt verify works with local projects, remote git repos and crates.io crates, you can use it like this:
Currently though, a crate compiled an linux will be different than on macos. If you know why this is and how to fix it, let me know.
Installation
Performance
How much of a difference does this make regarding compile times?
I profiled a crate with the following dependencies:
= { = "0.2", = ["macros"] }
= "1.0"
[]
= { = "https://github.com/jakobhellermann/watt-contrib" }
= { = "https://github.com/jakobhellermann/watt-contrib" }
Without the patches:

With patches:

That's a difference of 6 seconds vs 17 seconds, so not bad. Of course, in a real project you're gonna have a more non-macro crates so the speed-up is less noticable, but it's still faster.
LICENSE
MIT © Jakob Hellermann