ข้ามไปยังเนื้อหา

Rust และ wasm-bindgen

Rust เป็นภาษาที่ได้รับความนิยมมากที่สุดสำหรับการเขียน WebAssembly Module ใหม่ Zero-Cost Abstractions, ไม่มี Garbage Collector และ Tooling ที่ยอดเยี่ยมทำให้เหมาะกับ Wasm มาก wasm-bindgen Crate สร้าง JavaScript Glue Code อัตโนมัติ และ wasm-pack ห่อ Pipeline การ Build ทั้งหมดไว้ในคำสั่งเดียว

Terminal window
# ติดตั้ง Rust (ถ้ายังไม่มี)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# เพิ่ม WebAssembly Target ลงใน Rust Toolchain
rustup target add wasm32-unknown-unknown
# ติดตั้ง wasm-pack — เครื่องมือ Build Rust → Wasm แบบครบวงจร
cargo install wasm-pack
Terminal window
# สร้าง Library Crate (ไม่ใช่ Binary)
cargo new --lib hello-wasm
cd hello-wasm

เพิ่ม wasm-bindgen ลงใน Cargo.toml:

[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"

เปิด src/lib.rs และเขียนฟังก์ชัน Export อย่างง่าย:

use wasm_bindgen::prelude::*;
/// คืนค่า String ทักทาย
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}! from WebAssembly", name)
}
/// บวกเลขจำนวนเต็ม 32 บิตสองตัว
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

Attribute #[wasm_bindgen] บอก Code Generator ให้สร้าง JavaScript Binding ที่ตรงกัน หากไม่มี Attribute นี้ ฟังก์ชันจะถูกคอมไพล์ลงใน .wasm Binary แต่จะเข้าถึงจาก JS ไม่ได้

Terminal window
# Build สำหรับใช้กับ Bundler (webpack, Vite, Rollup)
wasm-pack build --target bundler
# Build สำหรับใช้โดยตรงในเบราว์เซอร์ผ่าน <script type="module">
wasm-pack build --target web
# Build สำหรับ Node.js
wasm-pack build --target nodejs

ไดเรกทอรี pkg/ จะถูกสร้างขึ้นพร้อมกับ:

pkg/
hello_wasm_bg.wasm ← Wasm Binary ที่คอมไพล์แล้ว
hello_wasm.js ← JS Glue ที่ Generate อัตโนมัติ
hello_wasm.d.ts ← TypeScript Types
package.json ← พร้อม Publish บน npm
// เมื่อใช้ --target web (ไม่ต้องการ Bundler)
import init, { greet, add } from './pkg/hello_wasm.js';
async function main() {
// init() ดาวน์โหลดและ Instantiate ไฟล์ .wasm
await init();
console.log(greet('world')); // Hello, world! from WebAssembly
console.log(add(3, 4)); // 7
}
main();

เมื่อใช้ Bundler เช่น Vite, Import จะเหมือนกันแต่ init() ถูกจัดการอัตโนมัติ:

// Vite / webpack — Bundler จัดการ Instantiation
import { greet, add } from 'hello-wasm';
console.log(greet('WebAssembly'));

wasm-bindgen รองรับมากกว่าแค่ตัวเลข คุณสามารถส่งผ่าน:

  • &str / String — UTF-8 String คัดลอกผ่าน Shared Memory Buffer
  • JsValue — Handle ทึบแสงสำหรับค่า JavaScript ใดก็ได้
  • Vec<u8> — Byte Array ที่ Map กับ Uint8Array
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn reverse_string(s: &str) -> String {
s.chars().rev().collect()
}
ตัวเลือกBenefitCost
Rust + wasm-bindgenMemory Safety จาก Ownership Model, Binary Size เล็ก, JS Binding สร้างอัตโนมัติOwnership/Borrowing เป็น Learning Curve สูงสำหรับคนที่ไม่เคยเขียน Rust มาก่อน
C/C++ + EmscriptenPort Codebase C/C++ ที่มีอยู่แล้วได้เกือบทันทีGlue Code ใหญ่กว่า และมี Runtime Overhead จาก POSIX Emulation Layer
  • สู้กับ Borrow Checker จนเสียเวลามาก แทนที่จะออกแบบ Ownership ให้เรียบง่ายตั้งแต่แรก เช่น clone() พร่ำเพรื่อเพื่อหนี Lifetime Error แทนที่จะแก้โครงสร้างข้อมูล
  • ลืมใส่ #[wasm_bindgen] บนฟังก์ชันที่ต้องการ Export ทำให้ฟังก์ชันคอมไพล์ผ่านลงใน .wasm แต่เรียกจาก JavaScript ไม่ได้
  • เลือก Type ที่ Serialize แพงอย่าง JsValue หรือ Struct ซับซ้อนผ่าน serde สำหรับ Hot Path ที่เรียกบ่อยๆ ทำให้ Performance แย่กว่าที่ควรจะเป็น

💡 ตัวอย่างจากของจริง

1Password ใช้ Rust คอมไพล์เป็น Wasm สำหรับส่วน Core Cryptography และ Vault Logic ที่ต้องรันใน browser เพื่อให้ได้ทั้ง Memory Safety และ Performance ใกล้เคียง Native โดยไม่ต้องพึ่ง JavaScript ล้วนๆ

Attribute #[wasm_bindgen] ใน Rust ทำงานอย่างไร?
Flag --target ใดของ wasm-pack ที่ควรใช้เมื่อต้องการเรียก Module โดยตรงจาก browser ผ่าน <script type="module"> โดยไม่มี Bundler?
wasm-bindgen ใช้ Type ใดเพื่อแทนค่า JavaScript แบบ Opaque ใน Rust?
ต้องระบุ crate-type ใดใน Cargo.toml สำหรับ Wasm Library?