Rust 提供強大而安全的字串切片功能,可以有效地從字串中提取部分內容,不需複製也不會轉移所有權。這邊依照四個主題說明:從 String
創建切片、字串字面值與切片、len()
與 UTF-8 字元長度、語法簡寫技巧(Syntactic shortcuts)。
fn main() {
let full_name = String::from("Chen Roger"); // 創建一個 String
let slices_name = &full_name[0..4]; // 從 String 創建一個字串切片
println!("{}", slices_name); // 印出 "Chen"
}
let slices_name = &full_name[0..4]
&
(借用操作符):我們使用借用操作符 &
來表示我們要創建一個引用,而不是取得數據的所有權。字串切片本質上就是一個引用。full_name
這個 String 中獲取一部分。[0..4]
(範圍語法): 這指定了我們想要切片的部分。重要的是,這裡的索引是 基於位元組 (byte) 的索引,而不是基於字元 (character) 的索引。
[0..4]
表示從索引 0 開始,直到索引 4 (但不包含索引 4) 的所有位元組。這恰好是 "Chen" 這四個字元所佔用的位元組。slices_name
的類型是 &str (一個字串切片引用)。它指向 full_name
所擁有的堆積記憶體中 "Chen" 這部分數據。而slices_name
並不擁有這些數據,它只是借用了它們。fn main() {
let first_name = {
let name = "Chen Roger"; // name 是字串字面值,型別為 &str
&name[0..4] // 從 &str 建立另一個 &str 切片
}; // 雖然 name 在內部作用域,但 first_name 仍然有效,因為是借用靜態字串
println!("{}", first_name); // 印出 "Chen"
}
let name = "Chen Roger";
&'static str
。&str
表示它是一個字串切片。'static
(靜態生命週期) 表示這個字串數據是直接嵌入到程式的二進制文件中的,並且在程式的整個運行期間都有效。所以,變數 name
是一個指向這個靜態儲存區域的引用。name
變數離開作用域,因為它是靜態資料,所以 first_name
仍然有效。let first_name = { ... &name[0..4] };
&name[0..4]
:
[0..4]
來從 name
所引用的字串數據中提取一部分。name
本身已經是一個 &'static str
(一個引用),對它進行切片操作 name[0..4]
會得到一個概念上的 str (雖然 str 本身不能直接作為變數類型,因為它的大小不固定)。然後,我們再對這個結果取引用 &
,所以 &name[0..4]
的結果仍然是一個 &str
。name
指向的是具有 'static
生命週期的數據,所以 first_name
得到的切片也將具有 'static
生命週期,即 first_name
的類型也是 &'static str
。它指向靜態記憶體中 "Chen" 這部分。slices_name: &str
依賴於 full_name: String
的存在。如果 full_name
被銷毀,slices_name
就會變成一個懸垂引用(Rust 的借用檢查器會阻止這種情況發生)。first_name: &'static str
是在一個內部作用域中根據 name: &'static str
創建的。當內部作用域結束時,name
這個 變數綁定 本身確實消失了。然而,name
所指向的數據 "Chen Roger" 是一個字串字面值,它存在於程式的靜態記憶體區域,擁有 'static
生命週期,意味著它在整個程式運行期間都有效。first_name
引用的是這塊靜態記憶體中的 "Chen" 部分。儘管創建它的 name
變數 不再存在於 first_name
的作用域中,但 first_name
所引用的 數據 仍然有效。所以,first_name
依賴的是那塊靜態儲存的字串數據,而不是 name
這個局部變數的持續存在。fn main() {
let moji = "🤣"; // 單一 emoji
println!("{}", moji.len()); // 輸出 4(UTF-8 編碼為 4 bytes)
// 下行程式會造成 panic,因為索引切到不完整的 UTF-8 字元
// let moji_slice = &moji[0..3];
// println!("{}", moji_slice);
}
String
與 &str
的 .len()
回傳的是位元組長度,而非字元數量。fn main() {
let hello = "hello";
println!("長度為:{}", hello.len()); // 輸出 5,因為每個字母是 1 byte
let zh = "哈囉";
println!("長度為:{}", zh.len()); // 輸出 6,每個中文字是 3 bytes
}
fn main() {
let action_hero = String::from("Arnold Schwarzenegger");
let first_name = &action_hero[..6];
println!("His first name is {first_name}.");
let last_name = &action_hero[7..];
println!("His last name is {last_name}.");
let full_name = &action_hero[..];
println!("His full name is {full_name}.");
}
[..6]
代表從開頭到第 6 個 byte。[7..]
代表從第 7 個 byte 到字串結尾。[..]
表示整個字串的切片。&action_hero[..]
與 &action_hero
的差異在&action_hero[..]
中,它的意思是選取整個字串,從開頭到末尾。因為是個&str
類型,所以創建了一個指向 action_hero
所擁有的實際字串數據(儲存在堆積上的字元序列)的切片。這個切片覆蓋了 action_hero
的全部內容。&str
是一個「胖指針」(fat pointer),它包含兩個部分:一個指向字串數據起始位置的 指針 (pointer)、切片的 長度 (length),以字節為單位。
因此,當你想要一個不可變的、對字串內容的直接引用時,通常使用 &str
。很多處理字串數據的函式都接受 &str
作為參數,因為它更通用(既可以引用 String 的一部分或全部,也可以引用字串字面值)。
&action_hero
,其型別是&String
(一個對 String 結構體的引用),而String 結構體是一個管理者,它內部包含了指向堆上字串數據的指針,以及該數據的長度和容量 (capacity)。內部結構 (被引用的 String 結構體)中,包含:一個指向堆積上實際字串數據的 指針 (pointer)、字串的目前 長度 (length)、堆上緩衝區的總 容量 (capacity)。當你需要引用整個 String 物件,並且可能需要存取 String 特有的方法或其長度/容量等元數據時,你會使用 &String
。
本質不同:
&action_hero[..] (&str)
是一個對 字串數據本身 的直接視圖/切片。&action_hero (&String)
是一個對 管理字串數據的結構體 的引用。資訊含量:
&str
直接告訴你數據在哪裡以及有多長。&String
告訴你 String 這個「容器」在哪裡,你可以透過這個容器找到數據以及容器的容量等資訊。這節的主軸是以「字串切片作為函式參數」(String Slice as Function Parameters),並說明 &String 和 &str 之間的轉換關係。在 Rust 中,當你編寫一個需要讀取字串數據但不獲取其所有權的函式時,通常最符合習慣且最具彈性的做法是讓參數型別為 &str
(字串切片)。
fn main() {
let action_hero = String::from("Arnold Schwarzenegger");
do_hero_stuff(&action_hero); // 傳入 &String(自動轉為 &str)
let other_action_hero = "Sylvester Stallone"; // 字串字面值為 &str
do_hero_stuff(other_action_hero);
}
fn do_hero_stuff(the_hero: &str) {
println!("{} saves the day.", the_hero);
}
&String
給 &str
參數 ( &String -> &str
)do_hero_stuff(&action_hero);
,在這行程式碼中,&action_hero
創建了一個對String物件action_hero
的引用,因此它的型別是&String
。但是,在 do_hero_stuff
函式期望的參數型別是 &str
。而這個函式能成功轉換是因為 Rust 的一個重要特性叫做 Deref Coercion (解引用轉換)。因為String 型別實作了 Deref <Target = str> Trait
。這個 Trait 允許 String 物件(或者更準確地說,是對 String 的引用)在需要時被自動「解引用」成 str
型別的引用(即 &str
)。所以,當你將 &Strin
g 傳遞給一個 &str
的函式時,編譯器會自動進行轉換,使得 &action_hero
就像一個指向其內部字串數據的 &str
切片一樣被使用。這實際上等同於傳遞了 &action_hero[..]
或 action_hero.as_str()
。&str
(字串字面值) 給 &str
參數do_hero_stuff(other_action_hero);
,在這行程式碼中,other_action_hero
本身即為一個&str
,它與 do_hero_stuff
函式參數 the_hero: &st
r 的型別直接匹配。因此,這裡不需要任何特殊的轉換,可以直接傳遞。&str -> &String
呢?&str
不是 heap 所有者,不能轉為 &String
類型來源 | 型別 | 可否傳給 &str 函式? |
備註 |
---|---|---|---|
字串字面值 | &'static str |
✅ 是 | 最常見,也最安全 |
String 變數 |
String |
✅ 是(需加 & ) |
&String 會自動轉為 &str |
&String |
&String |
✅ 是 | 自動解參照為 &str |
&str |
&str |
✅ 是 | 完全相容 |
&str → String |
❌ 不能直接轉 | 可用 .to_string() 或 .into() |
透過使用 &str
作為函式參數,可以讓你的 API 更通用,同時支援 String
與字串字面值,大幅增加彈性與效率。