在 Rust 的世界裡,集合 (Vec
, HashMap
) 定義了資料的「擁有權邊界」。
這一篇把邊界再往內切一刀:如何將「一段資料」從「整份資料」中分離出來,並以零拷貝 (zero-copy) 的方式安全共享。
其精髓只有一句話:將資料的視圖 (View) 從容器 (Container) 中抽離,只留下語意與約束。
&[T]
與 &str
是一種不擁有資料的參考型別。
它們的主要語意是作為一個指向某段連續記憶體的 「借用視圖」。
它們本身不負責記憶體的生與死,只提供一個有範圍、有約束的存取窗口。
這個設計將「我擁有這整份資料」的權力,與「我只想讀取其中一段」的需求徹底分離,讓資料共享變得幾乎沒有成本,且沒有意外的副作用。
切片是將「資料的一部分」提升為一等公民,讓你傳遞視圖,而非被迫複製資料。
fn main() {
// 建立擁有者 (Vec<T>),它在記憶體中配置一塊連續的資料。
let data: Vec<i32> = vec![10, 20, 30];
// 從 'data' 建立一個切片視圖 (&[T])。
// 包含索引 1,但不包含索引 3。也就是索引 1 和 2。
let view: &[i32] = &data[1..3];
}
我們可以將資料互動分為三個層次,每一層都是一道更嚴格的責任邊界:
擁有者 (Owner) - String
/ Vec<T>
:擁有資料,承擔配置與釋放記憶體的全部責任。
完整借用 (Borrow) - &String
/ &Vec<T>
:暫時借用整個容器的讀取權,責任仍在擁有者身上。
視圖 (View) - &str
/ &[T]
:只借用一段範圍的存取權,形成比容器更窄、更通用的責任區段。
如果一個函式只需要讀取資料,就給它一個不可變的視圖 (&[T]
)。
如果它只需要讀取一個字串,就給它 &str
。
不要交出整個容器的所有權或可變性。
String
與 &str
的彈性// 讀取視圖,回傳一個新的、擁有所有權的 String
fn shout(s: &str) -> String {
s.to_uppercase()
}
fn main() {
let owned_string = String::from("hello");
let string_literal = "world";
let part_of_string = &owned_string[0..2]; // "he"
// 同一個函式,無縫接軌三種不同來源的字串資料
let a = shout(&owned_string);
let b = shout(string_literal);
let c = shout(part_of_string);
}
讀 (&[T]
/ &str
):傳遞一個只讀視圖。這是最常見、最安全、零成本的操作。
改 (&mut [T]
):傳遞一個唯一的可變視圖。允許原地修改,但 Rust 的借用規則會保證在同一時間只有這一個可變視圖存在,將副作用完美地限制在一個範圍內。
搬 (Vec<T>
/ String
):轉移擁有權。當你需要為資料找到新的擁有者時使用。這是一個清晰的邊界劃分。
let mut data = vec![10, 20, 30, 40, 50];
// 讀:任何需要 &[i32] 的 API 都能接受
fn sum(xs: &[i32]) -> i32 { xs.iter().sum() }
let total = sum(&data);
// 改:以 &mut [i32] 原地處理
fn scale(xs: &mut [i32], k: i32) { for x in xs { *x *= k; } }
scale(&mut data[1..4], 2); // 只改中間那段
// 視圖:子切片零拷貝
let mid: &[i32] = &data[1..4];
切片只是一把「尺」,它量測的是其「母體」資料的一段長度。
如果母體資料消失了,這把尺的度量就失去了意義。
因此,Rust 編譯器強制規定:&[T]
或 &str
的生命週期,永遠不能超過其來源資料的生命週期。
切片不擁有資料,其創建永遠是零拷貝的。
切片的有效性由來源資料的生命週期決定,由編譯器保證安全。
切片讓 API 從「綁定具體容器」升級為「面向抽象資料視圖」。
Rust 鼓勵我們將這些檢查移到編譯期。
String
?」——簽名改成 &str
就好。&mut [T]
的唯一借用保證了排他性。Rust 的切片 (Slice)
機制不只是一個好用的工具,它是一種設計哲學的體現:
以視圖收斂 API 入口:用 &str
和 &[T]
接受所有相容的資料來源,提升通用性。
以型別保障共享安全:讓編譯器代替你在執行期擔心生命週期和資料競爭。
以權限約束副作用:用 &mut [T]
將可變操作限制在最小的必要範圍內。
讓成本變得透明:當你需要新擁有者時,再透過 String::new()
或 .to_vec()
等方法明確地承擔記憶體分配的成本。
這套系統的主要思考是:不要輕易複製記憶體,而是給出一個帶有長度和生命週期約束的指標,然後讓強大的編譯器確保你不會搞砸。