Rust และ wasm-bindgen
Rust เป็นภาษาที่ได้รับความนิยมมากที่สุดสำหรับการเขียน WebAssembly Module ใหม่ Zero-Cost Abstractions, ไม่มี Garbage Collector และ Tooling ที่ยอดเยี่ยมทำให้เหมาะกับ Wasm มาก wasm-bindgen Crate สร้าง JavaScript Glue Code อัตโนมัติ และ wasm-pack ห่อ Pipeline การ Build ทั้งหมดไว้ในคำสั่งเดียว
ติดตั้ง Toolchain
หัวข้อที่มีชื่อว่า “ติดตั้ง Toolchain”# ติดตั้ง Rust (ถ้ายังไม่มี)curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# เพิ่ม WebAssembly Target ลงใน Rust Toolchainrustup target add wasm32-unknown-unknown
# ติดตั้ง wasm-pack — เครื่องมือ Build Rust → Wasm แบบครบวงจรcargo install wasm-packสร้างโปรเจกต์ใหม่
หัวข้อที่มีชื่อว่า “สร้างโปรเจกต์ใหม่”# สร้าง Library Crate (ไม่ใช่ Binary)cargo new --lib hello-wasmcd hello-wasmเพิ่ม wasm-bindgen ลงใน Cargo.toml:
[lib]crate-type = ["cdylib"]
[dependencies]wasm-bindgen = "0.2"เขียนฟังก์ชัน Rust
หัวข้อที่มีชื่อว่า “เขียนฟังก์ชัน Rust”เปิด 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 ไม่ได้
Build ด้วย wasm-pack
หัวข้อที่มีชื่อว่า “Build ด้วย wasm-pack”# Build สำหรับใช้กับ Bundler (webpack, Vite, Rollup)wasm-pack build --target bundler
# Build สำหรับใช้โดยตรงในเบราว์เซอร์ผ่าน <script type="module">wasm-pack build --target web
# Build สำหรับ Node.jswasm-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เรียกใช้จาก JavaScript
หัวข้อที่มีชื่อว่า “เรียกใช้จาก JavaScript”// เมื่อใช้ --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 จัดการ Instantiationimport { greet, add } from 'hello-wasm';
console.log(greet('WebAssembly'));ส่งผ่าน Type ที่ซับซ้อน
หัวข้อที่มีชื่อว่า “ส่งผ่าน Type ที่ซับซ้อน”wasm-bindgen รองรับมากกว่าแค่ตัวเลข คุณสามารถส่งผ่าน:
&str/String— UTF-8 String คัดลอกผ่าน Shared Memory BufferJsValue— 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()}ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Rust + wasm-bindgen | Memory Safety จาก Ownership Model, Binary Size เล็ก, JS Binding สร้างอัตโนมัติ | Ownership/Borrowing เป็น Learning Curve สูงสำหรับคนที่ไม่เคยเขียน Rust มาก่อน |
| C/C++ + Emscripten | Port 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 ล้วนๆ