Rust สำหรับระบบไฟล์
(lwn.net)- ในงาน LSFMM+BPF Summit ปี 2024 มีการหารือแนวทางนำ Rust มาใช้กับระบบไฟล์ของ Linux และแพตช์ RFC ฉบับที่สองซึ่งออกมาต่อจาก RFC เดือนธันวาคม 2023 ก็เป็นศูนย์กลางของการถกเถียง
- ฝั่ง Rust-for-Linux ต้องการใส่ข้อกำหนดของ API ระบบไฟล์ลงใน ระบบชนิดของ Rust เพื่อให้ตรวจจับข้อผิดพลาดได้ตั้งแต่คอมไพล์ จัดการการคืนทรัพยากรอัตโนมัติ และลดช่องโหว่ที่เกี่ยวข้องกับหน่วยความจำ
- กรณีของ
iget_locked()แสดงให้เห็นทิศทางที่get_or_create_inode()ใน Rust จะใช้ ชนิดข้อมูลและการจัดการอัตโนมัติ เพื่อบังคับเรื่องการตรวจสอบ null การแยกสถานะ inode และการจัดการความล้มเหลว ซึ่งเดิมผู้เรียกใน C ต้องจัดการเอง - Dave Chinner, Christian Brauner, James Bottomley, Ted Ts'o และคนอื่น ๆ กังวลเรื่องชื่อที่ไม่สอดคล้องกันระหว่าง C API กับ Rust API การซิงก์ API ความต่างของวงจรชีวิตออบเจ็กต์ และภาระการดูแลรักษาเมื่อมี ระบบไฟล์มากกว่า 50 ตัว
- แกนกลางของความขัดแย้งไม่ใช่ข้อดีของ abstraction ใน Rust เอง แต่เป็นเรื่องว่าเมื่อโค้ด C เปลี่ยนไปเรื่อย ๆ ใครจะเป็นผู้รับภาระของ ความเจ็บปวดในการตามให้ Rust binding และ abstraction สอดคล้องกัน
เซสชัน Rust สำหรับระบบไฟล์ใน LSFMM+BPF
- ในงาน Linux Storage, Filesystem, Memory Management, and BPF Summit ปี 2024 Wedson Almeida Filho และ Kent Overstreet ได้พูดถึงแนวทางใช้ Rust กับระบบไฟล์ของ Linux
- Almeida เคยส่งชุดแพตช์ RFC สำหรับ abstraction ของระบบไฟล์ใน Rust เมื่อเดือนธันวาคม 2023 และแนวทางนี้ก็มีความเห็นที่แตกต่างกันอยู่
- ในวันเดียวกับที่มีเซสชันช่วงกลางเดือนพฤษภาคม Almeida ได้ส่งแพตช์ RFC เวอร์ชันที่สองขึ้นมาเพื่อให้หารือร่วมกับหัวข้อ Rust อื่น ๆ
เป้าหมายของ abstraction ระบบไฟล์ใน Rust-for-Linux
- abstraction ระบบไฟล์ที่เสนอสะท้อนทิศทางที่โครงการ Rust-for-Linux project ต้องการผลักดัน
- แกนหลักคือการแสดงข้อกำหนดของ API ระบบไฟล์ให้มากขึ้นผ่าน ระบบชนิดของ Rust เพื่อจับความผิดพลาดได้ตั้งแต่ขั้นคอมไพล์
- ยังต้องการทำงานบางอย่างให้เป็นอัตโนมัติ ซึ่งในโค้ด C ทำได้ยาก
- เช่น การคืนทรัพยากร
- เป้าหมายคือทำให้ประสบการณ์พัฒนาระบบไฟล์มีประสิทธิภาพมากขึ้น ลดเวลาที่ใช้ดีบักปัญหาที่คอมไพเลอร์สามารถหาได้ และลด ช่องโหว่ที่เกี่ยวข้องกับหน่วยความจำ
- Overstreet บอกว่าเขาเจอการไล่บั๊กที่กินเวลาสองสัปดาห์ใน bcachefs บ่อยเกินไป และมองว่า Rust ให้อะไรได้มากกว่า C
- Rust กำจัดพฤติกรรมที่ไม่ถูกกำหนดไว้
- ให้ความสามารถในการมองเห็นว่าเกิดอะไรขึ้นภายในโค้ด
- หากสามารถพิสูจน์ความถูกต้องของโค้ด Rust ได้ บั๊กที่ขัดขวางการพัฒนาฟีเจอร์ก็น่าจะลดลงมาก
กรณีศึกษาระบบชนิดรอบ iget_locked()
- Almeida ยกตัวอย่างในสไลด์ว่าฟังก์ชัน
iget_locked()ในเคอร์เนลปัจจุบันมีข้อกำหนดที่ซับซ้อน - ผู้เรียกใน C ต้องจัดการหลายเงื่อนไขด้วยตัวเอง
- ต้องตรวจว่าค่าที่คืนมาเป็น null หรือไม่
- ต้องตรวจว่า
struct inodeที่คืนมาเป็น inode ใหม่หรือ inode เดิม - ถ้าเป็น inode ใหม่ ต้องทำการ initialize ก่อนใช้งาน
- หาก initialize ไม่สำเร็จ ต้องเรียก
iget_failed()
- Al Viro ไม่เห็นด้วยกับข้อกำหนดบางส่วนของผู้เรียก
iget_locked()ที่อยู่ในสไลด์ของ Almeida และก็มีการโต้เถียงกันต่อในรายละเอียดของพฤติกรรม - Overstreet มองว่าหากครอบกฎเหล่านี้ไว้ใน ชนิดและ abstraction ของ Rust คอมไพเลอร์ก็จะสามารถบังคับให้จัดการอย่างถูกต้องได้
- ฟังก์ชันฝั่ง Rust ที่ Almeida เสนอเป็นคู่กันคือ
get_or_create_inode()- เช่นเดียวกับ C ยังต้องตรวจว่าล้มเหลวหรือไม่
- หากสำเร็จ ผู้เรียกจะได้รับ inode แบบนับอ้างอิงปกติ หรือไม่ก็ inode ใหม่
- สำหรับ inode ปกติ ตัวนับอ้างอิงจะถูกลดลงอัตโนมัติเมื่อไม่มีการอ้างอิงออบเจ็กต์อีกต่อไป
- สำหรับ inode ใหม่ หากไม่ได้ initialize จะมีการเรียกการจัดการที่เทียบเท่า
iget_failed()โดยอัตโนมัติ - เมื่อ inode ใหม่ถูก initialize แล้วหนึ่งครั้ง มันจะกลายเป็น inode ปกติ และหลังจากนั้นจะใช้การลดตัวนับอ้างอิงอัตโนมัติ
- พฤติกรรมเหล่านี้ถูกบังคับผ่าน ระบบชนิด
- Viro ตั้งคำถามว่าข้อจำกัดเหล่านี้จะถูกนิยามไว้ตรงไหนในซอร์สโค้ดจริง
- Almeida ตอบว่าเขาต้องการทำความเข้าใจข้อจำกัดจาก Viro และนักพัฒนาระบบไฟล์คนอื่น ๆ ก่อน แล้วจึงสร้างชนิดและ abstraction ที่บังคับสิ่งเหล่านั้นได้
รอยขาดระหว่าง C API และ Rust API
- Dave Chinner มองว่าหากชื่อของ C API และ Rust API ต่างกัน นักพัฒนาที่คุ้นกับโค้ด C จะดูแล้วไม่รู้ว่าคำเรียกฝั่ง Rust ที่สอดคล้องกันคืออะไร
- ยังมีความกังวลด้วยว่าถ้าไม่ใช้ชื่อเดียวกัน API จะกลายเป็นสิ่งที่แปลกใหม่โดยสิ้นเชิงสำหรับชุมชนนักพัฒนาเดิม
- เมื่อโค้ด C เปลี่ยน โค้ด Rust ก็ต้องเปลี่ยนตามด้วย จึงเกิดคำถามว่าใครจะเป็นคนรับงานนั้น
- Almeida ยอมรับว่านี่เป็นประเด็นที่ต้องคุยกัน
- เขาไม่ได้คัดค้านการเปลี่ยนชื่อ
- แต่ก็ไม่คิดว่า
iget_locked()เป็นชื่อที่ดีนัก และอาจมองว่าเป็นโอกาสในการตั้งชื่อที่ดีกว่า
- Viro มองว่าการเลือก
iget_locked()มาเป็นตัวอย่างไม่ค่อยเหมาะ เพราะมันไม่ใช่เมธอดสมาชิกของออบเจ็กต์ superblock แต่เป็นฟังก์ชันไลบรารี - Almeida ตอบว่า
get_or_create_inode()ก็สามารถทำให้เป็นฟังก์ชันไลบรารีได้เช่นกัน และตัวอย่างนี้มีไว้เพื่อแสดงวิธีเข้ารหัสข้อจำกัดลงในชนิดข้อมูล
ทางเลือกระหว่าง abstraction แบบครอบจักรวาลกับแนวทางเน้นระบบไฟล์แบบเรียบง่าย
- Christian Brauner มองว่าต้องตัดสินใจก่อนว่า abstraction ของ Rust จะเป็น abstraction ครอบจักรวาลสำหรับทุกระบบไฟล์ในเคอร์เนล หรือจะเน้นความสามารถที่จำเป็นสำหรับระบบไฟล์ที่เรียบง่ายกว่าและเขียนด้วย Rust
- ในระยะยาว ถ้าฟังก์ชันอย่าง
get_or_create_inode()มีข้อจำกัดมากกว่าiget_locked()มาก ก็อาจกลายเป็นปัญหาได้ - โค้ด C โดยเฉพาะในช่วงแรกอาจพัฒนาเร็วกว่าโค้ด Rust ทำให้ต้องคอย ซิงก์ API ทั้งสองฝั่งตลอดเวลา
- Overstreet มองว่าประเด็นสำคัญคือ เมื่อเพิ่ม abstraction ของ Rust แล้วจะทำ refactoring และจัดระเบียบไปพร้อมกันหรือไม่ และเขาเห็นอย่างชัดเจนว่านั่นเป็นสิ่งจำเป็น
- James Bottomley มองว่าวงจรชีวิตของออบเจ็กต์ถูกเข้ารหัสอยู่ใน Rust API แต่ไม่มีสิ่งที่เทียบกันได้ใน C
- ถ้าวงจรชีวิตออบเจ็กต์เปลี่ยนในฝั่งหนึ่ง ก็อาจทำให้เกิดบั๊กในอีกฝั่งได้
- Chinner บอกว่าวงจรชีวิตของออบเจ็กต์ inode บางครั้งก็แตกต่างกันไปตามแต่ละระบบไฟล์
- หากใส่ความเข้าใจเรื่องวงจรชีวิตแบบเดียวลงไปใน API ฟังก์ชันเหล่านั้นอาจใช้ไม่ได้กับบางระบบไฟล์
- Almeida ตอบว่าตัวอย่างนี้จะใช้เฉพาะกับระบบไฟล์ที่เรียก
iget_locked()อยู่แล้วและจะได้ประโยชน์จากมัน- นักพัฒนา Rust ไม่ได้พยายามบังคับให้ระบบไฟล์เปลี่ยนวิธีที่ใช้อยู่ในปัจจุบัน
“ใครจะเป็นคนรับภาระความเจ็บปวด”
- Ted Ts'o บอกว่ามันดูเหมือนมีความพยายามจะชักชวนทุกคนให้หันมานับถือ “ศาสนา” Rust แต่ใน Linux มี ระบบไฟล์มากกว่า 50 ตัว และคงไม่เปลี่ยนกันได้ทันที
- โค้ด C จะยังถูกปรับปรุงต่อไป และถ้าความเปลี่ยนแปลงนั้นทำให้ Rust binding พัง ระบบไฟล์ที่พึ่งพา binding นั้นก็อาจพังไปด้วย
- Ts'o มองว่าในช่วงนี้ Rust binding ยังเป็นพลเมืองชั้นสอง และถ้า Rust binding พังก็เป็นปัญหาของนักพัฒนา Rust-for-Linux ไม่ใช่ปัญหาของทั้งชุมชนระบบไฟล์
- เขามองว่าภายใน 1-2 ปี การพัฒนา Rust binding ควบคู่กับวิวัฒนาการของโค้ด C จะทำให้เห็นชัดเองว่าแนวทางใส่ความหมายจำนวนมากลงในระบบชนิดนั้นดีหรือไม่ดี
- สำหรับ Ts'o การเปลี่ยนแปลงครั้งใหญ่สุดท้ายแล้วคือปัญหาเรื่อง การกระจายภาระความเจ็บปวด
- นักพัฒนาที่เปลี่ยน C API อาจบอกได้ว่าตนยอมแก้โค้ด C ที่ได้รับผลกระทบ แต่จะไม่แก้ Rust binding เพราะไม่รู้ Rust
- Almeida ตอบว่าเขาไม่ได้พยายามตรึง C API ไว้ แต่หากนักพัฒนาระบบไฟล์ช่วยอธิบายความหมายของ API เขาก็ต้องการนำสิ่งนั้นมาเข้ารหัสไว้ใน Rust
- Bottomley มองว่ายิ่งเข้ารหัสความหมายไว้ใน binding มากเท่าไร ก็ยิ่งเปราะบางในแง่ของการซิงก์มากขึ้นเท่านั้น
- Almeida ตอบว่าเมื่อ API เปลี่ยน ก็ต้องอัปเดตผู้ใช้ API เช่นเดียวกับผู้ใช้รายอื่นอยู่แล้ว
จะใส่อะไรไว้ในเมธอด ฟังก์ชัน และชนิดข้อมูล
- Viro กลับมาตั้งข้อสังเกตอีกครั้งว่าทางเลือกแทน
iget_locked()นั้นพึ่งพาเมธอด- เขามองว่าเมื่อใช้เมธอด อาร์กิวเมนต์จะไม่ได้ถูกระบุอย่างชัดเจน
- Overstreet มองว่าความไม่ชอบเมธอดนั้นมาจากภาษาอย่าง C++ ที่พึ่งพา inheritance มากเกินไป
- แต่ Rust ไม่ได้เป็นแบบนั้น และเมธอดใน Rust ส่วนใหญ่ก็เป็นเพียงองค์ประกอบทางไวยากรณ์
- Jan Kara แยกความต่างระหว่างพฤติกรรมที่ติดมากับ inode เอง กับพฤติกรรมที่แฝงอยู่ในฟังก์ชัน
iget_locked()- inode มีพฤติกรรมอย่างการนับอ้างอิงและการจัดการที่เกี่ยวข้อง
- ส่วนฟังก์ชัน
iget_locked()ก็มีพฤติกรรมเฉพาะของมันเอง
- Overstreet และ Almeida ตอบว่าทั้งสองส่วนถูกเข้ารหัสไว้ในชนิดข้อมูล แต่แยกจากกัน และฟังก์ชันอื่นที่ใช้ชนิด inode ก็สามารถมีค่าที่ส่งคืนซึ่งมีคุณสมบัติต่างกันได้
- Viro อธิบายว่าทำไม inode ใน VFS จึงทำงานในรูปแบบปัจจุบัน และเห็นด้วยว่าควรเริ่มจากจุดเล็ก ๆ แล้วค่อยดูทิศทางต่อไป
- Overstreet บอกว่าตัวอย่างครั้งนี้อาจซับซ้อนเกินไปจนไม่เหมาะจะใช้เป็นจุดเริ่มต้น ส่วน Viro ตอบว่า “ไม่, ไม่ใช่แบบนั้น” ก่อนที่เซสชันจะจบลง
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ไม่เข้าใจตรงที่บอกว่าแต่ละไฟล์ซิสเต็มมี วงจรชีวิตของ inode แบบกำหนดเอง แต่กลับใช้ฟังก์ชันจัดการวงจรชีวิตเดียวกัน และต่างกันแค่เชิงความหมาย
ถ้าฟังก์ชันเดียวกันต้องถูกใช้ต่างกันตามรายละเอียดการ implement ก็ฟังดูเหมือนตรงข้ามกับเลเยอร์ abstraction
ถ้าวงจรชีวิตของ inode เป็นเรื่องเฉพาะของแต่ละไฟล์ซิสเต็ม ก็ควรจัดการด้วยฟังก์ชันเฉพาะของแต่ละไฟล์ซิสเต็ม
เมื่อรวบรวมข้อมูลแบบนั้นไปเรื่อย ๆ ก็อาจเผยให้เห็นจุดที่ควร refactor เพื่อไม่ให้เกิดคำถามแบบนี้ตั้งแต่แรก ซึ่งเป็นเรื่องดี
เป็นภาพรวมของ เลเยอร์ VFS ที่จัดการพฤติกรรมเฉพาะของแต่ละไฟล์ซิสเต็ม ขณะเดียวกันก็รักษาอินเทอร์เฟซที่สอดคล้องกันไว้ฝั่งเคอร์เนล
วงจรชีวิตของ inode อาจเป็นเพียงตัวอย่างตั้งต้นสำหรับเริ่มการอภิปรายก็ได้
โดยให้คอมไพเลอร์ช่วยเรื่อง reference ชั่วคราว แต่ไฟล์ซิสเต็มยังคงต้องเก็บจำนวนลิงก์ไว้บนดิสก์
iget_locked()ที่พูดถึงตรงนี้เป็นแพตเทิร์นเฉพาะแบบหนึ่งในนั้นไม่ใช่ทุกไฟล์ซิสเต็มที่ใช้วิธีนั้น และบางสถานการณ์ก็ไม่ใช้
เช่น FAT ไม่ใช้ เพราะมันสร้างหมายเลข inode ขึ้นมาเอง และรักษา mapping ของตัวเองจากตำแหน่งใน FAT ไปยัง inode
นอกจากนี้ยังมีไฟล์ซิสเต็มอย่าง
procที่ไม่แคชอ็อบเจ็กต์ inodeตัวอ็อบเจ็กต์ inode เองดูเหมือนจะมีการไหลของสถานะเหมือนกันไม่ว่ามาจากไหน ดังนั้นจากมุมมองของผู้ใช้ วิธีใช้
inodeจึงไม่เปลี่ยนสิ่งที่ต่างกันคือเลเยอร์ไฟล์ซิสเต็มสร้างอ็อบเจ็กต์ inode และจัดการภายในอย่างไร
ผมสงสัยว่าเราอาจกำลังตั้งคำถามผิดอยู่หรือเปล่า
Rust ควรเปลี่ยนให้เรียก C ได้ง่ายขึ้นกว่านี้หรือ?
ผมเคยลอง Rust มาบ้างเล็กน้อย แต่ในมุมของนักพัฒนางานอดิเรก ก็ยังไม่ชัดเจนว่าควรทำงานร่วมกับ C อย่างไร
ในทางกลับกัน ใน C++ หรือ Objective C แค่ include header ที่ถูกต้องแล้วเรียกฟังก์ชันก็ได้แล้ว
Swift สามารถ include ไฟล์ Objective C ได้ และจากตรงนั้นก็เรียก C ได้
ในกรณีนี้ แทนที่จะคาดหวังให้นักพัฒนาเคอร์เนลปรับตัวเข้ากับภาษา ผมคิดว่า ภาษา Rust เองควรยืดหยุ่นขึ้นอีกหน่อยหรือเปล่า
แค่ประกาศฟังก์ชันภายนอกแล้วเรียกใช้งาน
ตัวอย่างเช่น ตามที่อยู่ในหนังสือ Rust ที่ https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin... สามารถประกาศด้วย
extern "C"ได้ถ้าไม่อยากเขียน declaration ทั้งหมดด้วยมือสำหรับไลบรารีที่ซับซ้อน ก็ใช้เครื่องมืออย่าง bindgen ที่สร้าง
externdeclaration จากไฟล์ header ของ C ให้อัตโนมัติได้: https://github.com/rust-lang/rust-bindgenจะโต้แย้งก็ได้ว่าถ้ามีของอย่าง bindgen รวมอยู่ใน Rust ให้ใช้ได้โดยไม่ต้องพึ่ง dependency ภายนอกหรือการตั้งค่า
build.rsก็คงดี แต่ประเด็นหลักของบทความนี้ไม่ใช่เรื่องนั้นปัญหาไม่ใช่ binding ระดับล่าง แต่เป็น wrapper ระดับสูงที่เป็นสไตล์ Rust และไม่มีเครื่องมือทั่วไปใดที่สามารถสร้าง wrapper แบบนั้นจากโค้ด C ใด ๆ ได้โดยอัตโนมัติ
บทความพูดถึงการหาวิธี implement ไดรเวอร์ filesystem ของเคอร์เนล ฯลฯ ด้วย Rust จริง ๆ
อีกประเด็นสำคัญคือโค้ด Rust ภายในเคอร์เนลย่อมต้อง consume interface ของ C อย่างหลีกเลี่ยงไม่ได้
สำหรับ use case ที่นึกถึง bindgen ค่อนข้างเหมาะมาก: https://github.com/rust-lang/rust-bindgen
ถ้าจะเรียกจาก Rust ก็ประกาศ
extern "C" fn foo() -> Tแล้วส่ง link flags ผ่าน attribute#[link]หรือผ่านbuild.rsก็ได้สามารถสร้าง binding ล่วงหน้าด้วย crate
bindgenหรือสร้างในbuild.rsแล้ว include ด้วยinclude!()ได้โดยปกติจะสร้าง crate
-sysที่มีเฉพาะ binding ที่ generate มา แล้วโค้ดจริงก็usebinding จาก sys crate นั้นเหมือนปกติใน C++ และ Objective C ก็ไม่ใช่แค่ include header ที่ถูกต้องเท่านั้น แต่ยังต้อง link กับไลบรารีด้วย
การเรียกไปกลับสองทางทำได้ แต่ถ้า C ไม่สามารถแสดงสิ่งที่ Rust แสดงได้ ก็จะมีผลสำคัญต่อการออกแบบ API ที่ทั้งสองฝั่งต้องใช้ร่วมกัน
แค่ประกาศฟังก์ชัน C เป็น
extern "C"แล้วเรียกใช้โดยปกติต้องใช้
unsafeและต้องแปลง reference เป็น raw pointer หรือ cast แต่ตัว syntax เองเรียบง่ายมีเครื่องมือที่ scan ไฟล์ header ของ C แล้วสร้าง declaration ให้ด้วย และ bindgen เป็นตัวที่ใช้กันมากที่สุด
ประเด็นของบทความนี้ใกล้เคียงกับเรื่องว่าจะใช้ Rust อย่างไรมากกว่าตัวภาษาเอง
นักพัฒนา Rust-for-Linux อยากใช้ความสามารถและ type system ของ Rust เพื่อ encode semantics ของการเรียก API ให้ปลอดภัยขึ้นและเกิดข้อผิดพลาดน้อยลง
ฝั่งคนที่ใช้ C กังวลว่าถ้าทำแบบนั้นแล้วอาจทำให้การพัฒนาพฤติกรรมและ semantics ของ C API ทำได้ยากขึ้น
เพราะเมื่อ C API เปลี่ยน Rust API ก็ต้องแก้ตาม และพวกเขาไม่อยากแบกรับงานนั้น
ทางเลือกที่น่าจะยอมรับได้ง่ายกว่าคือใช้ความสามารถและ type system ของ Rust ให้น้อยลงในการ encode semantics ลงใน Rust API
แบบนั้นเมื่อ C API เปลี่ยน การอัปเดต Rust API ก็จะเป็นงานเชิงกลและง่ายขึ้น แต่ถ้า Rust-for-Linux ไม่สามารถใช้ความสามารถของ Rust เพื่อสร้าง API ที่ดีและปลอดภัยกว่าได้ ก็เกิดคำถามว่างานนี้มีความหมายอะไร
อย่างไรก็ตาม มันก็แปลกนิดหน่อยที่จะพูดเรื่องนี้แบบฟันธง ทั้งที่ยอมรับว่าไม่ได้เข้าใจภาษาอย่างเพียงพอ
ผมไม่ค่อยรู้ filesystem ของ Linux จึงไม่ชัดเจนว่า Rust API นี้เป็นการห่อ C API หรือเป็นการ implement ใหม่
ถ้าเป็นการ implement ใหม่หรือเป็น API แยกต่างหาก การคงชื่อให้เหมือน C API ไว้ดูเหมือนจะยิ่งสร้างความสับสนมากขึ้นเมื่อเวลาผ่านไป
แม้ในช่วงแรกมันอาจช่วยให้นักพัฒนาที่คุ้นเคยเข้าใจได้เร็วขึ้นก็ตาม
iget_locked()ใน Rust และชื่อคือget_or_create_inode()คำตอบดูเหมือนว่าจะเป็นการ implement ใหม่ และไม่ได้ใช้ชื่อเดียวกัน
เมื่อคิดถึงรูปแบบที่การถกเถียงแบบนี้มักจะไหลไปและขนาดของการเปลี่ยนแปลงแล้ว การอภิปรายครั้งนี้ถือว่า สุภาพอย่างน่าประหลาดใจ
ผมไม่เห็นด้วยกับบรรยากาศเชิงลบใน thread นี้
ผมค่อนข้างมองโลกในแง่ดี เพราะผู้เกี่ยวข้องสื่อสารจุดเจ็บปวดหลักได้ชัดเจนโดยไม่พูดเหลวไหล
การอภิปรายจริงคงดุเดือด กระจัดกระจาย และมีการจับผิดกันเยอะ สมกับเป็นการถกเถียงเรื่องภาษาโปรแกรมมิ่งระหว่างพวก geek ที่มีความเห็นแรง ๆ
ดูเหมือน Jake Edge ผู้เขียนสรุปนี้จะเก่งมากในการตัดส่วนเหล่านั้นออกและเขียนเฉพาะแก่นสำคัญ
คอมเมนต์บางส่วนใต้หน้า lwn.net ค่อนข้างหยาบคาย
ลองจินตนาการว่าคุณได้รับคอมเมนต์อย่าง Science advances one funeral at a time ในโปรเจกต์โอเพนซอร์สที่คุณกำลังมีส่วนร่วมอยู่ก็พอ
การที่เคอร์เนล Linux มีตัวเลือกมากขึ้นนั้นเป็นประโยชน์เสมอ
แต่ Rust อาจไม่ใช่คำตอบของทุกสิ่ง
Rust พยายามอย่างเต็มที่เพื่อรับประกันโมเดลการเขียนโปรแกรมที่ปลอดภัย แต่โมเดลนั้นก็มีข้อจำกัด
อาจดูเหมือนว่าถ้าเป็นปัญหาหน่วยความจำก็ใช้ Rust และถ้าเป็นปัญหาคอนเคอร์เรนซีก็เปลี่ยนมาใช้ Rust แต่หากไม่มีบล็อก
unsafeก็ไม่สามารถทำทุกอย่างที่ C ทำได้Rust อาจให้มุมมองใหม่ต่อปัญหาเหล่านี้ได้ แต่ไม่ใช่วิธีแก้ที่สมบูรณ์แบบ
C โดยเฉพาะ C ที่ใช้ในเคอร์เนล โยนความรับผิดชอบให้แต่ละคนต้องรู้กฎโดยนัยทั้งหมดอย่างครบถ้วน
สิ่งนั้นขยายต่อไปไม่ได้
แม้แต่นักพัฒนาเคอร์เนลที่ใช้โครงสร้างข้อมูลเดียวกันมารวมอยู่ในห้องเดียวกัน ก็ยังตกลงกันเรื่องกฎนั้นได้ไม่ครบถ้วน
Rust เก่งในการทำให้กฎที่ต้องรู้ปรากฏชัด และถ้าคนอื่นสามารถรับประกันการปฏิบัติตามกฎได้ ก็ทำให้เรื่องนั้นไม่ใช่ปัญหาของเรา
บางครั้งผลลัพธ์อาจไม่เหมาะที่สุด แต่ในเคอร์เนล Linux ค่าเริ่มต้นที่ไม่เหมาะที่สุดก็ถูกต้องอยู่บ่อยครั้ง และสำหรับคนที่มีแรงพอจะเรียนรู้กฎประหลาดเพิ่มอีกหกข้อเพื่อให้ได้ประสิทธิภาพที่ดีกว่า ก็แค่ให้ทางออกด้วย
unsafeunsafeได้เพียงแต่ควรใช้เฉพาะเมื่อจำเป็นเท่านั้น
การใช้บล็อก
unsafeที่มีขอบเขตผลกระทบจำกัดมาก ไม่ได้หมายความว่าหลักประกันทั้งหมดที่ได้จากโค้ดส่วนที่เหลือจะหายไปunsafeแต่การที่ต้องใช้
unsafeจึงสรุปว่า Rust ไม่เหมาะนั้นเป็นความเข้าใจผิดการแบ่งแยกระหว่าง safe/unsafe ของ Rust มีจุดประสงค์เพื่อทำเครื่องหมายให้ชัดเจนว่าส่วนใดของโค้ดไม่ปลอดภัย เพื่อให้สามารถมุ่งตรวจสอบเฉพาะส่วนเล็ก ๆ นั้น และเชื่อได้ว่าถ้าส่วนนั้นถูกต้อง ส่วนที่เหลือก็จะทำงานได้
ในโค้ดไฟล์ซิสเต็ม มีเหตุผลที่ดีอะไรที่จำเป็นต้องเป็น
unsafeหรือไม่?น่าจะเป็นเซตย่อยเล็กมาก ๆ ที่จำเป็นแค่ไม่กี่จุด
ไม่ค่อยเป็นธรรมชาติที่จะเข้าใจว่าเกิดอะไรขึ้นข้างใน
ดูจากบันทึกการประชุมแล้ว Rust ในเคอร์เนลดูเหมือนเป็น ต้นทุนความซับซ้อนเพิ่มเติม
ถ้าเขียนระบบปฏิบัติการใหม่ตั้งแต่ต้น ก็จะใช้พลังของภาษาได้เต็มที่
แต่เมื่อเอามาเติมข้าง ๆ โค้ดเบสขนาดใหญ่ที่มีอยู่แล้ว ก็เกิดปัญหาเพิ่มเติมอย่างที่เห็นที่นี่
ดูบล็อกที่บอกว่าไดรเวอร์ GPU ของ Asahi Linux ที่เขียนด้วย Rust ถูกสร้างขึ้นในหนึ่งเดือนได้
ค้นหา
tales of the m1 gpuใน Google ก็ได้ และผู้เขียนมีความคิดในแง่ลบอย่างมากต่อ Hacker Newsถ้าต้องการก็อ่านได้จากลิงก์นี้: https://asahilinux.org/2022/11/tales-of-the-m1-gpu/
จะใช้ได้ทั่วไปแค่ไหนต้องดูกันในอีกไม่กี่ปีข้างหน้า
ต้นทุนนั้นจะถูกมองว่าเป็นสิ่งจำเป็นเพื่อยอมรับอนาคตและความก้าวหน้า
สงสัยว่าทำไมจึงไม่จำกัดไว้แค่ เซตย่อยที่ปลอดภัย แทนที่จะกระโดดเข้าไปในกระแสขนาดใหญ่ที่มีบั๊กและการประนีประนอมมากมายที่ยังไม่รู้แน่
ไม่ได้หมายความว่าเพราะมันใหญ่เกินไปแล้ว จึงต้องหยุดนวัตกรรมและเข้าสู่สถานะบำรุงรักษาไปอย่างไม่มีกำหนด
เหมือนภาษีในโลกจริง ถ้าต้นทุนของด้านหนึ่งถูกใช้เพื่อชดเชยปัญหาอีกด้าน ก็อาจไม่ใช่การสูญเสียสุทธิ
การดูแค่การถกเถียงเดี่ยว ๆ ที่ยังไม่ได้ข้อสรุป แล้วบอกว่ามันจะชดเชยปัญหาใด ๆ ไม่ได้เลย ดูเป็นเหตุผลที่สั้นไปหน่อย
ส่วนที่บอกว่าชื่อของ C API กับ Rust API ไม่ตรงกันจนดูโค้ด C แล้วไม่รู้ว่าจะเรียก Rust ที่เทียบเท่ากันอย่างไรนั้น ดูเหมือนเป็นการต่อสู้กับ ธรรมเนียมการตั้งชื่อ แบบเก่า
เคยมีกรณีที่แก้ได้ดีด้วยการคงชื่อเดิมไว้ และเมื่ออยากได้ชื่อทางเลือก ก็ให้ชื่อใหม่ห่อหุ้มชื่อเก่าแทน
ถึงอย่างนั้น การตั้งชื่อก็ยังยากอยู่ดี
อีกสองอย่างที่เหลือคือคอนเคอร์เรนซีและข้อผิดพลาด off-by-one