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”# Install Rust (if not already installed)curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add the WebAssembly target to the Rust toolchainrustup target add wasm32-unknown-unknown
# Install wasm-pack — the all-in-one Rust → Wasm build toolcargo install wasm-packสร้างโปรเจกต์ใหม่
หัวข้อที่มีชื่อว่า “สร้างโปรเจกต์ใหม่”# Create a library crate (not a 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::*;
/// Returns a greeting string.#[wasm_bindgen]pub fn greet(name: &str) -> String { format!("Hello, {}! from WebAssembly", name)}
/// Adds two 32-bit integers.#[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 for use in a bundler (webpack, Vite, Rollup)wasm-pack build --target bundler
# Build for direct use in a browser via <script type="module">wasm-pack build --target web
# Build for 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”// When using --target web (no bundler required)import init, { greet, add } from './pkg/hello_wasm.js';
async function main() { // init() downloads and instantiates the .wasm file await init();
console.log(greet('world')); // Hello, world! from WebAssembly console.log(add(3, 4)); // 7}
main();เมื่อใช้ Bundler เช่น Vite, Import จะเหมือนกันแต่ init() ถูกจัดการอัตโนมัติ:
// Vite / webpack — bundler handles 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 แย่กว่าที่ควรจะเป็น
💡 ตัวอย่างจากของจริง
งาน Cryptography ฝั่ง client และโค้ดที่ critical ด้าน security อื่นๆ ในเบราว์เซอร์คือหนึ่งในเหตุผลหลักที่ทีมเลือกใช้ Rust คอมไพล์เป็น Wasm ตั้งแต่แรก — ได้ทั้ง Memory Safety จาก Ownership Model และ Performance ใกล้เคียง Native โดยไม่ต้องพึ่ง JavaScript ล้วนๆ สำหรับส่วนที่ sensitive