Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ภาคผนวก C: เทรตที่สามารถจัดทำอนุพันธ์ได้ (Derivable Traits)

ในหลายๆ ส่วนของหนังสือเล่มนี้ เราได้กล่าวถึงแอตทริบิวต์ derive ซึ่งคุณสามารถระบุใช้งานกับคำนิยามโครงสร้างข้อมูล (struct) หรือการแจกแจงรายละเอียด (enum) ได้ แอตทริบิวต์ derive จะสร้างโค้ดสำหรับอิมพลิเมนต์เทรตที่มีพฤติกรรมเริ่มต้นของตัวมันเองเข้ากับชนิดข้อมูลที่คุณใส่กำกับไว้ด้วยไวยากรณ์ derive

ในภาคผนวกนี้ เราจะให้ข้อมูลอ้างอิงของเทรตทั้งหมดในไลบรารีมาตรฐานที่คุณสามารถนำมาใช้งานร่วมกับ derive ได้ โดยในแต่ละส่วนจะครอบคลุมถึง:

  • ตัวดำเนินการและเมธอดใดที่จะพร้อมใช้งานเมื่อทำการทำอนุพันธ์เทรต (deriving this trait) นี้
  • พฤติกรรมของการอิมพลิเมนต์เทรตที่ได้รับจาก derive ว่ามีหน้าที่ทำอะไรบ้าง
  • การอิมพลิเมนต์เทรตนี้บ่งบอกความหมายอย่างไรเกี่ยวกับชนิดข้อมูลดังกล่าว
  • เงื่อนไขที่คุณได้รับอนุญาตหรือไม่อนุญาตให้อิมพลิเมนต์เทรตนี้
  • ตัวอย่างของการดำเนินการที่จำเป็นต้องใช้เทรตนี้

หากคุณต้องการให้มีพฤติกรรมที่แตกต่างไปจากที่เตรียมไว้ให้โดยแอตทริบิวต์ derive คุณสามารถศึกษาเพิ่มเติมได้จาก เอกสารประกอบของไลบรารีมาตรฐาน สำหรับแต่ละเทรตเพื่อศึกษารายละเอียดวิธีอิมพลิเมนต์มันด้วยตนเอง (manually implement)

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

ตัวอย่างของเทรตที่ไม่สามารถทำการดิไรฟ์ (derive) ได้คือ Display ซึ่งใช้จัดการการจัดรูปแบบข้อความสำหรับแสดงผลให้แก่ผู้ใช้ทั่วไป คุณควรพิจารณาวิธีการที่เหมาะสมในการแสดงผลชนิดข้อมูลให้กับผู้ใช้ทั่วไปอยู่เสมอ เช่น ส่วนใดของชนิดข้อมูลที่ผู้ใช้ควรจะมองเห็นได้บ้าง? ส่วนใดที่พวกเขาจะมองว่าเกี่ยวข้อง? และรูปแบบข้อมูลแบบใดที่จะตรงความต้องการของพวกเขามากที่สุด? คอมไพเลอร์ของ Rust ไม่มีข้อมูลเชิงลึกเหล่านี้ จึงไม่สามารถมอบพฤติกรรมเริ่มต้นที่เหมาะสมให้แก่คุณได้

รายการเทรตที่สามารถดิไรฟ์ได้ซึ่งจัดเตรียมไว้ในภาคผนวกนี้ไม่ใช่รายการที่ครอบคลุมทั้งหมด: ไลบรารีภายนอกต่างๆ สามารถอิมพลิเมนต์ derive สำหรับเทรตของตนเองได้ ทำให้รายการของเทรตที่คุณสามารถใช้ derive ร่วมด้วยนั้นเปิดกว้างอย่างไม่มีที่สิ้นสุด การอิมพลิเมนต์ derive นั้นเกี่ยวข้องกับการใช้โปรซีจูรัลมาโคร (procedural macro) ซึ่งครอบคลุมอยู่ในหัวข้อ “มาโคร derive แบบกำหนดเอง (Custom derive Macros)” ในบทที่ 20

Debug สำหรับผลลัพธ์ของนักพัฒนาโปรแกรม

เทรต Debug ช่วยเปิดใช้งานการจัดรูปแบบสำหรับการดีบักในข้อความที่ถูกจัดรูปแบบ (format strings) ซึ่งคุณสามารถระบุได้โดยการเพิ่ม :? ภายในตัวแทนวงเล็บปีกกา {}

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

ตัวอย่างการใช้งานที่จำเป็นต้องมีเทรต Debug เช่น การใช้มาโคร assert_eq! มาโครนี้จะพิมพ์ค่าของอินสแตนซ์ต่างๆ ที่ส่งไปเป็นอาร์กิวเมนต์หากการตรวจสอบความเท่ากันล้มเหลว เพื่อให้นักพัฒนาสามารถมองเห็นได้ว่าทำไมอินสแตนซ์ทั้งสองจึงไม่เท่ากัน

PartialEq และ Eq สำหรับการเปรียบเทียบความเท่ากัน

เทรต PartialEq ช่วยให้คุณสามารถเปรียบเทียบอินสแตนซ์ของชนิดข้อมูลเพื่อตรวจสอบความเท่ากัน และช่วยเปิดใช้งานการใช้ตัวดำเนินการ == และ !=

การดิไรฟ์ PartialEq จะอิมพลิเมนต์เมธอด eq ขึ้นมา เมื่อดิไรฟ์ PartialEq บนโครงสร้างข้อมูล (structs) อินสแตนซ์สองตัวจะเท่ากันก็ต่อเมื่อฟิลด์ ทั้งหมด มีค่าเท่ากัน และจะไม่เท่ากันหากมีฟิลด์ ใดฟิลด์หนึ่ง ไม่เท่ากัน เมื่อดิไรฟ์บนเอ็นัม (enums) ตัวแปรย่อย (variants) แต่ละตัวจะมีค่าเท่ากับตัวมันเอง และจะไม่เท่ากับตัวแปรย่อยอื่นๆ

ตัวอย่างการใช้งานที่จำเป็นต้องมีเทรต PartialEq เช่น การใช้มาโคร assert_eq! ซึ่งต้องสามารถเปรียบเทียบความเท่ากันระหว่างอินสแตนซ์สองตัวของชนิดข้อมูลได้

เทรต Eq ไม่มีเมธอดภายในตัวมันเอง วัตถุประสงค์ของมันคือการส่งสัญญาณบ่งชี้ว่าสำหรับทุกๆ ค่าของชนิดข้อมูลที่ถูกระบุ ค่าของมันย่อมมีค่าเท่ากับตัวเองเสมอ เทรต Eq สามารถใช้ได้กับชนิดข้อมูลที่อิมพลิเมนต์ PartialEq อยู่แล้วเท่านั้น อย่างไรก็ดี ไม่ใช่ว่าทุกชนิดข้อมูลที่อิมพลิเมนต์ PartialEq จะสามารถอิมพลิเมนต์ Eq ได้ ตัวอย่างหนึ่งคือชนิดข้อมูลตัวเลขทศนิยม (floating-point number types): การอิมพลิเมนต์ตัวเลขทศนิยมระบุว่า ค่าที่ไม่ได้เป็นตัวเลข (NaN หรือ not-a-number) สองค่าจะไม่มีค่าเท่ากัน

ตัวอย่างการใช้งานที่จำเป็นต้องมี Eq คือ การใช้คีย์ (keys) ใน HashMap<K, V> เพื่อให้ HashMap<K, V> สามารถแยกแยะได้ว่าคีย์ทั้งสองคีย์นั้นเป็นคีย์เดียวกันหรือไม่

PartialOrd และ Ord สำหรับการเปรียบเทียบการจัดเรียงลำดับ

เทรต PartialOrd ช่วยให้คุณสามารถเปรียบเทียบอินสแตนซ์ของชนิดข้อมูลเพื่อวัตถุประสงค์ในการจัดเรียงลำดับ ชนิดข้อมูลที่อิมพลิเมนต์ PartialOrd จะสามารถใช้ร่วมกับตัวดำเนินการ <, >, <=, และ >= ได้ คุณสามารถใช้เทรต PartialOrd กับชนิดข้อมูลที่ได้อิมพลิเมนต์ PartialEq อยู่แล้วเท่านั้น

การดิไรฟ์ PartialOrd จะเป็นการอิมพลิเมนต์เมธอด partial_cmp ซึ่งจะคืนค่ากลับมาเป็น Option<Ordering> ที่จะมีค่าเป็น None เมื่อค่าที่ระบุไม่สามารถนำมาเปรียบเทียบเพื่อจัดลำดับได้ ตัวอย่างของค่าที่ไม่นำไปสู่การจัดเรียงลำดับ (แม้ว่าค่าส่วนใหญ่ของชนิดข้อมูลนั้นจะเปรียบเทียบกันได้ก็ตาม) คือค่าทศนิยมแบบ NaN โดยการเรียกใช้ partial_cmp ระหว่างตัวเลขทศนิยมใดๆ กับค่าทศนิยมแบบ NaN จะส่งกลับค่าเป็น None

เมื่อดิไรฟ์กับโครงสร้างข้อมูล PartialOrd จะเปรียบเทียบอินสแตนซ์สองตัวโดยเปรียบเทียบค่าในแต่ละฟิลด์ตามลำดับการปรากฏของฟิลด์นั้นๆ ในคำนิยามโครงสร้างข้อมูล เมื่อดิไรฟ์กับเอ็นัม ตัวแปรย่อยของเอ็นัมที่ประกาศไว้ก่อนในคำนิยามเอ็นัมจะถือว่ามีค่าน้อยกว่าตัวแปรย่อยที่ระบุไว้ทีหลัง

ตัวอย่างการใช้งานที่จำเป็นต้องมีเทรต PartialOrd เช่น เมธอด gen_range จากเครต rand ที่ทำหน้าที่สร้างค่าสุ่มภายในช่วงที่กำหนดไว้โดยนิพจน์ช่วง (range expression)

เทรต Ord ช่วยให้คุณมั่นใจได้ว่าสำหรับค่าใดๆ สองค่าของชนิดข้อมูลดังกล่าว จะมีลำดับเปรียบเทียบที่ถูกต้องเกิดขึ้นเสมอ เทรต Ord จะอิมพลิเมนต์เมธอด cmp ซึ่งจะคืนค่ากลับมาเป็น Ordering แทนที่จะเป็น Option<Ordering> เนื่องจากจะสามารถจัดหาลำดับที่ถูกต้องได้เสมอ คุณสามารถนำเทรต Ord ไปใช้กับชนิดข้อมูลที่ได้อิมพลิเมนต์ PartialOrd และ Eq อยู่ก่อนแล้วเท่านั้น (และ Eq ก็ต้องการ PartialEq เช่นกัน) เมื่อดิไรฟ์บนโครงสร้างข้อมูลและเอ็นัม เมธอด cmp จะทำงานในลักษณะเดียวกับผลลัพธ์การดิไรฟ์ของ partial_cmp ร่วมกับ PartialOrd

ตัวอย่างการใช้งานที่จำเป็นต้องมี Ord เช่น เมื่อต้องการเก็บค่าต่างๆ ไว้ใน BTreeSet<T> ซึ่งเป็นโครงสร้างข้อมูลที่จัดเก็บข้อมูลตามลำดับการจัดเรียงของค่านั้นๆ

Clone และ Copy สำหรับการทำสำเนาค่าข้อมูล

เทรต Clone ช่วยให้คุณสามารถสร้างการคัดลอกแบบลึก (deep copy) ของค่าข้อมูลอย่างชัดเจนได้ และขั้นตอนการทำซ้ำนี้อาจรวมถึงการทำงานของโค้ดใดๆ และการคัดลอกข้อมูลในฮีป (heap data) ดูรายละเอียดเพิ่มเติมเกี่ยวกับ Clone ได้ในหัวข้อ “ตัวแปรและข้อมูลที่มีปฏิสัมพันธ์กับ Clone” ในบทที่ 4

การดิไรฟ์ Clone จะอิมพลิเมนต์เมธอด clone ซึ่งเมื่อมีผลครอบคลุมทั้งชนิดข้อมูลแล้ว จะเข้าไปเรียกใช้งาน clone กับส่วนประกอบย่อยแต่ละส่วนของชนิดข้อมูลนั้น ซึ่งหมายความว่าฟิลด์หรือค่าข้อมูลทั้งหมดในชนิดข้อมูลนั้นจะต้องอิมพลิเมนต์ Clone ด้วยเช่นกัน จึงจะสามารถดิไรฟ์ Clone ได้

ตัวอย่างการใช้งานที่จำเป็นต้องมี Clone เช่น การเรียกเมธอด to_vec บนชิ้นข้อมูล (slice) ชิ้นข้อมูลนั้นไม่ได้ถือครองความเป็นเจ้าของของอินสแตนซ์ที่มันประกอบอยู่ แต่เวกเตอร์ (vector) ที่ได้รับกลับมาจาก to_vec จำเป็นต้องเป็นเจ้าของอินสแตนซ์ของมันเอง ดังนั้น to_vec จึงต้องเรียกใช้งาน clone ในแต่ละไอเทม ด้วยเหตุนี้ ชนิดข้อมูลที่จัดเก็บในชิ้นข้อมูลจึงต้องอิมพลิเมนต์ Clone

เทรต Copy ช่วยให้คุณสามารถทำซ้ำค่าข้อมูลได้โดยการคัดลอกเฉพาะบิตที่จัดเก็บอยู่บนสแตก (stack) เท่านั้น โดยไม่จำเป็นต้องรันโค้ดใดๆ เพิ่มเติม ดูรายละเอียดเพิ่มเติมเกี่ยวกับ Copy ได้ในหัวข้อ “ข้อมูลที่อยู่บนสแตกเท่านั้น: Copy” ในบทที่ 4

เทรต Copy ไม่ได้นิยามเมธอดใดๆ ไว้ เพื่อป้องกันไม่ให้นักพัฒนาทำการทำโอเวอร์โหลดเมธอดเหล่านั้นและละเมิดข้อตกลงที่ว่าไม่มีโค้ดใดๆ ถูกสั่งให้ทำงานเพิ่มเติม ด้วยวิธีนี้ นักพัฒนาทุกคนจึงสามารถสันนิษฐานได้ว่าการคัดลอกค่าข้อมูลดังกล่าวจะทำงานได้อย่างรวดเร็วมาก

คุณสามารถดิไรฟ์ Copy บนชนิดข้อมูลใดๆ ที่ส่วนประกอบย่อยทั้งหมดอิมพลิเมนต์ Copy อยู่แล้ว ชนิดข้อมูลที่อิมพลิเมนต์ Copy จะต้องอิมพลิเมนต์ Clone เช่นกัน เนื่องจากชนิดข้อมูลที่อิมพลิเมนต์ Copy จะมีคำสั่งการทำงานที่เรียบง่ายของ Clone ซึ่งทำหน้าที่แบบเดียวกับที่ Copy ทำ

เทรต Copy ไม่ค่อยมีความจำเป็นต้องถูกเรียกใช้อย่างเข้มงวด ชนิดข้อมูลที่อิมพลิเมนต์ Copy จะมีฟีเจอร์การเพิ่มประสิทธิภาพเตรียมพร้อมไว้ให้ ซึ่งทำให้คุณไม่จำเป็นต้องเรียกใช้ clone ส่งผลให้โค้ดมีความกระชับยิ่งขึ้น

ทุกสิ่งที่สามารถทำได้ด้วย Copy คุณก็สามารถทำได้ด้วย Clone เช่นกัน แต่โค้ดอาจทำงานได้ช้ากว่า หรืออาจต้องระบุการเรียกใช้งาน clone ในบางจุดของโปรแกรม

Hash สำหรับการจับคู่ค่าข้อมูลไปยังค่าที่มีขนาดคงที่

เทรต Hash ช่วยให้คุณสามารถนำอินสแตนซ์ของชนิดข้อมูลที่มีขนาดใดๆ มาแปลงจับคู่ให้เป็นค่าที่มีขนาดคงที่ได้โดยใช้ฟังก์ชันแฮช (hash function) การดิไรฟ์ Hash จะเป็นการอิมพลิเมนต์เมธอด hash โดยพฤติกรรมการดิไรฟ์ของเมธอด hash จะเป็นการนำผลลัพธ์จากการเรียกใช้ hash ในแต่ละส่วนประกอบย่อยของชนิดข้อมูลมารวมกัน ซึ่งหมายความว่าฟิลด์หรือค่าข้อมูลทั้งหมดต้องอิมพลิเมนต์ Hash ด้วยเช่นกันเพื่อที่จะดิไรฟ์ Hash ได้

ตัวอย่างการใช้งานที่จำเป็นต้องมี Hash คือการจัดเก็บคีย์ใน HashMap<K, V> เพื่อบันทึกข้อมูลได้อย่างมีประสิทธิภาพ

Default สำหรับค่าเริ่มต้นของข้อมูล

เทรต Default ช่วยให้คุณสร้างค่าเริ่มต้นสำหรับชนิดข้อมูลได้ การดิไรฟ์ Default จะเป็นการอิมพลิเมนต์ฟังก์ชัน default โดยการทำงานที่ได้จากการดิไรฟ์ฟังก์ชัน default จะไปเรียกใช้งานฟังก์ชัน default ในแต่ละส่วนประกอบย่อยของชนิดข้อมูลนั้น ซึ่งหมายความว่าฟิลด์หรือค่าทั้งหมดในชนิดข้อมูลต้องอิมพลิเมนต์ Default ด้วยเพื่อทำการดิไรฟ์ Default

ฟังก์ชัน Default::default มักถูกนำมาใช้ร่วมกับไวยากรณ์การอัปเดตโครงสร้างข้อมูลที่กล่าวถึงในหัวข้อ “การสร้างอินสแตนซ์จากอินสแตนซ์อื่นด้วยไวยากรณ์อัปเดตโครงสร้างข้อมูล” ในบทที่ 5 คุณสามารถปรับแต่งฟิลด์บางส่วนของโครงสร้างข้อมูลด้วยตนเอง แล้วเลือกกำหนดและใช้ค่าเริ่มต้นสำหรับฟิลด์ส่วนที่เหลือได้โดยการระบุ ..Default::default()

ตัวอย่างการใช้งานที่จำเป็นต้องมีเทรต Default เช่น เมื่อคุณใช้เมธอด unwrap_or_default บนอินสแตนซ์ Option<T> หากค่า Option<T> เป็น None เมธอด unwrap_or_default จะส่งกลับผลลัพธ์ของ Default::default สำหรับชนิดข้อมูล T ที่ถูกเก็บอยู่ใน Option<T>