Rust wasm 基础速学

C/C++/Rust627 字

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。

maksim
Maksim(一笑,吡罗),PHPer,Goper
OωO
开启隐私评论,您的评论仅作者和评论双方可见