What if "Rust for UE" uses WebAssembly?

ue = Unreal Engine

I thought it might be possible to write game logic in Unreal Engine using the Rust language through invoking a WebAssembly instance from C++, requiring the Cargo project to always target wasm32-unknown-unknown.

These are the benefits as far as I know:

  • Less likely to crash depending on how bindings are done
  • Cross-platform

Problems:

  • Minor performance penalty just like all embedded scripting languages
  • How will UE and Blueprints interact with Rust?

I want to create a game in UE once I get a faster computer to run it, so I'm lost in the game project structure. But this is my question: how do you do import items from a specific WebAssembly module? Like:

extern "WebAssembly" {
    #[wasm(import(name = "f", from = "module_name"))]
    fn f();
}

wasm-bindgen seems to not include a way of doing this.

https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute

#[link(wasm_import_module = "foo")]
extern {
    // …
}

Speaking only to use in UE: the correct way to go about embedding Rust (or any other new language) into UE is to integrate into UHT and the UCLASS reflection. This is how C++ and Blueprint interact, and I believe Verse and the experimental editor JS bindings use this same interface as well. I'm (very slowly) experimenting with building out this approach.[1]

The difficult part isn't really the bindings; that's mostly just a lot of plumbing. The difficult part is build integration with UBT. The biggest decision point is how to handle the dylibs; the simple answer of bundling any used bits of Rust std into each Rust using module quickly runs into issues with ODR of the generated C shims between Rust and C++.[2] And if you split things into multiple modules (dylibs) and module dependencies, you're on your own handling however Rust's non-bundled dylibs function, and without using rustc as the linker driver.

It would actually be somewhat reasonable and straightforward to set up the ability to run modules in a wasm engine. But if you're paying the VM marshalling overhead anyway, it makes a lot more sense to use a more directly integrated language (e.g. Blueprint, maybe Verse) which handles the (very object oriented) engine design better than Rust does.

If you want to make a game, use the tools that the engine/ecosystem you're building on is biased towards. You'll enjoy the process more, and you'll only need to learn the one thing instead of learning both that ecosystem and whatever jank layer you're using for integration at the same time.

If you want to make tools, there's an endless rabbit hole available (smiles with a thousand-yare stare).

As for crashes, even a native Rust module with proper safety API wrappers would be basically as good as a wasm module. You have to handle Rust panics somehow, and if you're implementing a function expected to return a valid pointer, there's no safe default. If there is a safe default, the wasm bridge or a catch_unwind can apply that bridge just the same.


  1. There's more I could say, but don't really want to in a public forum without being behind the UE licensee NDA. The things most beneficial to have from rustc would be an extern "C++" capable of matching C++ ABI and ideally name mangling, and some reasonably supported way of linking rlibs into non-bundled dylibs using a system linker in an outer build system instead of rustc as a linker driver. ↩︎

  2. Blueprint has the advantage that its integration/engine is C++, so it can more easily directly use the (contemplated) C++ APIs. Rust doesn't support C++ ABI/mangling, so it needs extern "C" shims to get into the reflection metadata, and ideally it wants to (mostly) directly call the C++ functions rather than require repeated reflection lookup, which means more extern "C" shims, or assuming that it's possible to describe the C++ API in the language of extern "C". ↩︎

5 Likes

Isn't Verse only usable with UEFN? I'm going to ask in the community there to see if I can just use it then...

Possibly; I haven't looked that much into Verse. It's definitely not easily usable yet (e.g. the way BP and C++ are integrated). But Fortnite is very much a driver of and testing ground for UE features; if Verse works out well I find it reasonably likely that Verse will be exposed for use in the general purpose editor.

But also, don't dismiss blueprint out of hand for being a visual scripting language. It's a fully powerful scripting language and you can write nice, clean, maintainable code in Blueprint. The way to make the most out of UE is to prototype as much as you can in Blueprint and refactor things into C++ as needed for performance reasons. Using Blueprint is also the best way to avoid difficult to track down crashes 99.99% of the time[1]. (If you can crash the editor from Blueprint, it's always because of a C++ bug.)


  1. Sometimes you'll have a C++ implemented UFUNCTION which forgets that Blueprint can give it a None (nullptr) and it needs to check for that. Sometimes it will do the check, but only in WITH_EDITOR builds, so it works fine in editor and only crashes in the packaged game. Yes, I'm still salty about tracking down a bug of that shape. That was even fixed in the next release of the engine that we weren't upgrading to. Fun times. ↩︎

1 Like

Honestly C++-WASM FFI is far more unsolved area than C++-Rust FFI. I don't believe involving WASM here would help any binding issue.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.