String vs &str ใน Rust | muitsfriday.dev

web-logo-doge muitsfriday.dev

String vs &str ใน Rust

เปรียบเทียบความแตกต่างที่อาจจะทำให้งงได้สำหรับมือใหม่ String และ &str ต่างกันอย่างไร

Wed Jun 29 2022

บทความสั้นๆ อธิบายความแตกต่างระหว่าง String และ &str ใน Rust

ตัวแปรประเภทชุดตัวอักษร ใน Rust 🔤

หากเราอยากสร้างตัวแปรที่เก็บค่าชุดตัวอักษรใน Rust เราสามารถทำได้อยู่สองแบบ

let text = String::from("hello world")

และ

let text = "hello world"

ถ้าเราใช้ rust-analyzer ส่องดู type ของตัวแปรที่เราสร้างมาจะเห็นว่าการสร้างแบบแรกตัวแปรจะมี type เป็น String ในขณะที่แบบที่สองได้เป็น &str

Rust String vs &str creation

String 🟣

ตัวแปรประเภท String ใน Rust เปรียบเสมือนก้อน object ที่ควบคุมค่า string ที่เก็บอยู่ข้างในได้สมบูรณ์แบบ เราสามารถต่อ ตัด ดัดแปลงค่านั้นได้อย่างอิสระ มี method ให้ใช้มากมาย เปรียบเสมือน String object ใน Java

fn main() {
    let mut text = String::from("hello");
    text.push_str(" world");
    assert_eq!(text, String::from("hello world"));

    let mut name = String::from(" Jake ");
    assert_eq!(name.trim(), "Jake");
}

ดูว่า String มี method อะไรให้ใช้บ้างได้ที่นี่

สิ่งสำคัญเลยคือ เมื่อเราสั่งสร้าง String ขึ้นมาใหม่ ตัวแปรที่ได้มาจะเป็นเจ้าของค่านั้นทันที หมายความว่าเราสามารถเอาค่านี้ให้ไครยืมไปใช้ก็ได้ หรือย้ายความเป็นเจ้าของค่านี้ไปที่อื่นได้เช่นกัน

Rust String Visualization

&str 🟢

&str เป็นการ reference ไปยังข้อมูลชุดตัวอักษรใน heap สิ่งที่แตกต่างกับ String ก็คือตัวแปรประเภทนี้จะไม่ได้เป็นเจ้าของค่าที่ ref ไปหาแต่เป็นการขอยืมมาใช้เท่านั้น มีชื่อเรียกทางการว่า string slice

เมื่อเราสั่ง let text = "hello world" compiler จะไปสร้างค่า "hello world" เอาไว้ที่ static storage แล้วตัวแปรที่เราสร้างก็จะไปขอยืมค่ามาใช้

str โดยตัวมันเองแล้วหมายถึงชุดตัวอักษรที่แก้ไขไม่ได้ เราจะไม่ได้ใช้ตัวนี้ ปกติเราจะใช้เป็น &str ในการเขียนโปรแกรมมากกว่า

การที่ &str คือ reference ไปยังชุดอักษรที่แก้ไขค่าไม่ได้ดังนั้น method ส่วนใหญ่ที่มันมีจะเป็นการอ่านค่าของมัน หรือไม่ก็คายค่าอื่นๆ อันใหม่ออกมาโดยไม่แก้ไขอะไรในตัวเอง

&str ยังคงเป็น reference type อยู่ดังนั้นต้องการให้ตัวค่าที่__ยืม__มามีช่วงอายุขัย (lifetime) อยู่นานเพียงพอให้ใช้งานด้วย

Rust &str Visualization

เมื่อไหร่ควรใช้ String และ &str ⛹️‍♂️

เราจะใช้ String ก็ต่อเมื่อเราต้องการเป็นเจ้าของ String ก้อนนั้นและอยากที่จะเพิ่มลด ต่อเติมแก้ไข String ก้อนนั้นอย่างอิสระ การใช้ String เปรียบเทียบได้กับ String object ของภาษาอื่นๆ ใช้งานทั่วไปง่าย เข้าใจง่าย

ข้อควรระวังในการใช้ String คือตัวแปรนี้เก็บค่าใน heap เวลาเราส่งค่าเข้าฟังก์ชันจะเกิดการ move ของ ownership ขึ้น

fn main() {
    let mut text = String::from("hello");
    foo(text);
    println!("print from main: {}", text);
}

fn foo(s: String) {
    println!("string: {}", s)
}

string slice &str ถูกใช้เมื่อเราแค่อยากขอยืมค่ามาใช้ไวๆ เพื่อการอ่านหรือเพื่อเป็นค่าตั้งต้นเพื่อผลิตเป้นค่าใหม่ออกมา

// อ่านค่าเพื่อเอาไปใช้งานเฉยๆ ไม่แก้ไขอะไร
fn foo(s: &str) {
    println!("string: {}", s)
}

String ถูก Deref ลงไปเป็น str ได้ 🥗

เพื่อความสะดวกสบายเวลาเราสร้างฟังก์ชันที่รับ &str เราสามารถใส่ String ลงไปแทนได้ดังตัวอย่างนี้

fn main() {
    let mut text = String::from("hello");
    foo(&text);
    println!("print from main: {}", text);
}

fn foo(s: &str) {
    if s.len() % 2 == 0 {
        println!("even len");
    } else {
        println!("odd len");
    }
}

ที่ทำแบบนี้ได้เพราะ String ได้ทำการ implement trait Deref<target=str> เอาไว้ Type ในก็ตามที่ implement trait นี้จะมีการแปลงค่าให้อัตโนมัติ (deref coercion) เมื่อมีการส่งผ่านพาราติเตอร์เข้าฟังก์ชัน โดยจะแปลงจาก ref ของ type ที่ implement trait ไปยัง target ให้ (ในที่นี้คือ &String -> &str)

การแปลงไปมาระหว่าง String และ &str 🦉

ในทางปฏิบัติเราสามารถแปลงค่าสองประเภทนี้ไปมาได้

ถ้าเรามี String แล้วอยากเปลี่ยนให้เป็น &str แบบที่ไม่ต้องส่งลงไปในฟังก์ชันเพื่อให้ compiler ทำการ deref coercion ให้เราก็ใช้ method ที่มีเอาไว้ให้ได้ let r = text.as_str();

หรือเราอยากแปลง &str ไปเป็นค่า String เราสามารถสร้าง String ขึ้นมาใหม่จาก &ref ที่มีอยู่ได้ด้วยการ

let text1 = String::from(your_string_slice);
// หรือ
let text2 = "hello world".to_owned();
// หรือ
let text3 = "hello world".to_string();

สรุป 🔁

ใน Rust ชนิดข้อมูล String และ &str(string slice) มีความแตกต่างกันสิ้นเชิง แต่สามารถแปลงไปมากันได้

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