您是否希望 Rust 代码在任何地方运行——从大型服务器到网页、机器人,甚至手表?在三篇文章中的第二篇中,我将向您展示如何使用 WebAssembly (WASM) 直接在用户的浏览器中运行 Rust 代码。
使用这种技术,您可以从静态 Web 服务器(可能是免费的)提供 CPU 密集型的动态网页。作为奖励,用户的数据永远不会离开他们的机器,从而避免了隐私问题。例如,我提供了一个工具,用于搜索朋友、跑步俱乐部成员和队友的比赛结果。要查看该工具,请转到其网页,然后单击“匹配”。
旁白:要了解有关匹配名称的更多信息,请参阅《走向数据科学》中的使用贝叶斯定理在列表中查找独特名称。
在浏览器中运行 Rust 会带来挑战。您的代码无权访问完整的操作系统,例如 Linux、Windows 或 macOS。您无法直接访问文件或网络。您对时间和随机数的访问权限有限。我们将探索解决方法和解决方案。
在浏览器中将代码移植到 WASM 需要几个步骤和选择,并且导航这些步骤和选择可能很耗时。错过一个步骤可能会导致失败。我们将通过提供 9 条规则来减少这种复杂性,我们将详细探讨这些规则:
-
确认您现有的应用程序可以与 WASM WASI 一起使用,并创建一个简单的 JavaScript 网页。
-
安装目标、、 和 Chrome 进行测试 & Chrome驱动程序。
wasm32-unknown-unknown
wasm-pack
wasm-bindgen-cli
-
创建项目 (and)、添加依赖项并进行测试。
cdylib
rlib
wasm-bindgen
-
了解支持哪些类型。
wasm-bindgen
-
更改函数以使用支持的类型。将文件更改为通用 。
BufRead
-
调整测试,跳过那些不适用的测试。
-
如有必要,更改为 JavaScript 友好的依赖项。运行测试。
-
将您的网页连接到您的函数。
-
添加到您的 CI(持续集成)测试中。
wasm-pack
题外话:这些文章基于我在蒙特利尔的 RustConf24 上发表的三个小时的研讨会。感谢该研讨会的参与者。还要特别感谢 Seattle Rust Meetup 的志愿者,他们帮助测试了这些材料。这些文章用更新的信息替换了我去年写的一篇文章。
与本系列的第一篇文章一样,在我们逐一查看规则之前,让我们定义我们的术语。
-
本地:您的家庭操作系统(Linux、Windows、macOS)
-
标准库 (std):提供 Rust 的核心功能 — 、、文件输入/输出、网络、时间。
Vec
String
-
WASM:WebAssembly (WASM) 是一种二进制指令格式,可在大多数浏览器(及更高版本)中运行。
-
WASI:WebAssembly 系统接口 (WASI) 允许浏览器外部的 WASM 访问文件 I/O、网络(尚未访问)和时间处理。
-
no_std:指示 Rust 程序不使用完整的标准库,使其适用于小型嵌入式设备或资源高度受限的环境。
-
alloc:在环境中提供堆内存分配功能(、 等),这对于动态管理内存至关重要。
Vec
String
no_std
根据我对 数据结构项目的经验,以下是我建议的决策,一次描述一个。为了避免一厢情愿,我将它们表示为规则。range-set-blaze
规则 1:确认您现有的应用程序可以与 WASM WASI 一起使用,并创建一个简单的 JavaScript 网页。
如果您满足两个先决条件,则让 Rust 代码在浏览器中运行会更容易:
-
让您的 Rust 代码在 WASM WASI 中运行。
-
获取一些 JavaScript 以在浏览器中运行。
对于第一个先决条件,请参阅迈向数据科学中的在 WASM WASI 上运行 Rust 的九条规则。该文章(本系列的第一篇文章)详细介绍了如何将代码从本机操作系统迁移到 WASM WASI。通过这一移动,您将可以在浏览器中运行 WASM。
通过测试确认您的代码在 WASM WASI 上运行:
rustup target add wasm32-wasip1
cargo install wasmtime-cli
cargo test --target wasm32-wasip1
对于第二个先决条件,请表明您可以创建一些 JavaScript 代码并在浏览器中运行它。我建议将此文件添加到项目的顶层:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Counter</title>
</head>
<body>
<h1>Line Counter</h1>
<input type="file" id="fileInput" />
<p id="lineCount">Lines in file: </p>
<script>
const output = document.getElementById('lineCount');
document.getElementById('fileInput').addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) { output.innerHTML = ''; return } // No file selected
const reader = new FileReader();
// When the file is fully read
reader.onload = async (e) => {
const content = e.target.result;
const lines = content.split(/rn|n/).length;
output.textContent = `Lines in file: ${lines}`;
};
// Now start to read the file as text
reader.readAsText(file);
});
</script>
</body>
</html>
现在,将此页面提供给您的浏览器。您可以通过编辑器扩展提供网页。我使用 VS Code 的实时预览。或者,您可以安装和使用独立的 Web 服务器,例如 Simple Html Server:
cargo install simple-http-server
simple-http-server --ip 127.0.0.1 --port 3000 --index
# then open browser to http://127.0.0.1:3000
现在,您应该会看到一个网页,您可以在该网页上选择一个文件。页面上的 JavaScript 对文件中的行进行计数。
让我们回顾一下 JavaScript 的关键部分,因为稍后我们会将其更改为调用 Rust。
题外话:您必须学习 JavaScript 才能在浏览器中使用 Rust?是的,也不是。是的,您至少需要创建一些简单的 JavaScript 代码。不,你可能不需要 “学习”JavaScript。我发现 ChatGPT 足够好,可以生成我需要的简单 JavaScript。
-
查看用户选择的文件。如果没有,就返回:
const file = event.target.files[0];
if (!file) { output.innerHTML = ''; return } // No file selected
-
创建一个新对象,进行一些设置,然后将文件作为文本读取:
FileReader
const reader = new FileReader();
// ... some setup ...
// Now start to read the file as text
reader.readAsText(file);
-
这是设置。它说:等到文件被完全读取,将其内容作为字符串读取,将字符串拆分为行,并显示行数。
// When the file is fully read
reader.onload = async (e) => {
const content = e.target.result;
const lines = content.split(/rn|n/).length;
output.textContent = `Lines in file: ${lines}`;
};
满足先决条件后,我们接下来安装所需的 WASM-in-the-Browser 工具。
规则2:安装目标wasm-pack和Chrome进行测试和Chrome驱动程序。wasm32-unknown-unknown
wasm-bindgen-cli
我们从一些简单的事情开始,安装以下三个工具:
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --force
cargo install wasm-bindgen-cli --force
第一行安装新目标 。这个目标将 Rust 编译为 WebAssembly,而无需对代码将运行的环境做出任何假设。缺乏假设使其适合在浏览器中运行。(有关目标的更多信息,请参阅上一篇文章的规则 #2。wasm32-unknown-unknown
接下来的两行 install 和 , 命令行实用程序。第一个 API 构建、打包并发布到适合 Web 页面使用的表单。第二个选项使测试更容易。我们用于确保实用程序是最新的并且相互兼容。wasm-pack
wasm-bindgen-cli
--force
现在,我们来到烦人的部分,安装Chrome进行测试和Chromedriver。Chrome for Testing 是 Chrome 浏览器的可自动化版本。Chromedriver 是一个单独的程序,可以获取您的 Rust 测试用例并在 Chrome 中运行它们以进行测试。
为什么安装它们很烦人?首先,这个过程有些复杂。其次,用于测试的 Chrome 版本必须与 Chromedriver 的版本匹配。第三,安装 Chrome for Testing 将与您当前安装的常规 Chrome 冲突。
有鉴于此,以下是我的建议。首先将这两个程序安装到主目录的专用子文件夹中。
-
Linux 和 WSL(适用于 Linux 的 Windows 子系统):
cd ~
mkdir -p ~/.chrome-for-testing
cd .chrome-for-testing/
wget https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/linux64/chrome-linux64.zip
wget https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/linux64/chromedriver-linux64.zip
unzip chrome-linux64.zip
unzip chromedriver-linux64.zip
-
Windows (PowerShell):
New-Item -Path $HOME -Name ".chrome-for-testing" -ItemType "Directory"
Set-Location -Path $HOME.chrome-for-testing
bitsadmin /transfer "ChromeDownload" https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/win64/chrome-win64.zip $HOME.chrome-for-testingchrome-win64.zip
bitsadmin /transfer "ChromeDriverDownload" https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/win64/chromedriver-win64.zip $HOME.chrome-for-testingchromedriver-win64.zip
Expand-Archive -Path "$HOME.chrome-for-testingchrome-win64.zip" -DestinationPath "$HOME.chrome-for-testing"
Expand-Archive -Path "$HOME.chrome-for-testingchromedriver-win64.zip" -DestinationPath "$HOME.chrome-for-testing"
旁白:很抱歉,我还没有测试任何 Mac 说明。请参阅 Chrome for Testing 网页,然后尝试调整 Linux 方法。如果你让我知道什么有效,我会更新这个部分。
这将安装版本 129.0.6668.70,这是截至 2024 年 9 月 30 日的稳定版本。如果您愿意,请查看 Chrome 的测试可用性页面以获取更新的稳定版本。
接下来,我们需要将这些程序添加到我们的 .我们可以临时添加它们,这意味着仅适用于当前终端会话:PATH
-
Linux 和 WSL(仅适用于此会话):
export PATH=~/.chrome-for-testing/chrome-linux64:~/.chrome-for-testing/chromedriver-linux64:$PATH
-
Windows(仅用于此会话):
# PowerShell
$env:PATH = "$HOME.chrome-for-testingchrome-win64;$HOME.chrome-for-testingchromedriver-win64;$PATH"
# or, CMD
set PATH=%USERPROFILE%.chrome-for-testingchrome-win64;%USERPROFILE%.chrome-for-testingchromedriver-win64;%PATH%
或者,我们可以将它们添加到我们的 permanently 中,用于所有未来的终端会话。请注意,这可能会干扰您对常规版 Chrome 的访问。PATH
Linux 和 WSL(然后重新启动终端):
echo 'export PATH=~/.chrome-for-testing/chrome-linux64:~/.chrome-for-testing/chromedriver-linux64:$PATH' >> ~/.bashrc
Windows(PowerShell,然后重新启动终端):
[System.Environment]::SetEnvironmentVariable("Path", "$HOME.chrome-for-testingchrome-win64;$HOME.chrome-for-testingchromedriver-win64;" + $env:PATH, [System.EnvironmentVariableTarget]::User)
安装后,您可以使用以下方法验证安装:
chromedriver --version
题外话:您可以跳过安装和使用 Chrome 进行测试和 Chromedriver 吗?是的,也不是。如果你跳过它们,你仍然可以从 Rust 创建 WASM。此外,您将能够在网页中从 JavaScript 调用该 WASM。
但是,您的项目 — 就像所有好的代码一样 — 应该已经包含测试。如果您跳过 Chrome 进行测试,您将无法运行 WASM-in-the-Browser 测试用例。此外,浏览器中的 WASM 违反了 Rust 的“如果它编译,它就可以工作”的原则。具体来说,如果您使用不受支持的功能(如文件访问),则编译为 WASM 不会捕获错误。只有测试用例才能捕获此类错误。这使得运行测试用例变得至关重要。
现在我们已经有了在浏览器中运行测试的工具,让我们尝试(几乎可以肯定失败)运行这些测试。
规则 3:创建项目 (and)、添加依赖项并测试。cdylib
rlib
wasm-bindgen
该包是 Rust 和 JavaScript 之间自动生成的一组绑定。它允许 JavaScript 调用 Rust。wasm-bindgen
要在浏览器中为 WASM 准备代码,您需要将项目设置为库项目。此外,您将添加和使用依赖项。请执行以下步骤:wasm-bindgen
-
如果您的项目是可执行的,请通过重命名 将其更改为库项目。此外,注释掉您的函数。
src/main.rs
src/lib.rs
main
-
让您的项目同时创建一个静态库(默认)和一个动态库(WASM 需要)。具体而言,编辑以包括:
Cargo.toml
[lib]
crate-type = ["cdylib", "rlib"]
-
添加依赖项:
wasm-bindgen
cargo add wasm-bindgen
cargo add wasm-bindgen-test --dev
-
Create or update (不要与 混淆) 以包括:
.cargo/config.toml
Cargo.toml
[target.wasm32-unknown-unknown]
runner = "wasm-bindgen-test-runner"
接下来,您希望 JavaScript 看到哪些函数?标记这些函数并使其 (public) 。在函数文件的顶部,添加 .#[wasm_bindgen]
pub
use wasm_bindgen::prelude::*;
旁白:目前,您的函数可能无法编译。我们将在后续规则中解决此问题。
测试呢?无论您在哪里都可以添加 .在测试需要的地方,添加以下语句和配置语句:#[test]
#[wasm_bindgen_test]
use
use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
如果您愿意,可以在小型示例项目中尝试上述步骤。从 GitHub 安装示例项目:
# cd to the top of a work directory
git clone --branch native_version --single-branch https://github.com/CarlKCarlK/rustconf24-good-turing.git good-turing
cd good-turing
cargo test
cargo run pg100.txt
在这里,我们在小型示例项目的 上看到了所有这些更改:lib.rs
// --- May fail to compile for now. ---
use wasm_bindgen::prelude::*;
// ...
#[wasm_bindgen]
pub fn good_turing(file_name: &str) -> Result<(u32, u32), io::Error> {
let reader = BufReader::new(File::open(file_name)?);
// ...
}
// fn main() {
// ...
// }
#[cfg(test)]
mod tests {
use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
// ...
#[test]
#[wasm_bindgen_test]
fn test_process_file() {
let (prediction, actual) = good_turing("./pg100.txt").unwrap();
// ...
}
}
完成这些更改后,我们已准备好进行测试(并且可能会失败):
cargo test --target wasm32-unknown-unknown
在这个例子中,编译器抱怨浏览器中的 WASM 不喜欢返回元组类型,这里是 .它还抱怨它不喜欢返回带有 .要解决这些问题,我们需要了解浏览器中的 WASM 支持哪些类型。这就是规则 4 的主题。(u32, u32)
Result
io::Error
在我们修复类型问题并可以运行测试后会发生什么?测试仍将失败,但现在会出现运行时错误。浏览器中的 WASM 不支持从文件中读取。但是,示例测试会尝试从文件中读取数据。在规则 5 中,我们将讨论类型限制和文件访问限制的解决方法。
规则 4:了解支持哪些类型。wasm-bindgen
JavaScript 可以看到的 Rust 函数必须具有支持的输入和输出类型。使用不受支持的类型会导致编译器错误。例如,传入 a 就可以了。传入 Tuples 为 not 。wasm-bindgen
u32
(u32, 32)
更一般地说,我们可以将 Rust 类型分为三类:“Yep!”、“Nope!”和“Avoid”。
是的!
这是 JavaScript(通过 )很好地理解的 Rust 类型的类别。wasm-bindgen
我们将从 Rust 的简单复制类型开始:
这里有两件事让我感到惊讶。首先,64 位整数需要在 JavaScript 端做额外的工作。具体来说,它们需要使用 JavaScript 的类。其次,JavaScript 不支持 128 位整数。128 位整数是 “Nopes”。BigInt
现在转到 String 相关和 vector 相关类型:
这些超级有用的类型使用堆分配的内存。由于 Rust 和 JavaScript 管理内存的方式不同,因此每种语言都会创建自己的数据副本。我想我可以通过将(可变字节切片)从 JavaScript 传递给 Rust 来避免这种分配。那没有用。它不是 0 个副本或 1 个副本,而是复制了两次。&mut [u8]
旁白: For 和 也在 JavaScript 的 UTF-16 Unicode 编码和 Rust 的 UTF-8 Unicode 编码之间进行转换。
String
&str
wasm-bindgen
接下来,在 Rust 中,我们喜欢我们的 Option 和 Result 类型。我很高兴地报告他们是 “Yeps”。
Rust 变成 JavaScript ,Rust 变成 JavaScript 。换句话说,将 Rust 的类型安全 null 处理转换为 JavaScript 的老式方法。在这两种情况下, / 在每种语言中都是惯用的。Some(3)
3
None
null
wasm-bindgen
null
None
Rust 的行为类似于 .Rust 变为 JavaScript ,而 Rust 变为可以使用 / 捕获的 JavaScript 异常。请注意,Rust 中的值仅限于实现 trait 的类型。使用通常效果很好。Result
Option
Ok(3)
3
Err("Some error message")
try
catch
Err
Into<JsValue>
String
最后,让我们看看 struct、enum 和 JSValue,这是我们的最后一组 “Yeps”:
令人兴奋的是,JavaScript 可以在 Rust 结构上构造和调用方法。要启用此功能,您需要用 .#[wasm_bindgen]
例如,假设你希望避免将一个巨大的字符串从 JavaScript 传递到 Rust。您可以定义一个 Rust 结构体,以增量方式处理一系列字符串。JavaScript 可以构造结构体,从文件中向其提供块,然后请求结果。
JavaScript 对 Rust 枚举的处理就不那么令人兴奋了。它只能处理没有关联数据的枚举(类似 C 的枚举),并将其值视为整数。
在令人兴奋的范围中,您可以将不透明的 JavaScript 值作为 .然后,Rust 可以动态检查该值以确定其子类型,或者(如果适用)调用其方法。JsValue
“Yeps”到此结束。是时候看看“Nopes”了。
不!
这是 JavaScript (通过 ) 不处理的 Rust 类型的类别。wasm-bindgen
例如,不能通过引用传递是可以的,因为你可以只使用 ,无论如何它可能更有效。&u8
u8
无法返回字符串 slice () 或常规 slice () 有点烦人。为避免生存期问题,您必须返回 or 等从属类型。&str
&[u8]
String
Vec<u8>
您不能接受可变引用 ()。但是,您可以接受 by 值,对其进行更改,然后返回修改后的 .String
&mut String
String
String
我们如何解决“Nopes”?使用 vectors () 或 structs 代替固定长度的数组、元组和 128 位整数。Vec<T>
Rust 有 sets 和 map。JavaScript 有 sets 和 map。但是,该库不会在它们之间自动转换。那么,例如,如何将 a 从 Rust 传递到 JavaScript?将其包装在你自己的 Rust 结构体中并定义所需的方法。然后,用 .wasm-bindgen
HashSet
#[wasm-bindgen]
现在是我们的第三个类别。
避免
这是 JavaScript(通过 )允许但您不应该使用的 Rust 类型的类别。wasm-bindgen
避免使用 and,因为大多数人会假设它们是 64 位整数,但在 WebAssembly (WASM) 中,它们是 32 位整数。请改用 、 、 或 。usize
isize
u32
i32
u64
i64
在 Rust 中,是一个只能包含有效 Unicode 标量值的特殊值。相比之下,JavaScript 将 a 视为字符串。它检查 Unicode 的有效性,但不强制字符串的长度为 1。如果你需要将 from JavaScript 传递到 Rust 中,最好使用该类型,然后在 Rust 端检查长度。char
u32
char
char
String
规则 5:更改函数以使用支持的类型。将文件更改为通用 。BufRead
凭借我们对支持的类型的了解,我们可以修复我们希望提供给 JavaScript 的函数。我们留下了规则 3 的示例,其中包含如下函数:wasm-bindgen
#[wasm_bindgen]
pub fn good_turing(file_name: &str) -> Result<(u32, u32), io::Error> {
let reader = BufReader::new(File::open(file_name)?);
// ...
}
现在,我们通过删除 来更改函数。我们还将函数更改为从通用读取器读取,而不是从文件名读取。使用可提供更大的灵活性,使函数能够接受不同类型的输入流,例如内存数据或文件。#[wasm_bindgen] pub
BufRead
fn good_turing<R: BufRead>(reader: R) -> Result<(u32, u32), io::Error> {
// delete: let reader = BufReader::new(File::open(file_name)?);
// ...
}
JavaScript 看不到这个函数,所以我们创建了一个调用它的包装函数。例如:
#[wasm_bindgen]
pub fn good_turing_byte_slice(data: &[u8]) -> Result<Vec<u32>, String> {
let reader = BufReader::new(data);
match good_turing(reader) {
Ok((prediction, actual)) => Ok(vec![prediction, actual]),
Err(e) => Err(format!("Error processing data: {e}")),
}
}
这个包装函数将字节 slice () 作为输入,这是 JavaScript 可以传递的东西。该函数将字节切片转换为读取器,并调用内部的 .内部函数返回一个 .包装函数将此结果转换为 ,JavaScript 将接受的类型。&[u8]
good_turing
Result<(u32, u32), io::Error>
Result<Vec<u32>, String>
一般来说,我只愿意对在浏览器中以本机和 WASM 方式运行的函数进行细微的更改。例如,在这里,我愿意将函数更改为适用于通用读取器,而不是文件名。当 JavaScript 兼容性需要重大的、非惯用的更改时,我会创建一个包装函数。
在该示例中,在进行这些更改后,主代码现在会编译。但是,原始测试尚未编译。修复测试是规则 6 的主题。
规则 6:调整测试,跳过那些不适用的测试。
规则 3 主张将每个常规测试 () 也标记为 WASM-in-the-Browser 测试 ()。但是,由于 WASM 在访问系统资源(如文件)方面的限制,并非所有来自原生 Rust 的测试都可以在 WebAssembly 环境中运行。#[test]
#[wasm_bindgen_test]
在我们的示例中,规则 3 为我们提供了不编译的测试代码:
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test]
fn test_process_file() {
let (prediction, actual) = good_turing("./pg100.txt").unwrap();
assert_eq!(prediction, 10223);
assert_eq!(actual, 7967);
}
}
此测试代码失败,因为我们更新的函数需要通用读取器,而不是文件名。我们可以通过从示例文件创建一个 reader 来修复测试:good_turing
use std::fs::File;
#[test]
fn test_process_file() {
let reader = BufReader::new(File::open("pg100.txt").unwrap());
let (prediction, actual) = good_turing(reader).unwrap();
assert_eq!(prediction, 10223);
assert_eq!(actual, 7967);
}
这是一个很好的原生测试。不幸的是,我们不能将其作为 WASM-in-the-Browser 测试运行,因为它使用文件读取器——这是 WASM 不支持的。
解决方案是创建一个额外的测试:
#[test]
#[wasm_bindgen_test]
fn test_good_turing_byte_slice() {
let data = include_bytes!("../pg100.txt");
let result = good_turing_byte_slice(data).unwrap();
assert_eq!(result, vec![10223, 7967]);
}
在编译时,此测试使用宏将文件转换为与 WASM 兼容的字节切片。该函数将字节切片转换为读取器并调用 .(该宏是 Rust 标准库的一部分,因此可用于测试。include_bytes!
good_turing_byte_slice
good_turing
include_bytes
请注意,附加测试既是常规测试,也是 WASM-in-the-Browser 测试。我们希望我们的测试尽可能地两者兼而有之。
在我的项目中,我能够在浏览器中将几乎所有测试标记为常规测试和 WASM。一个例外:测试使用了 Criterion 基准测试函数。Criterion 不在浏览器中的 WASM 中运行,因此我仅将该测试标记为常规 ()。range-set-blaze
#[test]
在我们的主代码(规则 5)和测试代码(规则 6)都固定下来之后,我们真的可以运行我们的测试吗?不一定,我们可能需要找到 JavaScript 友好的依赖项。
旁白:如果您使用的是 Windows 并运行 WASM-in-the-Browser 测试,您可能会看到 “” 这与您的测试无关。你可以忽略它。
ERROR tiny_http] Error accepting new client: A blocking operation was interrupted by a call to WSACancelBlockingCall. (os error 10004)
规则 7:如有必要,更改为 JavaScript 友好的依赖项。运行测试。
依赖
现在将编译示例项目。但是,对于我的项目,修复我的代码和测试是不够的。我还需要修复几个依赖项。具体来说,我需要将它添加到我的 :range-set-blaze
Cargo.toml
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies]
getrandom = { version = "0.2", features = ["js"] }
web-time = "1.1.0"
这两个依赖项启用随机数并提供替代时间库。默认情况下,浏览器中的 WASM 无法访问随机数或时间。这两个依赖项都包装了 JavaScript 函数,使其可供 Rust 访问并且是惯用的。
题外话:有关在 中使用 表达式的更多信息,请参阅我的文章: Nine Rust Cargo.toml Wats and Wat Nots: Master Cargo.toml formatting rules and avoid unbunation | 迈向数据科学 (medium.com)。cfg
Cargo.toml
在 WebAssembly — Categories — crates.io 中查找其他此类 JavaScript 包装库。我没有尝试过但看起来有趣的流行板条箱包括:
-
reqwest— — HTTP 网络访问
features=["wasm"]
-
plotters — 绘图 — 包括一个从 Rust 控制 HTML 画布对象的演示
-
gloo — JavaScript 包装器工具包
另请参阅上一篇文章中的规则 7 — 关于 WASM WASI — 以了解有关修复依赖项问题的更多信息。在本系列的下一篇文章 - about 和 embedded - 我们将更深入地介绍修复依赖项的更多策略。no_std
运行测试
修复依赖项后,我们终于可以在浏览器中运行常规和 WASM 测试了:
cargo test
cargo test --target wasm32-unknown-unknown
回想一下,在幕后,我们对cargo test --target wasm32-unknown-unknown:
-
进去看(规则 3)。
.cargo/config.toml
wasm-bindgen-test-runner
-
调用。
wasm-bindgen-test-runner
-
使用 Chromedriver 在 Chrome 中运行我们的测试以进行测试。(规则 2,确保 Chrome for Testing 和 Chromedriver 在您的道路上)。
随着我们的测试工作,我们现在可以从网页调用 Rust 代码了。
规则 8:将网页连接到函数。
要从网页调用 Rust 函数,您必须首先为 Web 打包 Rust 库。我们在规则 2 中安装。现在,我们运行它:wasm-pack
wasm-pack build --target web
这将编译您的项目并创建 JavaScript 可以理解的输出目录。pkg
例
在规则 1 中,我们创建了一个不调用 Rust 的文件。现在让我们更改它,以便它调用 Rust。下面是一个此类示例,后跟对 INTEREST 更改的描述。index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Good-Turing Estimation</title>
</head>
<body>
<h1>Good-Turing Estimation</h1>
<input type="file" id="fileInput" />
<p id="lineCount"></p>
<script type="module">
import init, { good_turing_byte_slice } from './pkg/good_turing.js'; // These files are generated by `wasm-pack build --target web`
const output = document.getElementById('lineCount');
document.getElementById('fileInput').addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) { output.innerHTML = ''; return } // No file selected
const reader = new FileReader();
// When the file is fully read
reader.onload = async (e) => {
await init(); // Ensure 'good_turing_byte_slice' is ready
// View the memory buffer as a Uint8Array
const u8array = new Uint8Array(e.target.result);
try { // Actually run the WASM
const [prediction, actual] = good_turing_byte_slice(u8array);
output.innerHTML =
`Prediction (words that appear exactly once on even lines): ${prediction.toLocaleString()}<br>` +
`Actual distinct words that appear only on odd lines: ${actual.toLocaleString()}`;
} catch (err) { // Or output an error
output.innerHTML = `Error: ${err}`;
}
};
// Now start to read the file as memory buffer
reader.readAsArrayBuffer(file);
});
</script>
</body>
</html>
让我们来看看 Interest 的变化。
-
下面的代码行将两个函数从模块文件导入到 JavaScript 中,我们使用 .默认函数 ,初始化我们的 Rust 生成的 WebAssembly (WASM) 模块。第二个函数 , 是通过将其名称包含在大括号中来显式导入的。
pkg/good_turing.js
wasm-pack
init
good_turing_byte_slice
import init, { good_turing_byte_slice } from './pkg/good_turing.js';
-
创建一个新对象,进行一些设置,然后将文件作为字节数组读取。
FileReader
const reader = new FileReader();
// ... some setup code ...
// Now start to read the file as bytes.
reader.readAsArrayBuffer(file);
-
以下是我们如何设置将在文件完全读取后运行的代码:
reader.onload = async (e) => {
//...
};
-
此行可确保初始化 WASM 模块。第一次调用时,模块被初始化。在后续调用中,它不执行任何操作,因为模块已经准备就绪。
await init(); // Ensure 'good_turing_byte_slice' is ready
-
从读取的文件中提取字节数组。
// View the memory buffer as a Uint8Array
const u8array = new Uint8Array(e.target.result);
-
调用 Rust 生成的 WASM 函数。
const [prediction, actual] = good_turing_byte_slice(u8array);
旁白:这是一个常规的 (同步) 函数。但是,如果需要,可以在 Rust 端标记它,然后在 JavaScript 端调用它。如果您的 Rust 处理速度很慢,这可以使您的网页更加生动。
good_turing_byte_slice
async
await
-
显示结果。
output.innerHTML =
`Prediction (words that appear exactly once on even lines): ${prediction.toLocaleString()}<br>` +
`Actual distinct words that appear only on odd lines: ${actual.toLocaleString()}`;
-
如果出现错误,则显示错误消息。
try { // Actually run the WASM
// ...
} catch (err) { // Or output an error
output.innerHTML = `Error: ${err}`;
}
示例项目的最终代码位于 GitHub 上,包括一个解释其用途的 README.md。单击此链接可查看实时演示。
range-set-blaze
我应用户的要求移植到 WASM,以便他们可以在自己的项目中使用它。该项目通常用作其他项目中的库。换句话说,您通常不会期望成为网页的核心。尽管如此,我还是制作了一个小型演示页面。您可以浏览它或检查其index.html。该页面展示了如何将整数列表转换为不相交范围的排序列表。range-set-blaze
range-set-blaze
range-set-blaze
range-set-blaze
题外话:在 GitHub 上免费托管您的 WASM-in-the-Browser 项目 1.
在您的项目中,创建一个文件夹。
2. 执行 .
3. 复制(不要只是移动)并进入 。
4. 删除 中的文件。
5. 将项目签入 GitHub。
6. 转到 GitHub 上的项目。然后转到“设置”、“页面”。
7. 将分支(在本例中为 )和文件夹设置为 。救。
8. URL 将基于您的帐户和项目名称,例如 https://carlkcarlk.github.io/rustconf24-good-turing/
9。要更新,请重复步骤 2 到 5(含)。docs
wasm-pack build --target web
index.html
pkg
docs
.gitignore
docs/pkg
main
docs
规则 9:添加到您的 CI(持续集成)测试中。wasm-pack
您的项目现在正在浏览器中编译为 WASM,通过测试,并在 Web 页面上展示。你完成了吗?差一点。因为,正如我在第一篇文章中所说:
如果它不在 CI 中,则它不存在。
回想一下,持续集成 (CI) 是一个系统,它可以在您每次更新代码时自动运行您的测试,确保您的代码继续按预期工作。就我而言,GitHub 托管我的项目。以下是我添加的配置,用于在浏览器中的 WASM 上测试我的项目:.github/workflows/ci.yml
test_wasm_unknown_unknown:
name: Test WASM unknown unknown
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
target: wasm32-unknown-unknown
- name: Install wasm-pack
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Run WASM tests with Chrome
run: |
rustup target add wasm32-unknown-unknown
wasm-pack test --chrome --headless
通过将浏览器中的 WASM 集成到 CI 中,我可以放心地将新代码添加到我的项目中。CI 会自动测试我的所有代码将来是否继续在浏览器中支持 WASM。
所以,你有它 - 在浏览器中将 Rust 代码移植到 WASM 的 9 条规则。以下是让我感到惊讶的地方:
坏处:
-
在浏览器中设置 WASM 测试很困难。具体来说,Chrome for Testing 和 Chromedriver 很难安装和管理。
-
浏览器中的 WASM 违反了 Rust 的说法“如果它编译,它就可以工作”。如果您使用不受支持的功能(例如,直接文件访问),编译器将无法捕获错误。相反,您将在运行时失败。
-
传递字符串和字节向量会创建数据的两个副本,一个在 JavaScript 端,一个在 Rust 端。
好的:
-
浏览器中的 WASM 既有用又有趣。
-
您可以在浏览器中将常规测试标记为也在 WASM 中运行。只需使用这两个属性标记您的测试:
#[test]
#[wasm_bindgen_test]
-
您可以在浏览器中的 WASM 上运行,而无需移植到 .尽管如此,浏览器中的 WASM 还是有用的,可以作为在 embedded/ 上运行的垫脚石。
no_std
no_std
敬请期待!在下一篇文章中,我将向您展示如何通过 移植 Rust 代码以在嵌入式环境中运行。这允许你的代码在小型设备中运行,我觉得这非常酷。
其它课程
linux恶意软件开发对抗与基于ebpf网络安全视频教程
QT开发底层原理与安全逆向视频教程(2024最新)
linux文件系统存储与文件过滤安全开发视频教程(2024最新)
linux高级usb安全开发与源码分析视频教程
linux程序设计与安全开发
-
windows恶意软件开发与对抗视频教程
-
-
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
-
windows文件过滤(更新完成)
-
-
USB过滤(更新完成)
-
-
游戏安全(更新中)
-
-
ios逆向
-
-
windbg
-
-
还有很多免费教程(限学员)
-
-
-
更多详细内容添加作者微信
-
-
原文始发于微信公众号(安全狗的自我修养):在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论