คอลเลกชันคือสมาร์ตพอยน์เตอร์
คำอธิบาย
การนำ trait Deref มาใช้กับโครงสร้างข้อมูลประเภทคอลเลกชัน (Collections) เสมือนว่ามันเป็นสมาร์ตพอยน์เตอร์ เพื่อเปิดมุมมองข้อมูลทั้งในแบบที่เป็นเจ้าของข้อมูล (Owning view) และแบบยืมข้อมูล (Borrowed view)
ตัวอย่าง
use std::ops::Deref;
struct Vec<T> {
data: RawVec<T>,
//..
}
impl<T> Deref for Vec<T> {
type Target = [T];
fn deref(&self) -> &[T] {
//..
}
}
โครงสร้าง Vec<T> คือคอลเลกชันที่เป็นเจ้าของข้อมูลของ T ในขณะที่ slice (&[T]) คือคอลเลกชันในมุมมองแบบยืมข้อมูลของ T การอิมพลีเมนต์ Deref ให้แก่ Vec จะเปิดทางให้คอมไพเลอร์ทำ deref coercion จาก &Vec<T> ไปเป็น &[T] ได้โดยอัตโนมัติ และผนวกรวมความสัมพันธ์นี้เข้ากับกระบวนการค้นหาเมท็อดอัตโนมัติ (auto-dereferencing) ส่งผลให้เมท็อดส่วนใหญ่ที่คุณคุ้นเคยบน Vec แท้จริงแล้วถูกอิมพลีเมนต์ไว้บน slice แทบทั้งสิ้น
นอกจากนี้ String และ &str ก็มีความสัมพันธ์ในลักษณะเดียวกันนี้เช่นกัน
ที่มาและเหตุผล
ระบบความเป็นเจ้าของ (Ownership) และการยืมข้อมูล (Borrowing) คือหัวใจสำคัญของภาษา Rust โครงสร้างข้อมูลที่ออกแบบมาจึงต้องคำนึงถึงพฤติกรรมเหล่านี้อย่างรอบคอบเพื่อส่งมอบประสบการณ์การใช้งานที่ดี เมื่อใดก็ตามที่คุณสร้างโครงสร้างข้อมูลที่เป็นเจ้าของข้อมูล การเปิดมุมมองแบบยืมข้อมูล (Borrowed view) ออกมาจะช่วยให้ API มีความยืดหยุ่นและใช้งานง่ายขึ้นอย่างมหาศาล
ข้อดี
เมท็อดส่วนใหญ่สามารถเขียนอิมพลีเมนต์ไว้เพียงแค่บนมุมมองแบบยืมข้อมูลเท่านั้น แล้วคอมไพเลอร์จะเปิดให้มุมมองแบบเป็นเจ้าของสามารถเรียกใช้งานเมท็อดเหล่านั้นได้โดยอัตโนมัติ
เปิดทางเลือกให้ผู้เรียกใช้งานว่าต้องการยืมข้อมูล (Borrowing) หรือต้องการครอบครองความเป็นเจ้าของ (Taking ownership)
ข้อเสีย
เมท็อดหรือ trait ที่เข้าถึงได้ผ่านการ dereference เท่านั้น จะไม่ถูกนำมาพิจารณาในระหว่างการตรวจสอบขอบเขตชนิดข้อมูล (Trait Bounds checking) ดังนั้นการเขียนโปรแกรมเชิงเจเนอริกกับโครงสร้างข้อมูลที่ใช้แพตเทิร์นนี้จึงอาจมีความซับซ้อนเพิ่มขึ้น (ดูเพิ่มเติมได้ที่ trait Borrow, AsRef ฯลฯ)
เจาะลึกรายละเอียด
ในเชิงแนวคิด สมาร์ตพอยน์เตอร์และคอลเลกชันถือว่ามีความคล้ายคลึงกันอย่างยิ่ง: สมาร์ตพอยน์เตอร์จะชี้ไปยังออบเจกต์เดี่ยว ในขณะที่คอลเลกชันจะชี้ไปยังกลุ่มของออบเจกต์จำนวนมาก เมื่อมองจากมุมของระบบชนิดข้อมูล (Type System) ทั้งสองแทบไม่มีความแตกต่างกันเลย คอลเลกชันจะถือว่าเป็นเจ้าของข้อมูลอย่างแท้จริงหากวิธีเดียวที่จะเข้าถึงข้อมูลได้คือผ่านคอลเลกชันนั้น และคอลเลกชันมีหน้าที่รับผิดชอบในการคืนหน่วยความจำเมื่อเลิกใช้งาน (แม้ในกรณีของความเป็นเจ้าของร่วมหรือ Shared Ownership มุมมองแบบยืมข้อมูลก็ยังคงมีความจำเป็น) และเมื่อคอลเลกชันเป็นเจ้าของข้อมูล การเปิดมุมมองแบบยืมข้อมูลออกมาก็จะมีประโยชน์อย่างยิ่ง เพราะช่วยให้ส่วนอื่นๆ สามารถอ้างอิงถึงข้อมูลนั้นซ้ำๆ ได้หลายจุดพร้อมกัน
สมาร์ตพอยน์เตอร์ส่วนใหญ่ (เช่น Foo<T>) จะอิมพลีเมนต์ Deref<Target=T> แต่สำหรับคอลเลกชัน มักจะ dereference ไปสู่ชนิดข้อมูลแบบกำหนดเอง แม้ว่า [T] และ str จะได้รับการสนับสนุนเป็นพิเศษในระดับภาษา แต่ในกรณีทั่วไปก็ไม่จำเป็นต้องยึดติดกับรูปแบบนี้เสมอไป โดย Foo<T> สามารถอิมพลีเมนต์ Deref<Target=Bar<T>> ได้ โดยที่ Bar เป็นชนิดข้อมูลแบบ Dynamically Sized Type (DST) และ &Bar<T> จะทำหน้าที่เป็นมุมมองแบบยืมของข้อมูลภายใน Foo<T>
โดยทั่วไป คอลเลกชันที่มีการเรียงลำดับจะอิมพลีเมนต์ trait Index ร่วมกับ Range เพื่อรองรับไวยากรณ์การทำ slicing โดยชนิดข้อมูลเป้าหมายที่ได้ออกมาก็คือมุมมองแบบยืมข้อมูลนั่นเอง