21 คะแนน โดย tsboard 2024-07-24 | 5 ความคิดเห็น | แชร์ทาง WhatsApp

ผมใช้ 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 ความคิดเห็น

 
ranolp 2024-07-28

สำหรับการจัดการข้อผิดพลาด หากเป็นไลบรารี การติดตั้งและใช้ snafu/thiserror จะสะดวก ส่วนแอปพลิเคชันก็จะสะดวกถ้าติดตั้งและใช้ eyre/anyhow

 
y15un 2024-07-26

ความยากของผู้เขียนไลบรารี: [..snip..] ทุกครั้งที่เพิ่ม dependency การต้องคอยเพิ่มประเภทข้อผิดพลาดของแต่ละฟังก์ชันเข้าไปใน wrapper error type นั้นน่ารำคาญเป็นพิเศษ

ส่วนนี้โดนใจแบบเจ็บลึกจริง ๆ ครับ ผมเองก็ไม่ได้มีแค่ครั้งสองครั้งที่สร้าง enum error เฉพาะของ crate ขึ้นมา แล้วต้องมานั่งเขียน impl From<ExtError> for Error ทุกครั้งสำหรับ error type ที่ดึงมาจาก dependency พร้อมคิดในใจว่า 'โคตรน่ารำคาญเลย'...

 
eususu 2024-07-26

อาจเป็นเพราะผมยังเริ่มต้นได้ไม่จริงจังพอ เลยอยากลองรู้สึกผิดหวังแบบนี้บ้าง
ขอบคุณสำหรับบทความดี ๆ ครับ~

 
undercat 2024-07-25

ขอบคุณสำหรับบทความดี ๆ ครับ!

 
tsboard 2024-07-24

ขอเสริมเพราะคิดว่าคอมเมนต์ด้านล่างน่าจะช่วยเรื่องเวลาในการคอมไพล์ที่นานได้: (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 ของกันและกันกลับมาใช้ซ้ำได้ แต่เรื่องนี้อาจทำได้ค่อนข้างยาก