Skip to main content

2 posts tagged with "WASM"

View All Tags

wasm-bindgen 是一个强大的工具,它提供了多种方式来实现 Rust 和 JavaScript 之间的互操作性。 本文将总结 wasm-bindgen 的几种常见用法,并结合代码案例进行说明。

wasm-bindgen

1. 使用 JavaScript 模块中的自定义函数

你可以在 Rust 中使用 extern "C" 块来声明从 JavaScript 导入的函数,然后在 Rust 中调用它们。 这种方式非常适合需要调用现有 JavaScript 函数的场景。

示例

  • JavaScript 部分 (foo.js)
export function js_add(a, b) {
return a + b;
}
  • Rust 部分
use wasm_bindgen::prelude::*;

// Declare the JavaScript function
#[wasm_bindgen(module = "/js/foo.js")]
extern "C" {
fn js_add(a: i32, b: i32) -> i32;
}

// Define a Rust function that uses the imported JavaScript function
#[wasm_bindgen]
pub fn add_in_rust(a: i32, b: i32) -> i32 {
js_add(a, b)
}

2. 使用 JavaScript 基础功能

如果你需要使用 JavaScript 的基础功能,可以依赖 js-sys crate。 js-sys 提供了对 JavaScript 标准库的绑定,例如 MathDateArray 等。

示例

use wasm_bindgen::prelude::*;
use js_sys::Math;

#[wasm_bindgen]
pub fn random_number() -> f64 {
Math::random()
}

3. 使用 DOM 内容

如果你需要操作 DOM,可以依赖 web-sys crate。 web-sys 提供了对 Web API 的绑定,例如 documentwindowElement 等。

示例

use wasm_bindgen::prelude::*;
use web_sys::window;

#[wasm_bindgen]
pub fn set_document_title(title: &str) {
let window = window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
document.set_title(title);
}

4. 在 Rust 中导出给 JavaScript 使用

在 Rust 中,只要添加 #[wasm_bindgen] 宏并且将函数或类型声明为 pub, 它们就可以导出给 JavaScript 使用。

示例

  • Rust 中的代码:
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}

#[wasm_bindgen]
pub struct Person {
name: String,
age: u32,
}

#[wasm_bindgen]
impl Person {
#[wasm_bindgen(constructor)]
pub fn new(name: &str, age: u32) -> Person {
Person {
name: name.to_string(),
age,
}
}

pub fn greet(&self) -> String {
format!("Hello, my name is {} and I am {} years old.", self.name, self.age)
}
}
  • 在 JavaScript 中使用:
import init, { greet, Person } from './pkg/your_project_name.js';

async function run() {
await init();
console.log(greet("World")); // Should output "Hello, World!"

let person = new Person("Alice", 30);
console.log(person.greet()); // Should output "Hello, my name is Alice and I am 30 years old."
}

run();

5. 异步函数

你可以使用 wasm-bindgen-futures crate 来处理异步函数, 使得 Rust 中的异步函数可以在 JavaScript 中以 Promise 的形式使用。

示例

use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::window;

#[wasm_bindgen]
pub async fn fetch_data() -> Result<JsValue, JsValue> {
let window = window().expect("no global `window` exists");
let response = JsFuture::from(window.fetch_with_str("https://api.example.com/data")).await?;
let response: web_sys::Response = response.dyn_into().unwrap();
let json = JsFuture::from(response.json()?).await?;
Ok(json)
}

6. 错误处理

使用 Result 类型和 JsValue 来捕获和处理 JavaScript 异常。

示例

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn catch_js_error() -> Result<(), JsValue> {
let result = js_sys::Reflect::get(&JsValue::NULL, &JsValue::from_str("non_existent_property"));
match result {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}

7. 内存管理

注意 WebAssembly 和 JavaScript 之间的内存管理, 特别是当你在 Rust 中创建大型数据结构并传递给 JavaScript 时。

  1. 线性内存

    • WebAssembly 使用线性内存模型,这意味着内存是一个连续的字节数组。
    • WebAssembly 模块可以通过 memory 对象来访问和操作这段内存。

2.内存分配:

  • 当从 JavaScript 传递数据到 WebAssembly 时,通常需要在 WebAssembly 内存中分配空间。
  • 可以使用 wasm-bindgen 提供的 wasm_bindgen::memory() 函数来访问 WebAssembly 内存,并手动进行内存分配和释放。
  1. 内存释放:

    • WebAssembly 没有垃圾回收机制,因此需要手动管理内存。
    • 确保在不再需要数据时,及时释放内存以避免内存泄漏。
  2. 传递大型数据结构:

    • 在传递大型数据结构(如数组、字符串等)时,尽量避免不必要的拷贝。
    • 可以使用共享内存或其他优化技术来提高性能。

示例

  • Rust 中的代码:
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use js_sys::Uint8Array;

#[wasm_bindgen]
pub fn allocate_memory(size: usize) -> *mut u8 {
let mut buffer = Vec::with_capacity(size);
let ptr = buffer.as_mut_ptr();
std::mem::forget(buffer); // Prevent Rust from deallocating the memory
ptr
}

#[wasm_bindgen]
pub fn deallocate_memory(ptr: *mut u8, size: usize) {
unsafe {
let _ = Vec::from_raw_parts(ptr, size, size); // Reclaim the memory
}
}

#[wasm_bindgen]
pub fn process_data(ptr: *mut u8, size: usize) -> Result<JsValue, JsValue> {
let data = unsafe { Vec::from_raw_parts(ptr, size, size) };
let sum: u8 = data.iter().sum();
Ok(JsValue::from(sum))
}
  • JavaScript 中的代码:
import init, { allocate_memory, deallocate_memory, process_data } from './pkg/your_project_name.js';

async function run() {
await init();

const size = 10;
const ptr = allocate_memory(size);
const memory = new Uint8Array(wasm_bindgen.memory.buffer, ptr, size);

for (let i = 0; i < size; i++) {
memory[i] = i + 1;
}

const result = process_data(ptr, size);
console.log(result); // Should output the sum of the array

deallocate_memory(ptr, size);
}

run();

8. 宏简化

使用 #[wasm_bindgen(module = "...")]#[wasm_bindgen(inline_js = "...")] 等宏来简化模块导入和内联 JavaScript 代码。

示例

  • 内联 JavaScript 代码:
use wasm_bindgen::prelude::*;

#[wasm_bindgen(module = "/js/foo.js")]
extern "C" {
fn foo();
}

#[wasm_bindgen(start)]
pub fn main() {
foo();
}
  • foo.js 中:
export function foo() {
console.log("Hello from JavaScript!");
}

综合示例

以下是一个综合示例,展示了如何结合这些用法:

  • 在 Rust 中:
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{console, window, Element};

#[wasm_bindgen(module = "/js/foo.js")]
extern "C" {
fn js_add(a: i32, b: i32) -> i32;
}

#[wasm_bindgen]
pub fn add_in_rust(a: i32, b: i32) -> i32 {
js_add(a, b)
}

#[wasm_bindgen]
pub async fn fetch_data() -> Result<JsValue, JsValue> {
let window = window().expect("no global `window` exists");
let response = JsFuture::from(window.fetch_with_str("https://api.example.com/data")).await?;
let response: web_sys::Response = response.dyn_into().unwrap();
let json = JsFuture::from(response.json()?).await?;
Ok(json)
}

#[wasm_bindgen(start)]
pub fn main() {
console::log_1(&"Wasm module initialized".into());
}
  • foo.js 中:
export function js_add(a, b) {
return a + b;
}

通过这种方式,你可以充分利用 wasm-bindgen 的功能,构建强大且高效的 WebAssembly 应用程序。

#[wasm_bindgen(start)]宏来指定一个初始化函数,这个函数会在 WebAssembly 模块加载时自动调用。

总结

wasm-bindgen 提供了多种方式来实现 Rust 和 JavaScript 之间的互操作性。 通过这些方式,你可以轻松地在 Rust 和 JavaScript 之间传递数据、调用函数、处理异步操作等。 这些功能使得 WebAssembly 成为一个强大的工具,可以用于构建高性能的 Web 应用程序。

鱼雪

WebAssembly(简称为Wasm)是一种可移植、高性能的二进制格式,它被设计成一种用于Web浏览器的低级编程语言。WebAssembly 是一种与编程语言无关的格式,这意味着你可以使用多种编程语言来编写WebAssembly模块。目前,主要的编程语言,如C、C++和Rust,已经支持WebAssembly。WebAssembly 的二进制格式是一种紧凑且高效的表示,使得它能够更快地加载和执行。由于它是一种低级格式,直接在虚拟机中执行,因此通常比使用高级语言编写的JavaScript更快。

为什么需要WASM

如果我问你:Javascript是什么?你的答案是什么?

一种变成语言,对吗?

这个答案是正确的但不完全正确。让我们看看是为什么?

在当今世界,直接在浏览器里面写JS代码很少见。有时候你可能写Typescript并且转换成Javascript

有时候你使用flutter构建web应用,也是将dart代码编译成Javascript,同样Kotlin,CoffeeScript, Elm等都是这样

也就是说Javascript是唯一在浏览器上运行的编程语言(假设没有WASM),任何想要针对浏览器和语言和框架都必须转为Javascript

但Javascript不是被设计成编译目标(Compilation Target),并且它没有针对此用例进行优化,WASM是更好的编译目标(Compilation Target)

什么是WASM

WebAssembly 是一种新型代码,可以在现代 Web 浏览器上运行, 它是一种低级代码具有紧凑二进制格式的类汇编语言,可以以接近本机的性能运行 并为 C/C++、C# 和 Rust 等语言提供编译目标,以便它们可以在网络上运行。

Wasm 和 WASI 之间的关系几乎与 JS 和 NodeJS 之间的关系相同。

事实上,WASM 与 JS 一样,在浏览器沙箱内运行时的访问权限受到限制。

例如,它无法读取系统上的文件或执行比 HTTP 更低级别的网络工作, 例如 TCP 和 UDP。 为什么,因为它的 API 不存在于浏览器上。

什么是WASI

WASI可以操作浏览器中不存在的系统API

WasmTime的运行时(Runtime)可以在浏览器外以CLI程序的形式运行WASM文件。 这个用于系统任务(如读取文件和使用网络等)的WASM CLI程序使用WASI定义的标准。 这些标准与WASM本身一样,独立于平台,这一点非常重要。 以致于Docker的创建者在推特上写到:

WASI不仅仅适用于云计算,未来我们还会看到需要WASM运行时才能运行的GUI和CLI程序,因为 通过这种方式,开发人员可以更轻松获得输出结果,并在一个操作系统上进行测试,然后就可以放心了。 因为他的程序在任何地方都能以同样的方式运行。

鱼雪