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

ใช้ชนิดที่ถูกยืมสำหรับอาร์กิวเมนต์

คำอธิบาย

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

หลักการนี้ไม่ได้จำกัดอยู่เพียงแค่ชนิดข้อมูลที่แปลงเป็น slice ได้ หรือชนิดข้อมูลแบบ fat pointer (พอยน์เตอร์พร้อม metadata) เท่านั้น แต่อันที่จริงแล้ว คุณควรเลือกใช้ไทป์แบบยืม (borrowed type) แทนการยืมไทป์ที่เป็นเจ้าของข้อมูล (borrowing the owned type) อยู่เสมอ เช่น เลือกใช้ &str แทน &String, เลือกใช้ &[T] แทน &Vec<T> หรือเลือกใช้ &T แทน &Box<T>

การใช้ไทป์แบบยืมจะช่วยให้คุณหลีกเลี่ยงการอ้างอิงทางอ้อม (indirection) ซ้ำซ้อนหลายชั้น ในกรณีที่ชนิดข้อมูลที่เป็นเจ้าของนั้นมีชั้นของการอ้างอิงทางอ้อมอยู่แล้ว ตัวอย่างเช่น String มีการอ้างอิงทางอ้อมอยู่หนึ่งชั้นอยู่แล้ว ดังนั้นการใช้ &String จึงกลายเป็นการอ้างอิงทางอ้อมซ้อนกันถึงสองชั้น เราสามารถหลีกเลี่ยงภาระนี้ได้โดยการเปลี่ยนมาใช้ &str แทน แล้วปล่อยให้คอมไพเลอร์แปลงค่าจาก &String ไปเป็น &str ผ่านกลไก deref coercion โดยอัตโนมัติเมื่อใดก็ตามที่มีการเรียกใช้งานฟังก์ชัน

ตัวอย่าง

ในตัวอย่างนี้ เราจะแสดงให้เห็นถึงความแตกต่างระหว่างการใช้ &String กับการใช้ &str เป็นอาร์กิวเมนต์ของฟังก์ชัน แต่แนวคิดเดียวกันนี้ก็สามารถนำไปปรับใช้กับการเลือก &[T] แทน &Vec<T> หรือการเลือก &T แทน &Box<T> ได้เช่นเดียวกัน

ลองพิจารณาตัวอย่างที่เราต้องการตรวจสอบว่าคำคำหนึ่งมีสระเรียงติดกันตั้งแต่สามตัวขึ้นไปหรือไม่ ในกรณีนี้ เราไม่จำเป็นต้องเป็นเจ้าของสตริงเพื่อตรวจสอบเงื่อนไขดังกล่าว เราจึงส่งข้อมูลเข้ามาในรูปของการอ้างอิง (Reference) แทน

โค้ดจะมีหน้าตาประมาณนี้:

fn three_vowels(word: &String) -> bool {
    let mut vowel_count = 0;
    for c in word.chars() {
        match c {
            'a' | 'e' | 'i' | 'o' | 'u' => {
                vowel_count += 1;
                if vowel_count >= 3 {
                    return true;
                }
            }
            _ => vowel_count = 0,
        }
    }
    false
}

fn main() {
    let ferris = "Ferris".to_string();
    let curious = "Curious".to_string();
    println!("{}: {}", ferris, three_vowels(&ferris));
    println!("{}: {}", curious, three_vowels(&curious));

    // This works fine, but the following two lines would fail:
    // println!("Ferris: {}", three_vowels("Ferris"));
    // println!("Curious: {}", three_vowels("Curious"));
}

โค้ดด้านบนนี้ทำงานได้ตามปกติเนื่องจากเราส่งค่าที่มีชนิดเป็น &String เข้าไปเป็นพารามิเตอร์ แต่หากเรานำคอมเมนต์ในสองบรรทัดสุดท้ายออก โค้ดตัวอย่างจะคอมไพล์ไม่ผ่านทันที นั่นเป็นเพราะว่าชนิดข้อมูลแบบ &str ไม่สามารถถูกแปลงย้อนกลับเป็น &String ได้โดยอัตโนมัติ เราสามารถแก้ไขปัญหานี้ได้อย่างง่ายดายด้วยการเปลี่ยนชนิดข้อมูลของพารามิเตอร์ในฟังก์ชันของเรา

ตัวอย่างเช่น หากเราเปลี่ยนการประกาศฟังก์ชันเป็น:

fn three_vowels(word: &str) -> bool {

ทั้งสองเวอร์ชันก็จะสามารถคอมไพล์ผ่านได้อย่างราบรื่นและให้ผลลัพธ์เหมือนกัน:

Ferris: false
Curious: true

แต่ช้าก่อน! ประโยชน์ของเรื่องนี้ยังไม่หมดเพียงเท่านี้ คุณอาจจะแย้งในใจว่า: นั่นไม่ใช่เรื่องใหญ่ เพราะอย่างไรเสียเราก็แทบไม่เคยส่งค่า &'static str เข้ามาตรงๆ อยู่แล้ว (เหมือนในตัวอย่างที่เราส่ง "Ferris") แต่ถึงแม้จะมองข้ามกรณีนี้ไป คุณก็จะพบว่าการใช้ &str ยังคงมอบความยืดหยุ่นให้มากกว่าการใช้ &String อยู่ดี

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

ตัวอย่างโค้ดจะมีลักษณะดังนี้:

fn three_vowels(word: &str) -> bool {
    let mut vowel_count = 0;
    for c in word.chars() {
        match c {
            'a' | 'e' | 'i' | 'o' | 'u' => {
                vowel_count += 1;
                if vowel_count >= 3 {
                    return true;
                }
            }
            _ => vowel_count = 0,
        }
    }
    false
}

fn main() {
    let sentence_string =
        "Once upon a time, there was a friendly curious crab named Ferris".to_string();
    for word in sentence_string.split(' ') {
        if three_vowels(word) {
            println!("{word} has three consecutive vowels!");
        }
    }
}

เมื่อรันตัวอย่างนี้โดยที่ฟังก์ชันประกาศรับอาร์กิวเมนต์ชนิด &str จะให้ผลลัพธ์ดังนี้:

curious has three consecutive vowels!

อย่างไรก็ตาม ตัวอย่างนี้จะไม่สามารถทำงานได้เลยหากฟังก์ชันของเราประกาศรับอาร์กิวเมนต์เป็นชนิด &String นั่นเป็นเพราะว่า String Slice ที่ได้จาก split มีชนิดข้อมูลเป็น &str ไม่ใช่ &String ซึ่งการจะแปลงเป็น &String จำเป็นต้องมีการจัดสรรหน่วยความจำบน heap (Allocation) ใหม่ และคอมไพเลอร์ไม่สามารถแปลงให้ได้โดยอัตโนมัติ ในขณะที่การแปลงจาก String ไปเป็น &str นั้นแทบไม่มี overhead และคอมไพเลอร์ทำการแปลงให้โดยอัตโนมัติ (implicit)

ดูเพิ่มเติม