ข้อจำกัดของ Derive Macro ใน Rust จุดประกายการถกเถียงในชุมชนเกี่ยวกับการใช้งาน "Perfect Derive"

ทีมชุมชน BigGo
ข้อจำกัดของ Derive Macro ใน Rust จุดประกายการถกเถียงในชุมชนเกี่ยวกับการใช้งาน "Perfect Derive"

ชุมชนโปรแกรมเมอร์ Rust กำลังหารือกันอย่างคึกคักเกี่ยวกับข้อจำกัดที่มีมายาวนานใน derive macros ของภาษา โดยเฉพาะอย่างยิ่งเรื่อง trait Clone การถกเถียงมุ่งเน้นไปที่ว่าแนวทางปัจจุบันของ Rust นั้นมีข้อบกพร่องพื้นฐานหรือเป็นเพียงการแลกเปลี่ยนที่สมเหตุสมผลที่นักพัฒนาได้เรียนรู้ที่จะปรับตัวให้เข้ากับมัน

ปัญหาหลักของพฤติกรรม Derive ปัจจุบัน

เมื่อคุณใช้ #[derive(Clone)] ใน Rust คอมไพเลอร์จะต้องการให้พารามิเตอร์ generic ทั้งหมดใช้งาน Clone ได้ แม้ว่าพารามิเตอร์เหล่านั้นอาจไม่จำเป็นต้องถูก clone จริงๆ ก็ตาม สิ่งนี้สร้างข้อจำกัดที่ไม่จำเป็นซึ่งป้องกันไม่ให้โค้ดคอมไพล์ได้ในกรณีที่มันควรจะทำงานได้ตามตรรกะ ตัวอย่างเช่น wrapper รอบ Arc<T> ไม่สามารถ derive Clone ได้ แม้ว่า Arc<T> เองจะสามารถ clone ได้โดยไม่ขึ้นกับว่า T จะใช้งาน Clone ได้หรือไม่

ชุมชนได้ระบุว่านี่เป็นปัญหาที่แพร่หลายซึ่งไม่เพียงส่งผลกระทบต่อ Clone เท่านั้น แต่ยังรวมถึง derivable traits อื่นๆ เช่น PartialEq, Eq, และ Debug ด้วย สิ่งนี้บังคับให้นักพัฒนาต้องเขียน manual implementations สำหรับสิ่งที่ควรจะเป็น automatic derivations ที่ตรงไปตรงมา

พฤติกรรม Derive ปัจจุบันเทียบกับ Derive ที่สมบูรณ์แบบ

ด้าน Derive ปัจจุบัน Derive ที่สมบูรณ์แบบ
ข้อจำกัดของ Generic ต้องการให้พารามิเตอร์ generic ทั้งหมดใช้งาน trait ได้ ต้องการเฉพาะประเภทของ field ที่ใช้งาน trait ได้
ตัวอย่างข้อจำกัด T: Clone สำหรับ struct Wrapper&lt;T&gt;(Arc&lt;T&gt;) Arc&lt;T&gt;: Clone สำหรับ struct Wrapper&lt;T&gt;(Arc&lt;T&gt;)
ผลกระทบต่อ Semver เสถียร - ข้อจำกัดไม่เปลี่ยนแปลงเมื่อมีการแก้ไข field อาจทำให้เกิดปัญหา - ข้อจำกัดเปลี่ยนแปลงเมื่อมีการเปลี่ยนแปลง field ส่วนตัว
ความซับซ้อนในการใช้งาน ง่าย คาดเดาได้ ต้องการการสนับสนุนการจับคู่ trait แบบวงจร

ความท้าทายทางเทคนิคเบื้องหลัง

การหารือเผยให้เห็นว่านี่ไม่ใช่เพียงการมองข้ามที่สามารถแก้ไขได้อย่างรวดเร็ว การใช้งาน perfect derive จะต้องการการเปลี่ยนแปลงที่สำคัญในระบบ trait matching ของ Rust คอมไพเลอร์จะต้องจัดการกับ cyclical trait matching ซึ่งปัจจุบันทำงานได้เฉพาะกับ auto traits เช่น Send เท่านั้น

นอกจากนี้ยังมีความกังวลเรื่อง semver compatibility ด้วย กับ perfect derive การเปลี่ยนแปลงประเภทของ private field อาจทำให้โค้ดของผู้ใช้งานเสียหายในลักษณะที่ไม่คาดคิด หาก library เปลี่ยนจากการเก็บ Arc<T> เป็นการเก็บ T โดยตรง trait bounds จะเปลี่ยนแปลงโดยอัตโนมัติ ซึ่งอาจทำให้เกิดการล้มเหลวในการคอมไพล์สำหรับผู้ใช้งาน library นั้น

มุมมองของชุมชนเกี่ยวกับการแก้ปัญหา

ชุมชน Rust แบ่งออกเป็นสองฝ่ายในเรื่องวิธีการจัดการกับข้อจำกัดนี้ นักพัฒนาบางคนโต้แย้งให้คงระบบปัจจุบันไว้ให้เรียบง่ายและคาดเดาได้ โดยมองว่าการใช้งาน manual trait implementations เป็นต้นทุนที่ยอมรับได้เพื่อความชัดเจน คนอื่นๆ ผลักดันให้มีการแก้ปัญหาที่ซับซ้อนมากขึ้นซึ่งจะวิเคราะห์ความต้องการของ field จริงๆ แทนที่จะเป็นข้อจำกัดของพารามิเตอร์ generic แบบครอบคลุม

ซอฟต์แวร์ที่สง่างามวัดได้จากจำนวนบรรทัดของโค้ดที่คุณไม่จำเป็นต้องเขียน

สมาชิกชุมชนหลายคนได้ชี้ไปที่ crates ที่มีอยู่แล้วเช่น derive_more, derivative, และ educe เป็นการแก้ปัญหาเฉพาะหน้าที่ใช้ได้จริง third-party solutions เหล่านี้ใช้งานพฤติกรรม derive ที่ฉลาดกว่าแล้ว แม้ว่าจะเพิ่ม dependency overhead ให้กับโปรเจกต์ก็ตาม

มีทางเลือกอื่นที่สามารถใช้ได้

  • crate derive_more: ฟังก์ชันการทำงานของ derive ที่ขยายออกไปพร้อมกับ bounds ที่ยืดหยุ่นมากขึ้น
  • crate derivative: ให้การควบคุมพฤติกรรมของ derive อย่างละเอียดผ่าน attributes
  • crate educe: มอบการใช้งาน derive ที่สามารถปรับแต่งได้
  • การเขียนโค้ดด้วยตนเอง: เขียนการใช้งาน trait ด้วยมือเมื่อ derive ไม่เพียงพอ
  • proc macros แบบกำหนดเอง: สร้าง derive macros เฉพาะโปรเจกต์ที่มีพฤติกรรมตามที่ต้องการ

เส้นทางข้างหน้า

ในขณะที่บางคนแนะนำว่าสิ่งนี้สามารถจัดการได้ผ่าน RFC และการเปลี่ยนแปลงภาษาในที่สุด ไทม์ไลน์สำหรับการปรับเปลี่ยนดังกล่าวน่าจะใช้เวลาหลายปีเนื่องจากลักษณะที่ทำลายความเข้ากันได้ของการเปลี่ยนแปลง การหารือของชุมชนเน้นย้ำถึงความตึงเครียดระหว่างความเสถียรของภาษาและความสะดวกสบายของนักพัฒนา

ในตอนนี้ นักพัฒนายังคงพึ่งพา manual implementations หรือ third-party crates เมื่อพวกเขาพบกับข้อจำกัดเหล่านี้ การถกเถียงเน้นย้ำว่าแม้แต่ฟีเจอร์ของภาษาที่ดูเหมือนง่ายๆ ก็สามารถเกี่ยวข้องกับการแลกเปลี่ยนการออกแบบที่ซับซ้อนซึ่งส่งผลกระทบทั้งต่อการใช้งานปัจจุบันและการพัฒนาภาษาในระยะยาว

อ้างอิง: Why not?