String vs &str ใน Rust
เปรียบเทียบความแตกต่างที่อาจจะทำให้งงได้สำหรับมือใหม่ String และ &str ต่างกันอย่างไร
Wed Jun 29 2022
muitsfriday.dev
เปรียบเทียบความแตกต่างที่อาจจะทำให้งงได้สำหรับมือใหม่ String และ &str ต่างกันอย่างไร
Wed Jun 29 2022
บทความสั้นๆ อธิบายความแตกต่างระหว่าง String และ &str ใน Rust
หากเราอยากสร้างตัวแปรที่เก็บค่าชุดตัวอักษรใน Rust เราสามารถทำได้อยู่สองแบบ
let text = String::from("hello world")
และ
let text = "hello world"
ถ้าเราใช้ rust-analyzer ส่องดู type ของตัวแปรที่เราสร้างมาจะเห็นว่าการสร้างแบบแรกตัวแปรจะมี type เป็น String
ในขณะที่แบบที่สองได้เป็น &str
ตัวแปรประเภท 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 ขึ้นมาใหม่ ตัวแปรที่ได้มาจะเป็นเจ้าของค่านั้นทันที หมายความว่าเราสามารถเอาค่านี้ให้ไครยืมไปใช้ก็ได้ หรือย้ายความเป็นเจ้าของค่านี้ไปที่อื่นได้เช่นกัน
&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) อยู่นานเพียงพอให้ใช้งานด้วย
เราจะใช้ 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)
}
เพื่อความสะดวกสบายเวลาเราสร้างฟังก์ชันที่รับ &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 แบบที่ไม่ต้องส่งลงไปในฟังก์ชันเพื่อให้ 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 จะดูดีกว่ามาก