Rust wasm 基础速学
WebAssembly
通过Web执行一种类似于机器码的程序,简称wasm。相对于JS解析执行性能大大提升,目前主流浏览器都已支持。
wasm 本身是一种字节码标准,一般通过c/c++、GO、Rust来进行开发,并编译成wasm。其中Rust在这块相对更活跃一些
场景:如图像处理、视觉效果、3D游戏、其他需要CPU高性能计算的场景
编译出第一个wasm模块
安装 wasm-pack,它是 Rust-Wasm 官方工作组开发,用于构建 wasm 应用程序的工具。
cargo install wasm-pack
创建项目,因为编写的是 wasm 所以我们选择 lib 。
cargo new --lib mywasm
修改 Cargo.toml
增加依赖项。
[package]
name = "mywasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
修改 lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn echo() -> String {
format!("{}", "maksim")
}
如果我们想要对外暴露函数可以在函数上方加上 #[wasm_bindgen]
注解,在上面的额代码中,我们暴露了一个 echo 函数,用来打印输出一段字符。
接下来,我们在命令行中执行:
$ wasm-pack build -t nodejs
$ ls pkg/
mywasm.d.ts mywasm.js mywasm_bg.js mywasm_bg.wasm mywasm_bg.wasm.d.ts package.json
wasm-pack build
是用来构建代码,-t nodejs
是用于指定生成 nodejs 可引入的 wasm 代码。pkg是我们生成的 wasm 文件。
使用 node 调用我们的 wasm 模块
我们在根目录中建立一个 test.js 的文件。
let {echo} = require("./pkg/mywasm");
console.log(echo());
执行结果如下:
$ node test.js
maksim
从 js 中导入函数到 Rust
我们如果想要使用js 中的函数只需要将 js 中的函数原型引入到 rust 中即可,wasm_bindgen 中提了该功能。
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(str: &str);
}
#[wasm_bindgen]
pub fn echo() {
log("hello maksim")
}
执行结果如下:
hello maksim
undefined
支持参数打印
现在我们对 echo 增加支持传递参数。
#[wasm_bindgen]
pub fn echo(s: &str) {
log(s)
}
修改test.js 的调用。
let {echo} = require("./pkg/mywasm");
console.log(echo('abc'));
// 运行结果
//abc
//undefined
用宏来简化 echo
针对于这类的输出函数,其实我们使用宏要更方便一些。
macro_rules! echo {
($expr:expr) => {
log(format!("{}", $expr).as_str());
};
}
#[wasm_bindgen]
pub fn echo(s: &str) {
echo!(s)
}
在 JS 中使用 Rust 的结构体
我们在 lib.rs 下增加一个 UserModel 的结构体,并且增加两个函数。
#[wasm_bindgen]
pub struct UserModel {
user_id: i32,
}
#[wasm_bindgen]
impl UserModel {
pub fn get_user_id(&self) -> i32 {
self.user_id
}
}
#[wasm_bindgen]
pub fn new_user(id: i32) -> UserModel {
UserModel { user_id: id }
}
- get_user_id 用来返回 user_id。
- new_user 用来创建 UserModel 结构体
修改 test.js
let {echo, new_user} = require("./pkg/mywasm");
let user = new_user(12);
echo(user.get_user_id().toString());
由于目前 echo 仅支持字符串,我们需要对 get_user_id() 的返回值进行类型转换。
运行结果如下:
$ node test.js
12
接下来,我们对这段代码进行优化,因为我们的代码不可能只写在一个文件里面。我们在 src
目录下创建一个名叫 models
的文件夹,用来存放我们的 model 代码。
首先是创建 mod.rs 这是 Rust 的基础,用来导出模块。
pub mod user_model;
然后创建 user_model.rs。
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct UserModel {
user_id: i32,
}
#[wasm_bindgen]
impl UserModel {
pub fn get_user_id(&self) -> i32 {
self.user_id
}
#[wasm_bindgen(constructor)]
pub fn new() -> UserModel {
UserModel { user_id: -1 }
}
}
#[wasm_bindgen]
pub fn new_user(id: i32) -> UserModel {
UserModel { user_id: id }
}
一定要注意最上面的导入,由于已经拆分成独立的文件了,如果没有导入会报错,同时我们在代码中增加了下面这段代码
#[wasm_bindgen(constructor)]
pub fn new() -> UserModel {
UserModel { user_id: -1 }
}
对应到 js 中就是构造方法。
let {echo, UserModel} = require("./pkg/mywasm");
let user = new UserModel();
echo(user.get_user_id().toString());
执行代码会输出-1。
接下来我们增加 getter 方法。
#[wasm_bindgen(getter)]
pub fn uid(&self) -> i32 {
self.user_id
}
#[wasm_bindgen(setter)]
pub fn set_uid(&mut self, value: i32) {
self.user_id = value
}
其中 setter 的命名是固定写法,必须以 set_ 开头,代码中还需要注意 &mut self。