Skip to content

Tích hợp WebAssembly

"Chạy Rust trong trình duyệt với tốc độ gần native."

1. Biên dịch Rust sang WASM

Pipeline biên dịch Rust sang WASM

                    Pipeline Rust sang WASM
┌──────────────────────────────────────────────────────────┐
│                                                          │
│   Rust Source      LLVM IR         WASM Binary           │
│   ───────────      ───────         ───────────           │
│                                                          │
│   lib.rs     ──▶   .ll       ──▶   .wasm                 │
│                                                          │
│   Cargo.toml                       pkg/                  │
│   ├── [lib]                        ├── mylib.js          │
│   └── crate-type = ["cdylib"]      ├── mylib.wasm        │
│                                    └── mylib.d.ts        │
│                                                          │
│   wasm-pack build ─────────────────────────────────────▶ │
│                                                          │
└──────────────────────────────────────────────────────────┘

Thiết lập Project

toml
# Cargo.toml
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"

[profile.release]
lto = true
opt-level = "z"      # Tối ưu cho kích thước

Export WASM cơ bản

rust
// src/lib.rs
use wasm_bindgen::prelude::*;

// Export hàm cho JavaScript
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

Lệnh Build

bash
# Cài đặt target
rustup target add wasm32-unknown-unknown

# Cài đặt wasm-pack
cargo install wasm-pack

# Build cho web
wasm-pack build --target web

# Build cho bundler (webpack, rollup)
wasm-pack build --target bundler

# Build cho Node.js
wasm-pack build --target nodejs

2. wasm-bindgen: Type Bindings

Giới hạn kiểu WASM

                Các kiểu WASM Native
┌────────────────────────────────────────────────┐
│                                                │
│   Chỉ các kiểu số:                             │
│   • i32, i64                                   │
│   • f32, f64                                   │
│                                                │
│   KHÔNG hỗ trợ trực tiếp:                      │
│   • Strings                                    │
│   • Structs                                    │
│   • Arrays                                     │
│   • Objects                                    │
│                                                │
│   wasm-bindgen cầu nối khoảng cách này!        │
│                                                │
└────────────────────────────────────────────────┘

Cách wasm-bindgen hoạt động

                Luồng chuyển đổi kiểu
┌─────────────────────────────────────────────────────────┐
│                                                         │
│   JavaScript            wasm-bindgen            Rust    │
│   ──────────            ────────────            ────    │
│                                                         │
│   "hello"    ──▶    Encode UTF-8     ──▶    &str       │
│              ──▶    Ghi vào WASM memory     ──▶        │
│              ──▶    Truyền pointer + length ──▶        │
│                                                         │
│   { a: 1 }   ──▶    Serialize vào    ──▶    MyStruct   │
│              ──▶    linear memory    ──▶               │
│                                                         │
└─────────────────────────────────────────────────────────┘

Làm việc với Strings

rust
use wasm_bindgen::prelude::*;

// &str: Mượn từ JS (zero-copy đọc)
#[wasm_bindgen]
pub fn count_chars(s: &str) -> usize {
    s.chars().count()
}

// String: Owned, trả về JS
#[wasm_bindgen]
pub fn to_uppercase(s: &str) -> String {
    s.to_uppercase()
}

// Cách nó hoạt động bên trong:
// 1. JS encode string thành UTF-8
// 2. Ghi bytes vào WASM linear memory
// 3. Truyền (ptr, len) cho Rust
// 4. Rust đọc từ memory

Export Structs

rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Point {
    x: f64,
    y: f64,
}

#[wasm_bindgen]
impl Point {
    // Constructor phải đặt tên "new" cho JS class syntax
    #[wasm_bindgen(constructor)]
    pub fn new(x: f64, y: f64) -> Point {
        Point { x, y }
    }
    
    // Getter
    #[wasm_bindgen(getter)]
    pub fn x(&self) -> f64 {
        self.x
    }
    
    // Setter
    #[wasm_bindgen(setter)]
    pub fn set_x(&mut self, x: f64) {
        self.x = x;
    }
    
    // Method
    pub fn distance(&self, other: &Point) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy).sqrt()
    }
}

Sử dụng trong JavaScript

javascript
import init, { Point, add, greet } from './pkg/my_wasm_lib.js';

async function main() {
    // Khởi tạo WASM module
    await init();
    
    // Sử dụng exported functions
    console.log(add(2, 3));        // 5
    console.log(greet("World"));   // "Xin chào, World!"
    
    // Sử dụng exported class
    const p1 = new Point(0, 0);
    const p2 = new Point(3, 4);
    console.log(p1.distance(p2));  // 5
    
    // Quan trọng: Giải phóng WASM memory khi xong
    p1.free();
    p2.free();
}

main();

Import hàm JavaScript

rust
use wasm_bindgen::prelude::*;

// Import hàm JS
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
    
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
    
    #[wasm_bindgen(js_namespace = Math)]
    fn random() -> f64;
}

// Import kiểu JS
#[wasm_bindgen]
extern "C" {
    type HTMLDocument;
    
    #[wasm_bindgen(static_method_of = HTMLDocument, js_class = "document")]
    fn getElementById(id: &str) -> Option<Element>;
}

#[wasm_bindgen]
pub fn greet_alert(name: &str) {
    log(&format!("Chào {}", name));
    alert(&format!("Xin chào, {}!", name));
}

JsValue: Giá trị JavaScript động

rust
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;

// Nhận bất kỳ giá trị JS nào
#[wasm_bindgen]
pub fn process_value(val: JsValue) -> JsValue {
    // Kiểm tra kiểu
    if val.is_null() {
        return JsValue::from_str("giá trị null");
    }
    
    if let Some(num) = val.as_f64() {
        return JsValue::from_f64(num * 2.0);
    }
    
    if let Some(s) = val.as_string() {
        return JsValue::from_str(&s.to_uppercase());
    }
    
    JsValue::UNDEFINED
}

3. Tối ưu kích thước WASM Binary

Chiến lược tối ưu kích thước

toml
# Cargo.toml
[profile.release]
lto = true            # Link-time optimization
opt-level = "z"       # Tối ưu cho kích thước (vs "3" cho tốc độ)
codegen-units = 1     # Giảm parallelism để opt tốt hơn
panic = "abort"       # Không có unwinding code
strip = true          # Xóa symbols

Phân tích kích thước

bash
# Cài đặt công cụ phân tích kích thước
cargo install twiggy

# Phân tích cái gì chiếm không gian
twiggy top -n 20 target/wasm32-unknown-unknown/release/my_lib.wasm

# Hiển thị cây phụ thuộc
twiggy dominators my_lib.wasm

wasm-opt Post-Processing

bash
# Cài đặt binaryen
# macOS: brew install binaryen
# Ubuntu: apt install binaryen

# Tối ưu WASM binary
wasm-opt -Oz -o optimized.wasm input.wasm

# Các Binaryen passes:
# -O0: Không tối ưu
# -O1: Tối ưu nhanh
# -O2: Tối ưu nhiều hơn
# -O3: Tối ưu nhiều nhất (tốc độ)
# -Os: Tối ưu cho kích thước
# -Oz: Tối ưu kích thước mạnh mẽ

So sánh kích thước

                    So sánh kích thước Binary
┌─────────────────────────────────────────────────────────┐
│                                                         │
│   Cấu hình                           Kích thước         │
│   ────────                           ──────────         │
│                                                         │
│   Debug build                        ~2 MB              │
│   Release (mặc định)                 ~500 KB            │
│   Release + LTO                      ~200 KB            │
│   Release + LTO + opt-level=z        ~100 KB            │
│   + wasm-opt -Oz                     ~80 KB             │
│   + wasm-gc (xóa dead code)          ~50 KB             │
│                                                         │
│   Mẹo: Dùng feature flags để                            │
│        biên dịch chỉ những gì cần                       │
│                                                         │
└─────────────────────────────────────────────────────────┘

Giảm Dependencies

rust
// Dùng core/alloc thay vì std khi có thể
#![no_std]

extern crate alloc;
use alloc::vec::Vec;
use alloc::string::String;

// Hoặc tắt default features
// [dependencies]
// serde = { version = "1.0", default-features = false, features = ["alloc"] }

4. Patterns nâng cao

Quản lý Memory

rust
use wasm_bindgen::prelude::*;
use std::alloc::{alloc, dealloc, Layout};

// Expose allocator cho JS quản lý memory
#[wasm_bindgen]
pub fn wasm_alloc(size: usize) -> *mut u8 {
    let layout = Layout::from_size_align(size, 1).unwrap();
    unsafe { alloc(layout) }
}

#[wasm_bindgen]
pub fn wasm_dealloc(ptr: *mut u8, size: usize) {
    let layout = Layout::from_size_align(size, 1).unwrap();
    unsafe { dealloc(ptr, layout) }
}

Streaming Compilation

javascript
// Stream và compile WASM đồng thời
const response = await fetch('module.wasm');
const module = await WebAssembly.compileStreaming(response);
const instance = await WebAssembly.instantiate(module);

Web Workers

javascript
// worker.js
import init, { process } from './pkg/my_lib.js';

self.onmessage = async (e) => {
    await init();
    const result = process(e.data);
    self.postMessage(result);
};

// main.js
const worker = new Worker('worker.js', { type: 'module' });
worker.postMessage(data);
worker.onmessage = (e) => console.log(e.data);

🎯 Best Practices

Khi nào dùng WASM

Use CaseLợi ích WASM
Xử lý ảnhNhanh hơn JS 10-20x
Mã hóaConstant-time operations
Game physicsHiệu năng dự đoán được
Nén dữ liệuCPU-intensive
Encode videoTính toán nặng

Khi KHÔNG nên dùng WASM

  • Thao tác DOM đơn giản (JS nhanh hơn cho glue code)
  • Network requests (dùng Fetch API trực tiếp)
  • Tính toán nhỏ (overhead gọi WASM)