字串與集合
實際的程式大量處理文字與成批的資料。本頁介紹三個最常用的堆積型別:字串 String、動態陣列 Vec<T>、鍵值對照表 HashMap<K, V>。它們都擁有自己的資料,會在離開作用域時自動釋放。
字串:String 與 &str
Rust 有兩種主要的字串型別:
| 型別 | 說明 | 使用時機 |
|---|---|---|
String |
擁有資料、可成長的字串,存在堆積上 | 需要建立、修改或儲存字串 |
&str |
字串切片,唯讀參照 | 函式參數、字串字面值 |
fn main() {
let mut s = String::from("Hello");
s.push_str(", Rust");
s.push('!');
println!("{s}");
let a = String::from("Hello");
let b = String::from("World");
let joined = format!("{a}, {b}"); // format! 不會取走 a、b 的所有權
println!("{joined}");
let borrowed: &str = &joined; // String 可以借用成 &str
println!("{}", borrowed.len());
}
字串是 UTF-8,不能用整數索引
Rust 字串以 UTF-8 編碼,一個中文字佔 3 個位元組。len() 回傳的是位元組數,不是字元數,所以 Rust 不允許 s[0] 這種寫法。要處理字元,請用 chars():
fn main() {
let s = "你好,Rust";
println!("位元組數:{}", s.len());
println!("字元數:{}", s.chars().count());
for c in s.chars() {
print!("[{c}]");
}
println!();
}
Vec:動態陣列
Vec<T> 是可以成長的陣列,元素型別必須相同:
fn main() {
let mut v = vec![1, 2, 3];
v.push(4);
v.push(5);
println!("{:?},長度 {}", v, v.len());
// 用索引取值:越界會 panic
println!("{}", v[0]);
// 用 get 取值:回傳 Option,較安全
match v.get(10) {
Some(n) => println!("{n}"),
None => println!("索引 10 不存在"),
}
// 走訪並修改
for n in &mut v {
*n *= 2;
}
println!("{:?}", v);
// 移除最後一個元素
let last = v.pop();
println!("{:?} {:?}", last, v);
}
for n in &mut v 取得每個元素的可修改參照,需要用 *n 解參照才能改值。
走訪時不能修改 Vec 本身
在走訪 Vec 的同時呼叫 push 會違反借用規則(走訪持有參照,push 需要可修改參照),編譯器會拒絕。這正是借用規則在保護你,避免因為重新配置記憶體造成參照失效。
HashMap:鍵值對照表
HashMap 需要先引入,儲存的順序不固定:
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("小明"), 90);
scores.insert(String::from("小華"), 85);
// 取值:回傳 Option
if let Some(score) = scores.get("小明") {
println!("小明:{score}");
}
// 覆寫
scores.insert(String::from("小明"), 95);
// 走訪(順序不固定)
let mut names: Vec<_> = scores.keys().collect();
names.sort();
for name in names {
println!("{name}:{}", scores[name]);
}
}
entry:不存在才插入
統計次數是 HashMap 的經典用法,entry(...).or_insert(...) 能一次處理「有就更新、沒有就新增」:
use std::collections::HashMap;
fn main() {
let text = "the quick brown fox jumps over the lazy dog the end";
let mut counts: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
let mut pairs: Vec<_> = counts.iter().collect();
pairs.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0)));
for (word, count) in pairs.iter().take(3) {
println!("{word}: {count}");
}
}
or_insert 回傳該值的可修改參照,所以用 * 解參照後就能直接加一。
該選哪一個?
| 需求 | 選擇 |
|---|---|
| 固定長度、編譯時已知 | 陣列 [T; N] |
| 一串會增減的同型別資料 | Vec<T> |
| 用鍵快速查值 | HashMap<K, V> |
| 建立、修改文字 | String |
| 只讀取文字(函式參數) | &str |
推薦影音
字串
簡述:微軟《Beginner's Series to Rust》第 29 集,介紹 String 與 &str 的差別與常見操作。
常見集合
簡述:Let's Get Rusty 依照官方書籍第 8 章製作,依序講解 Vec、String 與 HashMap,對應本頁全部內容。
資料建模到此完成。接下來學習如何寫出可重用的程式碼:抽象與錯誤處理。