Generic Type, Trait และ Lifetime
ทุกภาษาโปรแกรมมีเครื่องมือสำหรับจัดการการซ้ำของแนวคิดอย่างมีประสิทธิภาพ ใน Rust เครื่องมือหนึ่งคือ generic — ตัวแทนนามธรรมของ type คอนกรีตหรือ คุณสมบัติอื่น เราแสดงพฤติกรรมของ generic หรือวิธีที่พวกมันสัมพันธ์กับ generic อื่นได้ โดยไม่ต้องรู้ว่าอะไรจะอยู่ในที่ของมันเมื่อ compile และ รันโค้ด
ฟังก์ชันรับ parameter ของ generic type บางตัว แทน type คอนกรีตอย่าง
i32 หรือ String ได้ ในแบบเดียวกับที่รับ parameter ที่มีค่าไม่รู้
เพื่อรันโค้ดเดียวกันบนค่าคอนกรีตหลายตัว จริง ๆ เราใช้ generic แล้วในบทที่
6 กับ Option<T> ในบทที่ 8 กับ Vec<T> และ HashMap<K, V> และในบทที่
9 กับ Result<T, E> ในบทที่นี้ คุณจะสำรวจวิธีประกาศ type, ฟังก์ชัน และ
เมธอดของคุณเองด้วย generic!
ก่อนอื่น เราจะทบทวนวิธีดึงฟังก์ชันออกเพื่อลดการซ้ำของโค้ด แล้วเราจะใช้ เทคนิคเดียวกันสร้าง generic function จากสองฟังก์ชันที่ต่างกันแค่ที่ type ของ parameter เราจะอธิบายวิธีใช้ generic type ใน definition ของ struct และ enum ด้วย
จากนั้น คุณจะเรียนวิธีใช้ trait ประกาศพฤติกรรมในแบบ generic คุณรวม trait กับ generic type เพื่อจำกัด generic type ให้รับเฉพาะ type ที่มีพฤติกรรม เฉพาะ ตรงข้ามกับแค่ type ใด ๆ ได้
สุดท้าย เราจะพูดถึง lifetime — generic หลากหลายที่ให้ข้อมูล compiler ว่า reference สัมพันธ์กันอย่างไร Lifetime ให้เราให้ข้อมูล compiler เพียง พอเรื่องค่าที่ borrow เพื่อให้มันรับประกันได้ว่า reference จะ valid ใน สถานการณ์มากกว่าที่ทำได้โดยไม่มีความช่วยเหลือของเรา
ลดการซ้ำโดยดึงฟังก์ชัน
Generic ให้เราแทน type เฉพาะด้วย placeholder ที่แทน type หลายตัว เพื่อ ลดการซ้ำของโค้ด ก่อนลงลึก syntax ของ generic มาดูวิธีลดการซ้ำในแบบที่ไม่ เกี่ยวกับ generic type ก่อน โดยดึงฟังก์ชันที่แทนค่าเฉพาะด้วย placeholder ที่แทนค่าหลายค่า แล้วเราจะใช้เทคนิคเดียวกันดึง generic function! ด้วยการ ดูวิธีจับโค้ดที่ซ้ำที่คุณดึงเป็นฟังก์ชันได้ คุณจะเริ่มจับโค้ดที่ซ้ำที่ ใช้ generic ได้
เราจะเริ่มด้วยโปรแกรมสั้นใน Listing 10-1 ที่หาตัวเลขที่ใหญ่ที่สุดใน list
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
assert_eq!(*largest, 100);
}
เราเก็บ list ของ integer ในตัวแปร number_list และวาง reference ของ
ตัวเลขแรกใน list ในตัวแปรชื่อ largest จากนั้นเรา iterate ผ่านตัวเลข
ทั้งหมดใน list และถ้าตัวเลขปัจจุบันมากกว่าตัวเลขที่เก็บใน largest เรา
แทน reference ในตัวแปรนั้น อย่างไรก็ตาม ถ้าตัวเลขปัจจุบันน้อยกว่าหรือ
เท่ากับตัวเลขที่ใหญ่ที่สุดที่เห็นมา ตัวแปรไม่เปลี่ยน และโค้ดไปยังตัวเลข
ถัดไปใน list หลังพิจารณาตัวเลขทั้งหมดใน list largest ควรอ้างถึงตัวเลข
ที่ใหญ่ที่สุด ซึ่งในกรณีนี้คือ 100
ตอนนี้เราได้รับมอบหมายให้หาตัวเลขที่ใหญ่ที่สุดใน list ตัวเลขสอง list ที่ ต่างกัน ในการทำเช่นนั้น เราเลือกที่จะคัดลอกโค้ดใน Listing 10-1 และใช้ logic เดียวกันในสองที่ในโปรแกรม ดังที่แสดงใน Listing 10-2
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}
แม้โค้ดนี้ทำงาน การคัดลอกโค้ดน่าเบื่อและเสี่ยง error เรายังต้องจำที่จะ update โค้ดในหลายที่เมื่อเราอยากเปลี่ยน
เพื่อกำจัดการซ้ำนี้ เราจะสร้าง abstraction โดยประกาศฟังก์ชันที่ operate บน list ของ integer ใด ๆ ที่ส่งเข้าเป็น parameter คำตอบนี้ทำให้โค้ดของ เราชัดเจนขึ้นและให้เราแสดงแนวคิดของการหาตัวเลขที่ใหญ่ที่สุดใน list อย่าง เป็นนามธรรม
ใน Listing 10-3 เราดึงโค้ดที่หาตัวเลขที่ใหญ่ที่สุดเข้าฟังก์ชันชื่อ
largest จากนั้น เราเรียกฟังก์ชันหาตัวเลขที่ใหญ่ที่สุดในสอง list จาก
Listing 10-2 เรายังใช้ฟังก์ชันบน list ของค่า i32 อื่นใดที่เราอาจมีใน
อนาคตได้
fn largest(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 100);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 6000);
}
ฟังก์ชัน largest มี parameter ชื่อ list ซึ่งแทน slice คอนกรีตใด ๆ
ของค่า i32 ที่เราอาจส่งเข้าฟังก์ชัน ผลคือ เมื่อเราเรียกฟังก์ชัน โค้ด
รันบนค่าเฉพาะที่เราส่งเข้า
สรุป นี่คือขั้นตอนที่เราทำเพื่อเปลี่ยนโค้ดจาก Listing 10-2 เป็น Listing 10-3:
- ระบุโค้ดที่ซ้ำ
- ดึงโค้ดที่ซ้ำเข้า body ของฟังก์ชัน และระบุ input และ return value ของ โค้ดนั้นใน signature ของฟังก์ชัน
- Update สอง instance ของโค้ดที่ซ้ำให้เรียกฟังก์ชันแทน
ถัดไป เราจะใช้ขั้นตอนเดียวกันเหล่านี้กับ generic ลดการซ้ำของโค้ด ในแบบ
เดียวกับที่ body ของฟังก์ชัน operate บน list นามธรรมแทนค่าเฉพาะได้
generic ให้โค้ด operate บน type นามธรรม
เช่น สมมติเรามีสองฟังก์ชัน — ตัวหนึ่งหา item ที่ใหญ่ที่สุดใน slice ของ
ค่า i32 และตัวหนึ่งหา item ที่ใหญ่ที่สุดใน slice ของค่า char เราจะ
กำจัดการซ้ำนั้นยังไง? มาดูกัน!