在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

admin 2024年10月21日19:15:04评论19 views字数 18700阅读62分20秒阅读模式

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

在浏览器上运行的 Rust — 来源:https://openai.com/dall-e-2/。作者的所有其他数字。

您是否希望 Rust 代码在任何地方运行——从大型服务器到网页、机器人,甚至手表?在三篇文章中的第二篇中,我将向您展示如何使用 WebAssembly (WASM) 直接在用户的浏览器中运行 Rust 代码。

使用这种技术,您可以从静态 Web 服务器(可能是免费的)提供 CPU 密集型的动态网页。作为奖励,用户的数据永远不会离开他们的机器,从而避免了隐私问题。例如,我提供了一个工具,用于搜索朋友、跑步俱乐部成员和队友的比赛结果。要查看该工具,请转到其网页,然后单击“匹配”。

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

旁白:要了解有关匹配名称的更多信息,请参阅《走向数据科学》中的使用贝叶斯定理在列表中查找独特名称。

在浏览器中运行 Rust 会带来挑战。您的代码无权访问完整的操作系统,例如 Linux、Windows 或 macOS。您无法直接访问文件或网络。您对时间和随机数的访问权限有限。我们将探索解决方法和解决方案。

在浏览器中将代码移植到 WASM 需要几个步骤和选择,并且导航这些步骤和选择可能很耗时。错过一个步骤可能会导致失败。我们将通过提供 9 条规则来减少这种复杂性,我们将详细探讨这些规则:

  1. 确认您现有的应用程序可以与 WASM WASI 一起使用,并创建一个简单的 JavaScript 网页。

  2. 安装目标、、 和 Chrome 进行测试 & Chrome驱动程序。wasm32-unknown-unknownwasm-packwasm-bindgen-cli

  3. 创建项目 (and)、添加依赖项并进行测试。cdylib rlibwasm-bindgen

  4. 了解支持哪些类型。wasm-bindgen

  5. 更改函数以使用支持的类型。将文件更改为通用 。BufRead

  6. 调整测试,跳过那些不适用的测试。

  7. 如有必要,更改为 JavaScript 友好的依赖项。运行测试。

  8. 将您的网页连接到您的函数。

  9. 添加到您的 CI(持续集成)测试中。wasm-pack

题外话:这些文章基于我在蒙特利尔的 RustConf24 上发表的三个小时的研讨会。感谢该研讨会的参与者。还要特别感谢 Seattle Rust Meetup 的志愿者,他们帮助测试了这些材料。这些文章用更新的信息替换了我去年写的一篇文章。

与本系列的第一篇文章一样,在我们逐一查看规则之前,让我们定义我们的术语。

  • 本地:您的家庭操作系统(Linux、Windows、macOS)

  • 标准库 (std):提供 Rust 的核心功能 — 、、文件输入/输出、网络、时间。VecString

  • WASM:WebAssembly (WASM) 是一种二进制指令格式,可在大多数浏览器(及更高版本)中运行。

  • WASI:WebAssembly 系统接口 (WASI) 允许浏览器外部的 WASM 访问文件 I/O、网络(尚未访问)和时间处理。

  • no_std:指示 Rust 程序不使用完整的标准库,使其适用于小型嵌入式设备或资源高度受限的环境。

  • alloc:在环境中提供堆内存分配功能(、 等),这对于动态管理内存至关重要。VecStringno_std

根据我对 数据结构项目的经验,以下是我建议的决策,一次描述一个。为了避免一厢情愿,我将它们表示为规则。range-set-blaze

规则 1:确认您现有的应用程序可以与 WASM WASI 一起使用,并创建一个简单的 JavaScript 网页。

如果您满足两个先决条件,则让 Rust 代码在浏览器中运行会更容易:

  • 让您的 Rust 代码在 WASM WASI 中运行。

  • 获取一些 JavaScript 以在浏览器中运行。

对于第一个先决条件,请参阅迈向数据科学中的在 WASM WASI 上运行 Rust 的九条规则。该文章(本系列的第一篇文章)详细介绍了如何将代码从本机操作系统迁移到 WASM WASI。通过这一移动,您将可以在浏览器中运行 WASM。

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 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 对文件中的行进行计数。

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

让我们回顾一下 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-unknownwasm-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-packwasm-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 rlibwasm-bindgen

该包是 Rust 和 JavaScript 之间自动生成的一组绑定。它允许 JavaScript 调用 Rust。wasm-bindgen

要在浏览器中为 WASM 准备代码,您需要将项目设置为库项目。此外,您将添加和使用依赖项。请执行以下步骤:wasm-bindgen

  • 如果您的项目是可执行的,请通过重命名 将其更改为库项目。此外,注释掉您的函数。src/main.rssrc/lib.rsmain

  • 让您的项目同时创建一个静态库(默认)和一个动态库(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.tomlCargo.toml

[target.wasm32-unknown-unknown]
runner = "wasm-bindgen-test-runner"

接下来,您希望 JavaScript 看到哪些函数?标记这些函数并使其 (public) 。在函数文件的顶部,添加 .#[wasm_bindgen]pubuse 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)Resultio::Error

在我们修复类型问题并可以运行测试后会发生什么?测试仍将失败,但现在会出现运行时错误。浏览器中的 WASM 不支持从文件中读取。但是,示例测试会尝试从文件中读取数据。在规则 5 中,我们将讨论类型限制和文件访问限制的解决方法。

规则 4:了解支持哪些类型。wasm-bindgen

JavaScript 可以看到的 Rust 函数必须具有支持的输入和输出类型。使用不受支持的类型会导致编译器错误。例如,传入 a 就可以了。传入 Tuples 为 not 。wasm-bindgenu32(u32, 32)

更一般地说,我们可以将 Rust 类型分为三类:“Yep!”、“Nope!”和“Avoid”。

是的!

这是 JavaScript(通过 )很好地理解的 Rust 类型的类别。wasm-bindgen

我们将从 Rust 的简单复制类型开始:

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

这里有两件事让我感到惊讶。首先,64 位整数需要在 JavaScript 端做额外的工作。具体来说,它们需要使用 JavaScript 的类。其次,JavaScript 不支持 128 位整数。128 位整数是 “Nopes”。BigInt

现在转到 String 相关和 vector 相关类型

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

这些超级有用的类型使用堆分配的内存。由于 Rust 和 JavaScript 管理内存的方式不同,因此每种语言都会创建自己的数据副本。我想我可以通过将(可变字节切片)从 JavaScript 传递给 Rust 来避免这种分配。那没有用。它不是 0 个副本或 1 个副本,而是复制了两次。&mut [u8]

旁白: For 和 也在 JavaScript 的 UTF-16 Unicode 编码和 Rust 的 UTF-8 Unicode 编码之间进行转换。String&strwasm-bindgen

接下来,在 Rust 中,我们喜欢我们的 Option 和 Result 类型。我很高兴地报告他们是 “Yeps”。

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

Rust 变成 JavaScript ,Rust 变成 JavaScript 。换句话说,将 Rust 的类型安全 null 处理转换为 JavaScript 的老式方法。在这两种情况下, / 在每种语言中都是惯用的。Some(3)3Nonenullwasm-bindgennullNone

Rust 的行为类似于 .Rust 变为 JavaScript ,而 Rust 变为可以使用 / 捕获的 JavaScript 异常。请注意,Rust 中的值仅限于实现 trait 的类型。使用通常效果很好。ResultOptionOk(3)3Err("Some error message")trycatchErrInto<JsValue>String

最后,让我们看看 struct、enum 和 JSValue,这是我们的最后一组 “Yeps”:

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

令人兴奋的是,JavaScript 可以在 Rust 结构上构造和调用方法。要启用此功能,您需要用 .#[wasm_bindgen]

例如,假设你希望避免将一个巨大的字符串从 JavaScript 传递到 Rust。您可以定义一个 Rust 结构体,以增量方式处理一系列字符串。JavaScript 可以构造结构体,从文件中向其提供块,然后请求结果。

JavaScript 对 Rust 枚举的处理就不那么令人兴奋了。它只能处理没有关联数据的枚举(类似 C 的枚举),并将其值视为整数。

在令人兴奋的范围中,您可以将不透明的 JavaScript 值作为 .然后,Rust 可以动态检查该值以确定其子类型,或者(如果适用)调用其方法。JsValue

“Yeps”到此结束。是时候看看“Nopes”了。

不!

这是 JavaScript (通过 ) 不处理的 Rust 类型的类别。wasm-bindgen

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

例如,不能通过引用传递是可以的,因为你可以只使用 ,无论如何它可能更有效。&u8u8

无法返回字符串 slice () 或常规 slice () 有点烦人。为避免生存期问题,您必须返回 or 等从属类型。&str&[u8]StringVec<u8>

您不能接受可变引用 ()。但是,您可以接受 by 值,对其进行更改,然后返回修改后的 .String&mut StringStringString

我们如何解决“Nopes”?使用 vectors () 或 structs 代替固定长度的数组、元组和 128 位整数。Vec<T>

Rust 有 sets 和 map。JavaScript 有 sets 和 map。但是,该库不会在它们之间自动转换。那么,例如,如何将 a 从 Rust 传递到 JavaScript?将其包装在你自己的 Rust 结构体中并定义所需的方法。然后,用 .wasm-bindgenHashSet#[wasm-bindgen]

现在是我们的第三个类别。

避免

这是 JavaScript(通过 )允许但您不应该使用的 Rust 类型的类别。wasm-bindgen

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

避免使用 and,因为大多数人会假设它们是 64 位整数,但在 WebAssembly (WASM) 中,它们是 32 位整数。请改用 、 、 或 。usizeisizeu32i32u64i64

在 Rust 中,是一个只能包含有效 Unicode 标量值的特殊值。相比之下,JavaScript 将 a 视为字符串。它检查 Unicode 的有效性,但不强制字符串的长度为 1。如果你需要将 from JavaScript 传递到 Rust 中,最好使用该类型,然后在 Rust 端检查长度。charu32charcharString

规则 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] pubBufRead

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_turingResult<(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_slicegood_turinginclude_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-blazeCargo.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)。cfgCargo.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.tomlwasm-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.htmlindex.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.jswasm-packinitgood_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_sliceasyncawait

  • 显示结果。

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-blazerange-set-blazerange-set-blazerange-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(含)。
docswasm-pack build --target webindex.htmlpkgdocs.gitignoredocs/pkgmaindocs

规则 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_stdno_std

敬请期待!在下一篇文章中,我将向您展示如何通过 移植 Rust 代码以在嵌入式环境中运行。这允许你的代码在小型设备中运行,我觉得这非常酷。

其它课程

linux恶意软件开发对抗与基于ebpf网络安全视频教程

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

QT开发底层原理与安全逆向视频教程(2024最新)

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

linux文件系统存储与文件过滤安全开发视频教程(2024最新)

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

linux高级usb安全开发与源码分析视频教程

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

linux程序设计与安全开发

在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • windows恶意软件开发与对抗视频教程

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • windows

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • windows()

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • USB()

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • ()

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • ios

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • windbg

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • ()

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

  • 在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

原文始发于微信公众号(安全狗的自我修养):在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月21日19:15:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   在浏览器中运行 Rust 的九条规则-将 range-set-blaze 移植到 WASM 的实践经验http://cn-sec.com/archives/3291782.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息