字串(String)#
Rust 有兩種主要的字串型別:&str 和 String。
理解它們的差異,對於學習後續的所有權(Ownership)概念至關重要。
1. 字串切片 &str#
&str 是對字串資料的 不可變參考 ,通常稱為「字串切片」。
字串字面值的型別就是 &str,資料直接嵌入在編譯後的執行檔中,因此永遠有效且不可變:
1
| let hello: &str = "Hello, World!";
|
&str 只是指向某段 UTF-8 字串資料的指標,本身不擁有資料。
2. String 型別#
String 是可成長、可變、有所有權的字串,資料儲存在堆積(Heap)上:
1
2
3
4
| let mut s = String::from("Hello");
s.push_str(", World!"); // 附加字串
s.push('!'); // 附加單一字元
println!("{s}"); // Hello, World!!
|
String::from() 是建立 String 最常見的方式,它將字串字面值的內容 複製到堆積 上,並取得所有權。
3. &str 與 String 的比較#
| &str | String |
|---|
| 儲存位置 | 堆疊(指標)+ 唯讀記憶體 | 堆積 |
| 可變性 | 不可變 | 可變 |
| 所有權 | 借用,不擁有 | 擁有 |
| 大小 | 編譯時已知 | 執行時動態 |
| 建立方式 | "literal" | String::from("...") |
4. 兩者互轉#
&str → String:
1
2
3
| let s1: &str = "hello";
let s2: String = s1.to_string();
let s3: String = String::from(s1); // 同上
|
String → &str:
1
2
3
| let s: String = String::from("hello");
let slice: &str = &s; // 自動解參考
let slice2: &str = &s[..]; // 明確取全部切片
|
5. 字串切片(Slice)#
可以用索引範圍取得字串的一部分,回傳 &str:
1
2
3
4
| let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
let all = &s[..]; // "hello world"
|
注意 :Rust 字串是 UTF-8 編碼,索引以 位元組 計算,切在多位元組字元中間會 panic。中文字每個字元佔 3 個位元組:
1
2
3
| let s = String::from("你好世界");
// let ch = &s[0..1]; // panic!切在字元中間
let ch = &s[0..3]; // "你"(3 個位元組)
|
6. 常用方法#
1
2
3
4
5
6
7
8
9
10
11
| fn main() {
let s = String::from(" Hello, Rust! ");
println!("{}", s.len()); // 長度(位元組數)
println!("{}", s.is_empty()); // 是否為空
println!("{}", s.contains("Rust")); // 是否包含子字串
println!("{}", s.trim()); // 去除首尾空白
println!("{}", s.to_lowercase()); // 轉小寫
println!("{}", s.to_uppercase()); // 轉大寫
println!("{}", s.replace("Rust", "World")); // 替換
}
|
7. 字串串接#
使用 + 運算子:
+ 會取走左側 String 的所有權,右側需傳入 &str:
1
2
3
4
5
| let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1 的所有權移入,s2 仍有效
// println!("{s1}"); // 錯誤!s1 已失效
println!("{s3}");
|
使用 format! 巨集(較推薦):
format! 不取走任何所有權,使用更直覺:
1
2
3
4
5
| let s1 = String::from("Hello");
let s2 = String::from("World");
let s3 = format!("{s1}, {s2}!");
println!("{s3}"); // Hello, World!
// s1、s2 仍然有效
|
8. 迭代字元#
因為 UTF-8 編碼的關係,不能直接用索引取得字元,要用 .chars() 迭代:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| fn main() {
let s = String::from("你好Rust");
for c in s.chars() {
println!("{c}");
}
// 你
// 好
// R
// u
// s
// t
println!("字元數:{}", s.chars().count()); // 6
println!("位元組數:{}", s.len()); // 10(中文各3字節)
}
|
9. 函數參數的慣例#
函數接受字串時,慣用 &str 而非 &String,因為 &String 可以自動強制轉型(coerce)為 &str,但反過來不行。這樣函數可以同時接受字串字面值和 String:
1
2
3
4
5
6
7
8
| fn greet(name: &str) {
println!("你好,{name}!");
}
fn main() {
greet("Alice"); // &str 直接傳入
greet(&String::from("Bob")); // &String 自動轉為 &str
}
|
Reference#
https://doc.rust-lang.org/book/ch08-02-strings.html