切片與字串型別(slice, String, &str)
在 C 裡,一段連續的資料通常用兩個你必須自己保持同步的鬆散部件來描述:一個指向第一個元素的指標,和一個獨立的長度(對字串而言,還有一個標記結尾的 NUL 字元)。一旦弄丟長度、或讀過了結尾字元,你就跑到陣列外頭去了——這就是經典的緩衝區越界。Rust 把指標和長度打包成一個安全、會做邊界檢查的東西:切片。切片,寫作 &[T],是對住在別處、一連串型別為 T 的值的一個借來的視圖——它同時知道資料從哪裡開始、有幾個元素,所以語言能拿每次存取去對照那個長度做檢查。
字串有兩種相關的型別,初學者常常搞混,而這個分別正是你已經懂的所有權分別。String 是一個被擁有的、可成長的、配置在堆積上的 UTF-8 字串——它擁有自己的位元組,並在它釋放時釋放它們,很像一個擁有 malloc 緩衝區的值。&str(讀作 string slice,字串切片)是對某段既有 UTF-8 文字的借來視圖——它什麼也不擁有,只是一個指標加一個長度,指進別人擁有的位元組裡。你原始碼裡像 "hello" 這樣的字串字面值,就是一個指向程式唯讀資料的 &str。日常的模式是用 String 來存放與建構文字,但在函式參數裡接收 &str,因為一個只需要讀文字的函式應該借一個視圖,而非要求所有權——而且 &String 可以自動被借成 &str,所以 &str 參數兩者都收。
為何重要:切片是 Rust 給你 C 那份效率的方式——傳遞資料的視圖而不複製——同時把長度附在上頭,讓越界存取被抓到(它會安全地恐慌,而非讀進野記憶體)。而 String 對比 &str 的分裂,不過就是所有權對比借用套用到文字上:需要保留或修改時就擁有它,只需要讀時就借一個 &str。初學者常見的絆腳石是出於習慣到處用 String;慣用的做法是接收 &str 引數,這對呼叫者既更有彈性,也避免不必要的複製。
fn shout(text: &str) -> String { // 借來讀取,回傳一個被擁有的結果 text.to_uppercase() } let owned = String::from("hi"); // 擁有堆積位元組 let view: &str = &owned; // 指進它們的一個借來切片 println!("{}", shout(view)); // HI (字面值 "hi" 也是 &str)
String 擁有它的位元組;&str 借來一個做邊界檢查的視圖。唯讀參數優先用 &str。
切片帶著它的長度,所以索引會做邊界檢查,超出範圍的索引會恐慌而非讀進野記憶體。String 對比 &str 不過是「被擁有」對比「借來」套用到文字上——不是兩個無關的字串類別。