กว่าจะทำให้ enum with fields ใน Rust สามารถใช้ใน WebAssembly แบบมี TypeScript types ออกมาครบได้ก็ผ่านไป 3 เดือน
ตอนเราเริ่มโปรเจค เราเจอว่าเกือบทุก data structures ในโค้ดเรา จะต้องโมเดลเป็น enum with fields เพราะว่า WebAssembly ใช้กับ trait objects ไม่ได้ ถึงได้ก็ต้องใช้ serde-erased ซึ่งมันไม่สวย
ตัว wasm-bindgen จริงๆ ไม่รองรับการแปลง enum with fields ให้สามารถส่งไปกลับ WebAssembly ได้ แต่ไปเจอมาใน docs แรกของ wasm-bindgen มีบอกไว้ว่าเราสามารถใช้ serde-wasm-bindgen ในการแปลง serializable objects ไปมาได้
โอเค เจอปัญหาแรกว่าหน้าตา enum มันน่าเกลียดมาก เป็นแบบที่เรียกว่า "externally tagged" เช่น { Data: { body: [] } } ซึ่งใช้กับ TypeScript โคตรลำบาก ต้องเขียน type guards มา downcast จนปวดหัว แถมไม่ generate TypeScript types ให้อีก
ก่อนหน้านี้โค้ดแย่มาก มี type duplication สองสามที่ ต้องมี type hacks มี type guards เต็มไปหมด พยายามใช้ bindgen ให้มันเจน type ให้ แต่ representation มันก็ไม่เหมือนกับ serde อีก
สัปดาห์ก่อน เราไปเจอว่า Serde เองรองรับการเซ็ต enum representations ได้หลายแบบมาก แบบที่เราชอบที่สุดคือ "internally tagged" ซึ่งในฝั่ง TypeScript เราเรียกว่าเป็น "discriminated union" ที่มี tag เป็น discriminator ให้เราแยกออก ซึ่งท่านี้ทำให้โค้ดสวยขึ้นเยอะ
เมื่อคืนมีโอกาสลอง implement ดู เลยไปเจอว่ามี library ชื่อ Tsify ซึ่งตอนแรกนึกว่า lib มันตายแล้วเลยไม่ได้ใช้ แต่เอาจริงๆ คือใช้โคตรดี
ความเจ๋งคือมันจะ generate TypeScript typings แบบสวยๆ ให้เลย ซึ่งเราสามารถใส่ namespace attribute เพื่อให้มันแยกแต่ละ union ออกมาให้เป็น type แยก เช่น type Data = "", type Read = "" ใน namespace ที่ชื่อเดียวกับ enum ซึ่งทำให้ DX ดีมาก
นอกจากนั้น พอเราใช้ into_wasm_abi, from_wasm_abi แล้วตั้ง features เป็น "js" มันจะทำให้มันสามารถแปลง enum with fields ของเราจาก WebAssembly เข้ามาที่โค้ดได้เอง ไม่ต้องไปเรียก to_value, from_value ของ serde-wasm-bindgen เองแล้ว
สรุปคือตอนนี้โค้ดสวยมาก ใช้ Rust เป็น source of truth ของ TypeScript types ได้เลย โปรเจคหน้าเริ่มอยากใช้ Rust ในงาน production grade แล้ว แค่เซ็งว่าทำไม documentation มันไม่บอกทั้งหมดนี้เนี่ย กว่าจะเจอก็สามเดือน นั่ง refactor ถึงตีสามเมื่อคืน