ผมใช้ Rust มาราว ๆ 10 ปีแล้ว และรักภาษานี้มากจริง ๆ แต่ก็ยังมีบางอย่างที่น่าผิดหวังอยู่ ด้านล่างคือรายการเหล่านั้น
1. ปัญหาของ Result<T, E>
การจัดการข้อผิดพลาดของ Rust ที่ชัดเจนและบังคับใช้นั้นยอดเยี่ยมมาก แต่เมื่อใช้งานจริงกลับมีความไม่สะดวกอยู่ไม่น้อย
- ความยากสำหรับผู้เขียนไลบรารี: กระบวนการสร้างและแปลงชนิดข้อผิดพลาดใหม่ค่อนข้างยุ่งยาก ทุกครั้งที่เพิ่ม dependency ก็ต้องเพิ่มชนิดข้อผิดพลาดของแต่ละฟังก์ชันเข้าไปในชนิดข้อผิดพลาดแบบ wrapper ซึ่งน่ารำคาญเป็นพิเศษ
- ความยุ่งยากในโค้ดแอปพลิเคชัน: สิ่งสำคัญมักไม่ใช่ว่าฟังก์ชันล้มเหลวเพราะอะไร แต่คือการส่งต่อข้อผิดพลาดขึ้นไปยังระดับบนและแสดงผลให้ผู้ใช้เห็น ต่างจาก Java ตรงที่ Rust ไม่ให้ backtrace ระหว่างกระบวนการส่งต่อ ทำให้หาสาเหตุของปัญหาได้ยาก
2. ความยืดหยุ่นของระบบโมดูล
ระบบโมดูลของ Rust ยืดหยุ่นมากเกินไปจนบางครั้งกลับทำให้ใช้งานลำบาก
- ยืดหยุ่นเกินไป: แม้จะสามารถ re-export type หรือปรับระดับการเข้าถึงได้อย่างละเอียด แต่สิ่งนี้ก็อาจนำไปสู่การเผลอเปิดเผย type ที่ไม่ต้องการโดยไม่ได้ตั้งใจ
- ปัญหาของ orphan rule: แม้จะแนะนำให้แบ่งโปรเจ็กต์ออกเป็นหลาย crate แต่ orphan rule ก็กลายเป็นอุปสรรคในบางครั้ง
3. เวลาในการคอมไพล์และเครื่องมือ IDE
เวลาในการคอมไพล์ของ Rust และการตรวจข้อผิดพลาดของเครื่องมือ IDE นั้นช้าเกินไป
- เวลาในการคอมไพล์นาน: ในโปรเจ็กต์ขนาดใหญ่ แก้เพียงฟังก์ชันเดียวก็อาจทำให้ต้องคอมไพล์ทั้ง crate ใหม่ทั้งหมด ซึ่งไม่มีประสิทธิภาพอย่างมาก
- การตอบสนองของ IDE ที่ช้า: Rust analyzer ให้ความรู้สึกราวกับว่ามันต้องจัดทำดัชนีโปรเจ็กต์ใหม่ทุกครั้งที่พิมพ์ ซึ่งเป็นปัญหาอย่างยิ่งในโปรเจ็กต์ขนาดใหญ่
บทสรุป
Rust เป็นภาษาที่ผมชอบที่สุด แต่ก็ยังมีจุดที่น่าผิดหวังเหล่านี้อยู่ ผมสงสัยว่าผู้ใช้อื่น ๆ ก็เจอปัญหาแบบเดียวกันหรือไม่
5 ความคิดเห็น
สำหรับการจัดการข้อผิดพลาด หากเป็นไลบรารี การติดตั้งและใช้ snafu/thiserror จะสะดวก ส่วนแอปพลิเคชันก็จะสะดวกถ้าติดตั้งและใช้ eyre/anyhow
ส่วนนี้โดนใจแบบเจ็บลึกจริง ๆ ครับ ผมเองก็ไม่ได้มีแค่ครั้งสองครั้งที่สร้าง
enumerror เฉพาะของ crate ขึ้นมา แล้วต้องมานั่งเขียนimpl From<ExtError> for Errorทุกครั้งสำหรับ error type ที่ดึงมาจาก dependency พร้อมคิดในใจว่า 'โคตรน่ารำคาญเลย'...อาจเป็นเพราะผมยังเริ่มต้นได้ไม่จริงจังพอ เลยอยากลองรู้สึกผิดหวังแบบนี้บ้าง
ขอบคุณสำหรับบทความดี ๆ ครับ~
ขอบคุณสำหรับบทความดี ๆ ครับ!
ขอเสริมเพราะคิดว่าคอมเมนต์ด้านล่างน่าจะช่วยเรื่องเวลาในการคอมไพล์ที่นานได้: (by pr4wl)
หาก Rust analyzer ทำการรีคอมไพล์ครั้งใหญ่ทุกครั้งที่มีการเปลี่ยนแปลง นั่นอาจเป็นเพราะฟีเจอร์หรือ environment variable ที่ใช้ต่างจากตอนที่บิลด์แอปพลิเคชัน โดยปกติแล้ว RA จะใช้ target directory เดียวกับ
cargo buildในการเก็บ build artifacts และถ้ามีการบิลด์ที่เข้ากันไม่ได้สลับกันไปมา ก็จะลงเอยด้วยการต้องบิลด์ใหม่ทั้งชุดซ้ำ ๆปัญหานี้มักเกิดขึ้นได้บ่อยโดยเฉพาะใน Bevy เมื่อใช้ฟีเจอร์
bevy/dynamic_linkingตอนบิลด์ แต่ไม่ได้ใช้กับ Rust analyzerวิธีแก้ที่ง่ายที่สุดคือกำหนดให้ RA ใช้ target directory คนละตัว รายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้ดูได้ที่
rust-analyzer.cargo.targetDirอีกวิธีหนึ่งคือทำให้ทุกฟีเจอร์และ environment variable ตั้งค่าเหมือนกันทั้งหมด เพื่อให้สามารถนำ build artifacts ของกันและกันกลับมาใช้ซ้ำได้ แต่เรื่องนี้อาจทำได้ค่อนข้างยาก