wasm-bindgen
wasm-bindgen
is a library and tool to facilitate
high-level interactions between Wasm modules and JavaScript; it is built with Rust by
The Rust and WebAssembly Working Group.
Yew is built on wasm-bindgen
and specifically uses the following of its crates:
This section will explore some of these crates in a high level in order to make it easier to understand
and use wasm-bindgen
APIs with Yew. For a more in-depth guide into wasm-bindgen
and it's associated
crates then check out The wasm-bindgen
Guide.
For documentation on the above crates check out wasm-bindgen docs.rs
.
Use the wasm-bindgen
doc.rs search to find browser APIs and JavaScript types that have been imported
over using wasm-bindgen
.
wasm-bindgen
This crate provides many of the building blocks for the rest of the crates above. In this section we
are only going to cover two main areas of the wasm-bindgen
crate and that is the macro and some
types / traits you will see pop up again and again.
#[wasm_bindgen]
macro
The #[wasm_bindgen]
macro, in a high level view, is your translator between Rust and JavaScript, it
allows you to describe imported JavaScript types in terms of Rust and vice versa. Using this macro
is more advanced, and you shouldn't need to reach for it unless you are trying to interop with an
external JavaScript library. The js-sys
and web-sys
crates are essentially imported types using
this macro for JavaScript types and the browser API respectively.
Let's go over a simple example of using the #[wasm-bindgen]
macro to import some specific flavours
of the console.log
.
use wasm_bindgen::prelude::*;
// First up let's take a look of binding `console.log` manually, without the
// help of `web_sys`. Here we're writing the `#[wasm_bindgen]` annotations
// manually ourselves, and the correctness of our program relies on the
// correctness of these annotations!
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
// The `console.log` is quite polymorphic, so we can bind it with multiple
// signatures. Note that we need to use `js_name` to ensure we always call
// `log` in JS.
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
// Multiple arguments too!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
// using the imported functions!
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");
This example was adapted from 1.2 Using console.log of The wasm-bindgen
Guide.
Simulating inheritance
Inheritance between JavaScript classes is a big part of the language and is a major part of the
Document Object Model (DOM). When types are imported using wasm-bindgen
you can
also add attributes that describe its inheritance.
In Rust this inheritance is simulated using the Deref
and AsRef
traits. An example of this
might help; so say you have three types A
, B
, and C
where C
extends B
which in turn
extends A
.
When importing these types the #[wasm-bindgen]
macro will implement the Deref
and AsRef
traits in the following way:
C
canDeref
toB
B
canDeref
toA
C
can beAsRef
toB
- Both
C
&B
can beAsRef
toA
These implementations allow you to call a method from A
on an instance of C
and to use C
as if
it was &B
or &A
.
Its important to note that every single type imported using #[wasm-bindgen]
has the same root type,
you can think of it as the A
in the example above, this type is JsValue
which has
its own section
below.
extends section in The wasm-bindgen
Guide
JsValue
This is a representation of an object owned by JavaScript, this is a root catch-all type for wasm-bindgen
.
Any type that comes from wasm-bindgen
is a JsValue
and this is because JavaScript doesn't have
a strong type system so any function that accepts a variable x
doesn't define its type so x
can be
a valid JavaScript value; hence JsValue
. So when you are working with imported functions or types that
accept a JsValue
, then any imported value is technically valid.
JsValue
can be accepted by a function but that function may still only expect certain types and this
can lead to panics - so when using raw wasm-bindgen
APIs check the documentation of the JavaScript
being imported whether an exception will be caused if that value is not a certain type.
JsCast
Rust has a strong type system and JavaScript...doesn't 😞 So in order for Rust to maintain these
strong types but still be convenient the web assembly group came up with a pretty neat trait JsCast
.
Its job is to help you move from one JavaScript "type" to another, which sounds vague, but it means
that if you have one type which you know is really another then you can use the functions of JsCast
to jump from one type to the other. It's a nice trait to get to know when working with web-sys
,
wasm_bindgen
, js-sys
- you'll notice lots of types will implement JsCast
from those crates.
JsCast
provides both checked and unchecked methods of casting - so that at runtime if you are
unsure what type a certain object is you can try to cast it which returns possible failure types like
Option
and
Result
.
A common example of this in web-sys
is when you are trying to get the
target of an event, you might know what the target element is but the
web_sys::Event
API will always return an Option<web_sys::EventTarget>
so you will need to cast it to the element type. so you can call its methods.
// need to import the trait.
use wasm_bindgen::JsCast;
use web_sys::{Event, EventTarget, HtmlInputElement, HtmlSelectElement};
fn handle_event(event: Event) {
let target: EventTarget = event
.target()
.expect("I'm sure this event has a target!");
// maybe the target is a select element?
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
// do something amazing here
return;
}
// if it wasn't a select element then I KNOW it's a input element!
let input_element: HtmlInputElement = target.unchecked_into();
}
The dyn_ref
method is a checked cast that returns an Option<&T>
which means the original type
can be used again if the cast failed and thus returned None
. The
dyn_into
method will consume self
, as per convention for into methods in Rust, and the type returned is
Result<T, Self>
. If the casting fails, the original Self
value is returned in Err
. You can try again
or do something else with the original type.
Closure
The Closure
type provides a way to transfer Rust closures to JavaScript, the closures passed to
JavaScript must have a 'static
lifetime for soundness reasons.
This type is a "handle" in the sense that whenever it is dropped it will invalidate the JS closure that it refers to. Any usage of the closure in JS after the Closure has been dropped will raise an exception.
Closure
is often used when you are working with a js-sys
or web-sys
API that accepts a type
&js_sys::Function
.
An example of using a Closure
in Yew can be found in the Using Closure
section on the Events page.
js-sys
The js-sys
crate provides bindings / imports of JavaScript's standard, built-in objects, including
their methods and properties.
This does not include any web APIs as this is what web-sys
is for!
wasm-bindgen-futures
The wasm-bindgen-futures
crate provides a bridge for working with JavaScript Promise types as a
Rust Future, and similarly contains utilities to turn a rust Future into a JavaScript Promise.
This can be useful when working with asynchronous or otherwise blocking work in Rust (wasm),
and provides the ability to interoperate with JavaScript events and JavaScript I/O primitives.
There are three main interfaces in this crate currently:
-
JsFuture
- A type that is constructed with aPromise
and can then be used as aFuture<Output=Result<JsValue, JsValue>>
. This Rust future will resolve or reject with the value coming out of thePromise
. -
future_to_promise
- Converts a RustFuture<Output=Result<JsValue, JsValue>>
into a JavaScriptPromise
. The future’s result will translate to either a resolved or rejectedPromise
in JavaScript. -
spawn_local
- Spawns aFuture<Output = ()>
on the current thread. This is the best way to run a Future in Rust without sending it to JavaScript.
wasm-bindgen-futures
documentation.
spawn_local
spawn_local
is going to be the most commonly used part of the wasm-bindgen-futures
crate in Yew
as this helps when using libraries that have async APIs.
use web_sys::console;
use wasm_bindgen_futures::spawn_local;
async fn my_async_fn() -> String { String::from("Hello") }
spawn_local(async {
let mut string = my_async_fn().await;
string.push_str(", world!");
// console log "Hello, world!"
console::log_1(&string.into());
});
Yew has also added support for futures in certain APIs, most notably you can create a
callback_future
which accepts an async
block - this uses spawn_local
internally.