在受限的环境中运行Rust会带来挑战,代码可能无法访问完整的操作系统,如Linux、Windows或macOS。可能对文件、网络、时间、随机数甚至内存的访问有限(或没有)。我们将探索变通方法和解决方案。这篇文章主要关注在“WASM-WASI”(一种类似容器的环境)上运行Rust代码。作为在浏览器或嵌入式系统中运行Rust的第一步,它是有价值的。
将代码运行在WASM-WASI上需要许多步骤和规则,浏览这些规则可能会很耗时。错过一步就可能导致失败,我们将通过提供9条规则来减少这种复杂性。
1,WASI很简单,就目前而言,除了作为一个垫脚石之外,基本上没什么用。
2019年,Docker联合创始人所罗门·海克斯在推特上写道:“如果WASM-WASI在2008年就存在,我们就不需要创建Docker了。这就是它的重要性,服务器上的Webassembly是计算的未来。但是,标准化的系统接口是缺失的环节,让我们希望WASI能够胜任这项任务。”
如果WASM WASI真的准备好并且有用,那么每个人都应该已经在使用它了。但是,它还没有准备好。从WASI Preview 1开始,情况是这样的:你可以访问一些文件操作、环境变量,并可以访问时间和生成随机数。但是,它不支持网络。那么,WASM WASI有什么好处呢?目前,它的主要价值在于向在浏览器或嵌入式系统上运行代码迈出了一步。
2,理解Rust targets
让我们更深入地了解Rust targets——不仅是WASM WASI的基本信息,也是一般Rust开发的基本信息。在Windows机器上,可以编译一个Rust项目以在Linux或macOS上运行。同样,在Linux机器上,也可以编译Rust项目以Windows或macOS为目标。下面是用来在Windows机器上添加和检查Linux target的命令:
# 堆代码 duidaima.com
rustup target add x86_64-unknown-linux-gnu
cargo check --target x86_64-unknown-linux-gnu
要查看Rust支持的所有目标,使用命令:
rustc --print target-list
它将列出200多个目标,包括x86_64-unknown-linux-gnu、wasm32-wasip1和wasm32-unknown-unknown。目标名称最多包含四个部分:CPU系列、供应商、操作系统和环境(例如,GNU vs LVMM)。现在我们了解了一些targets,让我们继续安装WASM WASI所需的targets。
3,安装wasm32-wasip1 target和WASMTIME,然后创建“Hello, WebAssembly!”
为了在浏览器外的WASM上运行我们的Rust代码,我们需要安装wasm32-wasip1 target(带有WASI Preview1的32位WebAssembly)。我们还将安装WASMTIME,一个允许我们在浏览器外使用WASI运行WebAssembly模块的运行时。
rustup target add wasm32-wasip1
cargo install wasmtime-cli
为了测试我们的安装,让我们使用以下命令创建一个新的Rust项目:
cargo new hello_wasi
在src/main.rs文件中写入以下代码:
fn main() {
#[cfg(not(target_arch = "wasm32"))]
println!("Hello, world!");
#[cfg(target_arch = "wasm32")]
println!("Hello, WebAssembly!");
}
现在,使用cargo run运行该项目,应该看到Hello, world!打印到控制台。
接下来,创建.cargo/config.toml文件,它指定了Rust在以WASM-WASI为目标时应该如何运行和测试项目。
[target.wasm32-wasip1]
runner = "wasmtime run --dir ."
现在执行以下命令:
cargo run --target wasm32-wasip1
应该看到Hello, WebAssembly! 恭喜,我们刚刚成功地在类似容器的WASM-WASI环境中运行了一些Rust代码。
4,理解条件编译
现在,让我们研究一下#[cfg(…)]——Rust中条件编译代码的重要工具。我们看一下如下代码:
fn main() {
#[cfg(not(target_arch = "wasm32"))]
println!("Hello, world!");
#[cfg(target_arch = "wasm32")]
println!("Hello, WebAssembly!");
}
#[cfg(…)]告诉Rust编译器根据特定条件包含或排除某些代码项。“代码项”指的是一个代码单元,如函数、语句或表达式。使用#[cfg(…)],可以有条件地编译代码。换句话说,可以为不同的情况创建不同版本的代码。例如,在以wasm32为目标编译时,编译器会忽略#[cfg(not(target_arch = "wasm32"))]块,并且只包含以下内容:
fn main() {
println!("Hello, WebAssembly!");
}
可以将表达式与逻辑操作符not、any和all结合使用。
#[cfg(not(target_arch = "wasm32"))]
...
#[cfg(target_arch = "wasm32")]
...
要有条件地编译整个文件,请在文件的顶部放置#![cfg(…)]。当文件仅与特定目标或配置相关时,这很有用。也可以在Cargo.toml中使用cfg表达式,有条件地包含依赖项。这可以为不同的目标定制依赖项。例如,下面配置表示“当目标不是wasm32时,依赖于criterion的Rayon特性”
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5.1", features = ["rayon"] }
5,WASM-WASI 不支持网络
如果你的项目需要访问网络,使用Tokio的异步任务,或使用Rayon的多线程,不幸的是,这些功能在WASM-WASI Preview 1中不支持。在Rust中,有一个常见的说法:“如果它能编译,它就能工作。”不幸的是,这并不总是适用于WASM-WASI。如果使用不支持的特性,如网络,编译器将无法捕获错误。相反,它将在运行时失败。例如,这段代码在WASM-WASI上编译和运行,但总是返回一个错误,因为网络不被支持。
use std::net::TcpStream;
fn main() {
match TcpStream::connect("crates.io:80") {
Ok(_) => println!("Successfully connected."),
Err(e) => println!("Failed to connect: {e}"),
}
}
幸运的是,WASM-WASI Preview 2有望改进这些限制,提供更多的特性,包括对网络和异步任务的更好支持。
6,将WASM-WASI添加到CI(持续集成)测试中
持续集成(CI)是一种系统,它可以在每次更新代码时自动运行测试,确保代码继续按预期工作。通过将WASM-WASI添加到CI管道中,可以保证将来的更改不会破坏项目与WASM-WASI目标的兼容性。一般项目代码托管在GitHub上,会使用GitHub Actions作为CI管道系统。下面是.github/workflows/ci中的配置:
test_wasip1:
name: Test WASI P1
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: wasm32-wasip1
- name: Install Wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "${HOME}/.wasmtime/bin" >> $GITHUB_PATH
- name: Run WASI tests
run: cargo test --verbose --target wasm32-wasip1
通过将WASM-WASI集成到CI管道中,我们可以自信地向项目中添加新代码。CI将自动测试所有的代码在将来是否继续支持WASM-WASI。