Skip to main content
Version: Next

wasm-bindgen

wasm-bindgen 是一个在 JavaScript 和 Rust 函数之间建立调用桥梁的库和工具。它是由 Rust 和 WebAssembly 工作组 使用 Rust 构建的。

Yew 使用 wasm-bindgen 通过一些 crate 与浏览器进行交互:

本节将从更抽象的层次上探讨这些 crate,以便更容易地理解和使用 Yew 中的 wasm-bindgen API。要了解有关 wasm-bindgen 及其相关 crate 的更深入指南,请查看 wasm-bindgen 指引

有关上述 crate 的文档,请查看 wasm-bindgen docs.rs

提示

使用 wasm-bindgen doc.rs 搜索来查找已使用 wasm-bindgen 导入的浏览器 API 和 JavaScript 类型。

wasm-bindgen

这个 crate 为上面的其他 crate 提供了许多构建块。在本节中,我们只会涵盖 wasm-bindgen crate 的两个主要领域,即宏和一些您会一遍又一遍看到的类型/特性。

#[wasm_bindgen] macro

#[wasm_bindgen] 宏提供了 Rust 和 JavaScript 之间的接口,提供了一个在两者之间进行转换的系统。使用这个宏更为高级,除非您要使用外部 JavaScript 库,否则不应该使用它。js-sysweb-sys crate 为内置 JavaScript 类型和浏览器 API 提供了 wasm-bindgen 定义。

让我们通过一个简单的示例来使用 #[wasm-bindgen] 宏来导入一些特定版本的 console.log 函数。

use wasm_bindgen::prelude::*;

// 首先让我们手动绑定 `console.log`,而不使用 `web_sys` 的帮助。
// 在这里,我们手动编写 `#[wasm_bindgen]` 注解,我们程序的正确性取决于这些注解的正确性!
#[wasm_bindgen]
extern "C" {
// 在这里使用 `js_namespace` 来绑定 `console.log(..)` 而不是只有 `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);

// `console.log` 是多态的,所以我们可以使用多个签名绑定它。
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);

// 多个参数也是可以的!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}

// 使用导入的函数!
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");

这个示例是基于 1.2 使用 console.log 的 wasm-bindgen 指引 改编的。

模拟继承

在 JavaScript 类之间的继承是 JavaScript 语言的核心特性,DOM(文档对象模型)是围绕它设计的。当使用 wasm-bindgen 导入类型时,您还可以添加描述它们继承关系的属性。

在 Rust 中,这种继承关系使用 DerefAsRef 特性来表示。这里举个例子可能会有所帮助;假设您有三种类型 ABC,其中 C 扩展了 B,而 B 又扩展了 A

在导入这些类型时,#[wasm-bindgen] 宏将按照以下方式实现 DerefAsRef 特性:

  • C 可以 DerefB
  • B 可以 DerefA
  • C 可以被 AsRefB
  • CB 都可以被 AsRefA

这些实现允许您在 C 的实例上调用 A 的方法,并将 C 用作 &B&A

需要注意的是,使用 #[wasm-bindgen] 导入的每种类型都有相同的根类型,您可以将其视为上面示例中的 A,这种类型是 JsValue,下面有它的部分。

wasm-bindgen 指引中的 extends 部分

JsValue

这是 JavaScript 拥有的对象的表示,这是 wasm-bindgen 的根捕获类型。任何来自 wasm-bindgen 的类型都是 JsValue,这是因为 JavaScript 没有强类型系统,因此接受变量 x 的任何函数都不定义其类型,因此 x 可以是有效的 JavaScript 值;因此 JsValue。如果您正在使用接受 JsValue 的导入函数或类型,那么任何导入的值在技术上都是有效的。

JsValue 可以被函数接受,但该函数可能仍然只接受某些类型,这可能会导致 panic - 因此在使用原始 wasm-bindgen API 时,请检查导入的 JavaScript 的文档,以确定是否会在该值不是某种类型时引发异常(panic)。

JsValue 文档

JsCast

Rust 有一个强类型系统,而 JavaScript……没有😞。为了让 Rust 保持这些强类型但仍然方便,WebAssembly 工作组提出了一个非常巧妙的特性 JsCast。它的工作是帮助您从一个 JavaScript "类型" 转换到另一个 "类型",这听起来很模糊,但它意味着如果您有一个类型,您知道它是另一个类型,那么您可以使用 JsCast 的函数从一个类型跳到另一个类型。当使用 web-syswasm_bindgenjs-sys 时,了解这个很好的特性 - 您会注意到许多类型将从这些 crate 中实现 JsCast

JsCast 提供了转换的检查和不检查方法 - 因此在运行时,如果您不确定某个对象是什么类型,您可以尝试将其转换,这将返回可能的失败类型,如 OptionResult

一个常见的例子是在 web-sys 中,当您尝试获取事件的目标时。您可能知道目标元素是什么,但 web_sys::Event API 总是会返回一个 Option<web_sys::EventTarget>。 您需要将其转换为元素类型,以便调用其方法。

// 需要先导入这个 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!");

// 也许目标是一个选择元素?
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
// 做点别的
return;
}

// 如果它能确定不是一个选择元素,那么我可以肯定它是一个输入元素!
let input_element: HtmlInputElement = target.unchecked_into();
}

dyn_ref 方法是一个检查的转换,返回一个 Option<&T>,这意味着如果转换失败,则可以再次使用原始类型,因此返回 Nonedyn_into 方法将消耗 self,这是 Rust 中 into 方法的约定,返回的类型是 Result<T, Self>。如果转换失败,则原始的 Self 值将在 Err 中返回。您可以再试一次或对原始类型进行其他操作。

JsCast documentation.

Closure

Closure 类型提供了一种将 Rust 闭包传递到 JavaScript 的方法,出于健全性原因,传递给 JavaScript 的闭包必须具有 'static 生命周期。

这种类型是一个“句柄”,意味着每当它被丢弃时,它将使其引用的 JS 闭包无效。在 Closure 被丢弃后,对 JS 中闭包的任何使用都将引发异常。

当您使用接受类型 &js_sys::Functionjs-sysweb-sys API 时,通常会使用 Closure。在 Events 页面的 Using Closure 部分 中可以找到在 Yew 中使用 Closure 的示例。

Closure 文档.

js-sys

js-sys crate 提供了 JavaScript 标准内置对象的绑定/导入,包括它们的方法和属性。

这不包括任何 Web API,因为这是 web-sys 的作用!

js-sys 文档.

wasm-bindgen-futures

wasm-bindgen-futures crate 提供了一个桥梁,用于将 JavaScript Promise 类型作为 Rust Future 进行处理,并包含将 Rust Future 转换为 JavaScript Promise 的实用程序。当在 Rust(wasm)中处理异步或其他阻塞工作时,这可能很有用,并提供了与 JavaScript 事件和 JavaScript I/O 原语进行交互的能力。

目前这个 crate 中有三个主要接口:

  1. JsFuture - 一个使用 Promise 构造的类型,然后可以用作 Future<Output=Result<JsValue, JsValue>>。如果 Promise 被解析,这个 Future 将解析为 Ok,如果 Promise 被拒绝,则解析为 Err,分别包含 Promise 的解析或拒绝值。

  2. future_to_promise - 将 Rust Future<Output=Result<JsValue, JsValue>> 转换为 JavaScript Promise。未来的结果将转换为 JavaScript 中的已解析或已拒绝 Promise

  3. spawn_local - 在当前线程上生成一个 Future<Output = ()>。这是在 Rust 中运行 Future 的最佳方法,而不是将其发送到 JavaScript。

wasm-bindgen-futures 文档.

spawn_local

spawn_local 将是 Yew 中 wasm-bindgen-futures crate 中最常用的部分,因为这有助于使用具有异步 API 的库。

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!");
// 控制台输出 "Hello, world!"
console::log_1(&string.into());
});

Yew 还在某些 API 中添加了对 futures 的支持,最值得注意的是您可以创建一个接受 async 块的 callback_future - 这在内部使用了 spawn_local

spawn_local 文档.