Goish: พอร์ตรันไทม์ของ Go ทั้งตัวลงใน no_std Rust Chanwit Kaewkasi, an engineer from Thailand, has released Goish, a Rust runtime that ports Go 1.25's standard library and runtime to no_std Rust, eliminating the garbage collector and relying on Rust's ownership for memory management. The project aims to provide provenance at the function level, addressing compliance requirements like the EU Cyber Resilience Act and FIPS 140-2, by annotating each ported function with its source location in the Go tree and verifying it via CI. 📅 เขียนเมื่อ: สิงหาคม 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 นิยามสั้น ๆ ที่เจ้าตัวเขียนไว้ในหน้าแรก: "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 เกิด ส่วนนี้คือความเจ๋งทางวิศวกรรม — ถ้าไม่อยากอ่านเชิงลึก ข้ามไปหัวข้อถัดไปได้เลย runtime/proc.go Goish ไม่ได้ "เขียน scheduler ใหม่ให้คล้าย Go" — มัน แปล runtime/proc.go ของ Go 1.25 มาทีละบรรทัด : runqgrab / runqsteal / stealOrder sysmon ตัวจัดการ timer heap + force-preemptGoish เอา โครงสร้าง 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 จุดเด่นที่สุดสำหรับคนเขียน Go — public API ใช้ type ตัวพิมพ์เล็กแบบ Go แทน type ของ Rust: php 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