📅 เขียนเมื่อ: สิงหาคม 2026 | Goish (goish.cogentica.ai)
⚠️ โปรเจกต์เพิ่งเปิดตัว — รายละเอียด/เวอร์ชันอาจเปลี่ยนแปลง ตรวจสอบจาก GitHub ก่อนใช้งาน
ถ้าคุณเขียน Go มาสักพัก คุณน่าจะเคยอยากได้สองอย่างพร้อมกัน — concurrency model ของ Go (goroutine, channel, select) กับ ความปลอดภัยด้าน memory ของ Rust (ownership, ไม่มี GC) ปกติแล้วสองอย่างนี้อยู่คนละโลก: Go ให้ runtime มาให้แต่มี garbage collector, Rust ให้ ownership มาแต่ concurrency ต้องประกอบเองจาก async/futures
มีคนพยายามเชื่อมสองโลกนี้เข้าด้วยกันแล้ว — ชื่อโปรเจกต์ว่า Goish ผลงานของ Chanwit Kaewkasi (@chanwit) วิศวกรชาวไทยจากโคราช ภายใต้บริษัท Cogentica AI
Goish คืออะไร
นิยามสั้น ๆ ที่เจ้าตัวเขียนไว้ในหน้าแรก: "A Rust runtime for Go people" — รันไทม์ของ Go สำหรับคนที่เขียน Go
ถ้าขยายให้ละเอียด: Goish คือการพอร์ต standard library และ runtime ของ Go 1.25 ลงไปใน Rust แบบ no_std — ไม่พึ่ง glibc, ไม่พึ่ง std ของ Rust, ไม่พึ่ง Tokio, ไม่มี garbage collector มัน ship ทุกอย่างมาเองตั้งแต่ _start, page allocator, size-class heap, M:N scheduler, channel, select!, sync primitives, ไปจนถึง net/http และ crypto/tls
ผลลัพธ์คือ binary ไฟล์เดียวแบบ static เหมือน Go — ldd รันแล้วบอกว่า "not a dynamic executable"
แต่สิ่งที่ทำให้ Goish ต่างจากโปรเจกต์ "เขียน Go ให้เป็น Rust" ทั่วไป คือวิธีที่มันให้เครดิตกับต้นทาง — เรื่องนี้แหละคือหัวใจของที่มาที่ไป
ทำไมถึงคิดทำตัวนี้ (ที่มาที่ไป)
แรงจูงใจหลักไม่ใช่ "อยากให้ Rust เขียนง่ายเหมือน Go" ธรรมดา ๆ แต่เป็นเรื่องของ compliance และ provenance
ลองนึกภาพ: ทีมคุณเขียน Go อยู่ แล้วต้องย้ายไป Rust (ด้วยเหตุผลเรื่อง memory safety หรือนโยบายบริษัท) ระหว่างทางคุณต้อง "พอร์ต" โค้ด — เอาโค้ดจาก Go มาเขียนใหม่เป็น Rust แล้วคำถามสำคัญก็ผุดขึ้น:
AES-GCM ใน binary ตัวนี้ เป็น implementation ที่ Go review แล้ว หรือเป็นของที่เราเขียนประมาณเอา?
เครื่องมือ supply-chain ยุคปัจจุบันอย่าง SLSA provenance หรือ SBOM ตอบคำถามนี้ไม่ได้ — มันอธิบายแค่ว่า binary ถูก build มาจากไหน (builder ไหน, commit อะไร, digest อะไร) แต่อธิบายไม่ได้ว่าโค้ดข้างในถูก port มาอย่างซื่อสัตย์แค่ไหน SBOM จะบันทึกว่า "goish 0.1.0" แล้วจบ — มันแยกไม่ออกว่า crypto ในนั้นคือของจริงหรือของก๊อป
Goish แก้ด้วยการบันทึกที่มาเป็นฟังก์ชันต่อฟังก์ชัน:
// go: sdk 1.25.5 crypto/internal/fips140/aes/gcm/gcm.go:31-46 newGCM
บรรทัดนี้เขียนไว้เหนือ port แต่ละตัว — บอกว่าโค้ดตรงนี้ port มาจากไฟล์ไหน บรรทัดไหน ใน Go 1.25.5 และมี CI ที่เปิด Go tree ขึ้นมาเช็คทุก commit ว่าตำแหน่งที่อ้างยังถูกต้องอยู่
ทำไมเรื่องนี้ถึงสำคัญตอนนี้? เพราะเส้นตายทางกฎหมายกำลังมา:
| วันที่ | เกิดอะไรขึ้น |
|---|---|
| 11 ก.ย. 2026 | EU CRA (Cyber Resilience Act) บังคับใช้ภาระการรายงาน — แจ้งเตือน 24 ชม. / แจ้งเต็ม 72 ชม. เมื่อเจอช่องโหว่ที่ถูกโจมตีจริง |
| 21 ก.ย. 2026 | ใบรับรอง FIPS 140-2 ย้ายเข้า "Historical List" — ใช้ต่อได้แต่ justify การจัดซื้อภาครัฐใหม่ไม่ได้ |
| 1 ม.ค. 2027 | CNSA 2.0 กลายเป็นค่าเริ่มต้นสำหรับงาน NSS ของสหรัฐฯ |
| 11 ธ.ค. 2027 | ข้อกำหนดสำคัญของ CRA รวมถึง mandate เรื่อง machine-readable SBOM |
พูดง่าย ๆ: ถ้าสินค้าคุณต้องผ่าน compliance พวกนี้ คุณต้องพิสูจน์ได้ว่าโค้ดทุกบรรทัดมาจากไหน — และสำหรับโค้ดที่ "แปล" มาจากที่อื่น การพิสูจน์นั้นต้องลงถึงระดับฟังก์ชัน ไม่ใช่ระดับ build
นี่คือเหตุผลที่ Goish เกิด
ใช้เทคนิคอะไรบ้าง
ส่วนนี้คือความเจ๋งทางวิศวกรรม — ถ้าไม่อยากอ่านเชิงลึก ข้ามไปหัวข้อถัดไปได้เลย
1. M:N Scheduler — พอร์ตแบบ verbatim จาก runtime/proc.go
Goish ไม่ได้ "เขียน scheduler ใหม่ให้คล้าย Go" — มันแปล runtime/proc.go ของ Go 1.25 มาทีละบรรทัด:
- Per-P run queue แบบ lock-free SPMC (256 entry) + global overflow
- Work stealing แบบ coprime-permuted (
runqgrab/runqsteal/stealOrder) - Async preemption ผ่าน SIGURG (เทคนิคเดียวกับที่ Go ใช้หยุด goroutine ที่รันนานเกิน)
-
sysmonตัวจัดการ timer heap + force-preempt - epoll netpoller แบบ per-P (โมเดลเดียวกับ nginx) — เวลา goroutine รอ I/O มันจะ park goroutine ไม่ใช่ block thread
2. Memory: page allocator + 67 size classes (แต่ไม่มี GC)
Goish เอาโครงสร้าง allocator ของ Go (mheap → mcentral → per-P mcache, 67 size classes) มาใช้ แต่ตัด garbage collector ออก — ให้ Rust ownership เป็นคน reclaim แทน
สำหรับ stack ของ goroutine มันใช้วิธีฉลาด: goroutine หนึ่งตัวได้ virtual reservation 1 MiB แบบ lazy-commit (kernel ค่อย commit หน้า 4 KiB เมื่อถูกแตะจริง) — แปลว่า goroutine ที่ทำงานตื้น ๆ เสีย memory จริงแค่ประมาณหนึ่ง page และถ้าอยากได้ความหนาแน่นสุดขีด ก็ใช้ go!(stack(2*KB), …) เพื่อขอ stack ขนาด 2 KiB จาก stack pool
ผลลัพธ์ตาม demo:
ts vmsize_kb vmrss_kb vmpeak_kb vmhwm_kb threads
0s 1105148 44800 1108444 49024 13 ← baseline
2s 3069660 2406528 3069660 2406528 13 ← 1M parked
30s 3069660 2406528 3069660 2406528 13 ← steady-state
goroutine หนึ่งล้านตัว บน OS thread แค่ 13 เธรด — ~2.4 KiB RSS ต่อ goroutine
3. เขียนโค้ดที่ "อ่านแล้วเหมือน Go" แต่ compile ด้วย rustc
จุดเด่นที่สุดสำหรับคนเขียน Go — public API ใช้ type ตัวพิมพ์เล็กแบบ Go แทน type ของ Rust:
fn get(url: string) -> (int, string, string) {
let (mut resp, err) = http::Get(url.clone());
if err != nil {
return (-1, fmt::Sprintf!("get %s: %v", url, err), string(""));
}
let (body, _) = io::ReadAll(&mut resp.Body);
let _ = io::Closer::Close(&mut resp.Body);
return (resp.StatusCode, string(body), resp.Header.Get("Content-Type"));
}
string, int, multi-return, if err != nil — ทั้งหมดคือ Rust จริง ๆ ที่ rustc compile ได้ แต่อ่านแล้วแทบไม่ต่างจาก Go และ Vec<u8> กับ &str ไม่โผล่ใน signature สาธารณะเลย
นี่คือ philosophy หลัก: "Port first, get idiomatic later" — พอร์ตโค้ด Go มาแบบเกือบ line-by-line ก่อน แล้วค่อย ๆ ปรับเป็น Rust idiom ทีละ module เมื่อทีมพร้อม
4. crypto/tls — พอร์ตครบ 100% พร้อมใบเสร็จทุกบรรทัด
ส่วนที่ภาคภูมิใจที่สุดคือ crypto/ — ครบ 1722/1722 declarations (100%) ทั้ง 66 packages รวมถึง crypto/internal/fips140* ทั้ง 35 packages ที่เป็น code path ของ FIPS validation และ net/http ครบ 639/639 functions (100%)
TLS 1.3 ที่รันจริงคือ crypto/tls ที่ port มา verbatim — ไม่ใช่ implementation ใหม่ที่เขียนเลียนแบบ
ประโยชน์ที่ได้
-
Binary เดียว ไม่มี libc ไม่มี ld.so — ลด attack surface เหลือแค่ตัว binary เอง เหมาะกับ
scratch/distroless container, confidential VM, appliance image - Memory safety + concurrency model ของ Go — ได้ ownership ของ Rust (ไม่มี dangling pointer, ไม่มี data race) กับ goroutine/channel/select ของ Go ในตัวเดียว
- Provenance ถึงระดับฟังก์ชัน — พิสูจน์ compliance (EU CRA, FIPS, CNSA) ได้ เพราะทุกฟังก์ชันมีที่มาอ้างอิง
- ไม่มี GC ให้ tune — ไม่มี stop-the-world ไม่มี GC pause
- เส้นทาง migrate จาก Go → Rust แบบค่อยเป็นค่อยไป — ไม่ต้องหยุดทุกอย่างเพื่อ rewrite ใหม่หมด
วิธีเริ่มต้น
ต้องการแค่ Rust 1.79+ และ Linux x86_64 (single-target ณ ตอนนี้)
git clone https://github.com/cogentica-ai/goish
cd goish
# build library
cargo build --target x86_64-unknown-linux-gnu --release
# demo: หนึ่งล้าน goroutine
cargo build --target x86_64-unknown-linux-gnu --release --example spawn_million
./examples/spawn_million.sh
# goginx: nginx clone ที่เขียนด้วย goish
cargo build --target x86_64-unknown-linux-gnu --release --example goginx
โค้ด "Hello" แบบ Goish หน้าตาประมาณนี้:
use goish::{fmt, go};
#[goish::main]
fn main() {
go!(move || {
fmt::Println!("hello from a goroutine");
});
}
ข้อจำกัด (บอกตรง ๆ)
-
ยังไม่ใช่ Go compiler — คุณเขียน Rust ที่อ่านเหมือน Go ไม่ได้ compile ไฟล์
.go - Linux x86_64 เท่านั้น — target อื่นยังไม่อยู่ใน scope
- ยังไม่ผ่าน security audit — TLS stack port มาแบบ machine-checked แต่ยังไม่มี external review (ดู SECURITY.md ก่อนใช้จริง)
-
ยัง port ไม่ครบทั้ง Go —
crypto/กับnet/httpเสร็จแล้ว แต่net,encoding,osยังบางส่วน
สรุป
Goish ไม่ได้เป็นแค่ "Go runtime ใน Rust" — มันเป็นความพยายามตอบคำถามที่ใหญ่กว่านั้น: เมื่อเราต้องย้ายภาษาในโลกที่มีกฎหมายบังคับเรื่องที่มาของโค้ด เราจะพิสูจน์ได้ยังไงว่าโค้ดทุกบรรทัดไว้ใจได้
คำตอบของ Chanwit คือการพอร์ต Go แบบมี "ใบเสร็จ" ทุกบรรทัด แล้วเอา ownership ของ Rust มาแทน GC — ได้ทั้งความคุ้นเคยของ Go, ความปลอดภัยของ Rust, และหลักฐาน compliance ที่ตรวจซ้ำได้
โปรเจกต์นี้เพิ่งเปิด (สิงหาคม 2026) และยังพัฒนาอย่างต่อเนื่อง ถ้าสนใจเข้าไปดูได้ที่:
เครดิต: บทความนี้อ้างอิงข้อมูลจากเว็บไซต์ goish.cogentica.ai และ README ของ github.com/cogentica-ai/goish — โปรเจกต์โดย Chanwit Kaewkasi (@chanwit) ภายใต้ Cogentica AI (ประเทศไทย)
⚠️ Goish ไม่เกี่ยวข้องกับ Google หรือ Go project — "Go" เป็นเครื่องหมายการค้าของ Google LLC
Top comments (0)