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

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

Filename: src/main.rs
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);
}
Listing 10-1: หาตัวเลขที่ใหญ่ที่สุดใน list ของตัวเลข

เราเก็บ list ของ integer ในตัวแปร number_list และวาง reference ของ ตัวเลขแรกใน list ในตัวแปรชื่อ largest จากนั้นเรา iterate ผ่านตัวเลข ทั้งหมดใน list และถ้าตัวเลขปัจจุบันมากกว่าตัวเลขที่เก็บใน largest เรา แทน reference ในตัวแปรนั้น อย่างไรก็ตาม ถ้าตัวเลขปัจจุบันน้อยกว่าหรือ เท่ากับตัวเลขที่ใหญ่ที่สุดที่เห็นมา ตัวแปรไม่เปลี่ยน และโค้ดไปยังตัวเลข ถัดไปใน list หลังพิจารณาตัวเลขทั้งหมดใน list largest ควรอ้างถึงตัวเลข ที่ใหญ่ที่สุด ซึ่งในกรณีนี้คือ 100

ตอนนี้เราได้รับมอบหมายให้หาตัวเลขที่ใหญ่ที่สุดใน list ตัวเลขสอง list ที่ ต่างกัน ในการทำเช่นนั้น เราเลือกที่จะคัดลอกโค้ดใน Listing 10-1 และใช้ logic เดียวกันในสองที่ในโปรแกรม ดังที่แสดงใน Listing 10-2

Filename: src/main.rs
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}");
}
Listing 10-2: โค้ดหาตัวเลขที่ใหญ่ที่สุดใน list ตัวเลข สอง ตัว

แม้โค้ดนี้ทำงาน การคัดลอกโค้ดน่าเบื่อและเสี่ยง error เรายังต้องจำที่จะ update โค้ดในหลายที่เมื่อเราอยากเปลี่ยน

เพื่อกำจัดการซ้ำนี้ เราจะสร้าง abstraction โดยประกาศฟังก์ชันที่ operate บน list ของ integer ใด ๆ ที่ส่งเข้าเป็น parameter คำตอบนี้ทำให้โค้ดของ เราชัดเจนขึ้นและให้เราแสดงแนวคิดของการหาตัวเลขที่ใหญ่ที่สุดใน list อย่าง เป็นนามธรรม

ใน Listing 10-3 เราดึงโค้ดที่หาตัวเลขที่ใหญ่ที่สุดเข้าฟังก์ชันชื่อ largest จากนั้น เราเรียกฟังก์ชันหาตัวเลขที่ใหญ่ที่สุดในสอง list จาก Listing 10-2 เรายังใช้ฟังก์ชันบน list ของค่า i32 อื่นใดที่เราอาจมีใน อนาคตได้

Filename: src/main.rs
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);
}
Listing 10-3: โค้ดนามธรรมหาตัวเลขที่ใหญ่ที่สุดในสอง list

ฟังก์ชัน largest มี parameter ชื่อ list ซึ่งแทน slice คอนกรีตใด ๆ ของค่า i32 ที่เราอาจส่งเข้าฟังก์ชัน ผลคือ เมื่อเราเรียกฟังก์ชัน โค้ด รันบนค่าเฉพาะที่เราส่งเข้า

สรุป นี่คือขั้นตอนที่เราทำเพื่อเปลี่ยนโค้ดจาก Listing 10-2 เป็น Listing 10-3:

  1. ระบุโค้ดที่ซ้ำ
  2. ดึงโค้ดที่ซ้ำเข้า body ของฟังก์ชัน และระบุ input และ return value ของ โค้ดนั้นใน signature ของฟังก์ชัน
  3. Update สอง instance ของโค้ดที่ซ้ำให้เรียกฟังก์ชันแทน

ถัดไป เราจะใช้ขั้นตอนเดียวกันเหล่านี้กับ generic ลดการซ้ำของโค้ด ในแบบ เดียวกับที่ body ของฟังก์ชัน operate บน list นามธรรมแทนค่าเฉพาะได้ generic ให้โค้ด operate บน type นามธรรม

เช่น สมมติเรามีสองฟังก์ชัน — ตัวหนึ่งหา item ที่ใหญ่ที่สุดใน slice ของ ค่า i32 และตัวหนึ่งหา item ที่ใหญ่ที่สุดใน slice ของค่า char เราจะ กำจัดการซ้ำนั้นยังไง? มาดูกัน!