Rust 學習筆記:參考與切片
October 6, 2023 · Chinese
前言
本系列文書寫我個人初探 Rust 的學習筆記,章節劃分主要基於著名的 The Book,The Rust Programming Language,程式碼部分通常是個人閱讀消化後以方便說明的方式撰寫,完整學習建議直接參見該書。
The Rust Programming Language
該書也有中文翻譯版,不過個人閱讀以英文原版為主以鞏固對 terminology 的一致認識,我認為對未來閱讀以及查找資料會較為順暢。
不論語言該書都是相當優秀的學習資源,選擇適合你的語言開始學習 Rust 吧。
Appendix F: Translations of the Book
參考
A reference is like a pointer in that it’s an address we can follow to access the data stored at that address;
在 Rust 中參考就像是 pointer,和 pointer 的差異是在 Rust 中參考保證值是 valid 的。
參考以下程式,say_hello
接受一個 name
參數,print 出結果。這是我們以往的作法:
fn main() { let s = String::from("Josh"); say_hello(s); println!("{}", s); // ERROR: borrow of moved value: `s` } fn say_hello(name: String){ println!("Hello {}!", name); }
這邊會發現若我們在後面要繼續使用 s
會出現報錯,因為 s
在傳入函式時產生了所有權的移轉。若我們要再次使用會需要在函式中把 s 再 return 出來,即把所有權轉移回來。
另一種方式是使用參考,寫法是以 &
prefix:
fn main() { let s = String::from("Josh"); say_hello(&s); // Hello Josh! println!("{}", s); // Josh } fn say_hello(name: &String){ println!("Hello {}!", name); }
利用參考不產生所有權移轉,因此我們即使不做回傳也能在 main 中繼續使用 s
。
可變參考
參考與值一樣預設是不可變的,一樣可以利用 &mut
讓其可變。
let mut s = String::from("Josh"); let s1 = &s; s1.push('1'); // cannot borrow `*s1` as mutable, as it is behind a `&` reference println!("{}", s1);
let mut s = String::from("Josh"); let s1 = &mut s; s1.push('1'); println!("{}", s1); // Josh1
參考的規則
- 同時只能有無限的不可變參考,或一個可變參考。
- 參考必須永遠合法 (valid)
不可變參考數量不限
let mut s = String::from("Josh"); let s1 = &s; let s2 = &s; println!("{}", s1); // Josh println!("{}", s2); // Josh
可變參考只能有一個
let mut s = String::from("Josh"); let s1 = &mut s; let s2 = &mut s; println!("{}", s1); // ERROR: cannot borrow `s` as mutable more than once at a time println!("{}", s2);
不可同時有不可變參考與可變參考
let mut s = String::from("Josh"); let s1 = &s; let s2 = &mut s; println!("{}", s1); println!("{}", s2); // ERROR: cannot borrow `s` as mutable because it is also borrowed as immutable
迷途指標 dangling pointer
Rust 中會保證參考的合法性,產生迷途指標會直接報錯。
以下是簡單的範例程式,在 get_dangling_pointer
我們 return s
的參考,然而我們知道 s 在函式結束後便被釋放,我們回傳的參考便指向一個已經被釋放的東西上,即為迷途指標。
Rust 在這種情況給出的錯誤訊息為 missing lifetime specifier,涉及到之後會學習的 lifetime。
fn main() { let s = get_dangling_pointer(); // missing lifetime specifier } fn get_dangling_pointer() -> &String { let s = String::from("Josh"); &s }
切片 Slice
看了上述的迷途指標,顯然我們無法在函式內建立新的 String 將其回傳。
切片型別便是一種解決方案,字串切片是字串某一段部分的參考。
不只字串有切片型別,
let s = String::from("Hello Josh"); let hello = &s[0..5]; println!("{}", hello); // Hello
使用 [..]
語法指定一段範圍,若以字串開頭為起始則 0 可省略,以字串末尾為結束則最後的 index 可省略。
let s = String::from("Hello"); println!("{}", &s[1..4]); // ell println!("{}", &s[0..5]); // Hello println!("{}", &s[..5]); // Hello println!("{}", &s[0..]); // Hello println!("{}", &s[..]); // Hello
String literal
string literal 的型別為字串切片 &str
。
let s: &str = "Hello";
切片作為參數
以字串切片作為參數會比 &String
更具彈性,同時能夠接受切片與字串參考。
fn main() { let name_string = String::from("Josh"); let name_slice = &name_string[..]; say_hello(&name_string); // OK: Hello Josh say_hello(name_slice); // OK: Hello Josh } fn say_hello(name: &str){ println!("Hello {}", name); }
fn main() { let name_string = String::from("Josh"); let name_slice = &name_string[..]; say_hello(&name_string); // OK: Hello Josh say_hello(name_slice); // ERROR: mismatched types } fn say_hello(name: &String){ println!("Hello {}", name); }
切片作為回傳值
有了切片我們有回傳一部分字串的方法了。
下面程式回傳字串的前半段。
fn main() { let s = "I am handsome"; let first_half = get_first_half(s); println!("{}", first_half); // I am h } fn get_first_half(s: &str)->&str{ &s[..s.len() / 2] }