slice 也是 borrow 的一種,但是可以 borrow 連續結構(String、array、vector....)的片段
一片檸檬,一口茶,一個章節,一段時光
Photo by Joanna Kosinska on Unsplash |
先來看一個簡單的例子
first_word 函式是要尋找傳入字串的第一個單字
所以找到第一個空白就回傳,無空白就回傳整個字串長度
在 main 中拿到第一個單字最後的索引之後就可以透過原始 s 拿到第一個單字
fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() { let mut s = "Hello World".to_string(); let first_index = first_word(&s); s.clear(); println!("{}", first_index); println!("{}", s.as_bytes()[first_index]); // panic! }但是有一個危險的事情發生了
上面的程式碼 first_index 會得到 5,不過 s 已經先執行了 clear() 了
所以若是透過 first_index 去拿字串就會 panic (程式異常中斷)
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 5'
Slices
因此就來介紹 slice與 borrow 相似,但是 slice 借用的只有片段而不是全部
對於 String、&str 來說 slice 的型別都是 &str
使用 [開始..結束] 來表示範圍,注意,包含開始,不包含結束,使用 usize 表示位置
在 1.26.0 後可以使用 = 表示要包含結束
[..結束] 表示從 0 開始
[開始..] 表示到結尾
[..] 表示整個連續結構
e.g.
let s = "Hello world".to_string(); let slice1 = &s[..=4]; // "Hello" .... 1.26.0 stable let slice2 = &s[6..]; // "World"詳細關係如下圖:
右邊的 "Hello World" 是 string literal,會寫死在執行檔中
左邊 s 是 String,可以發現其指向 string literal
另外兩個是 slice 它們指向片段的 string literal
String v.s. &str
這裡就可以知道 String 和 &str 差異的根本了去看 String 的原始碼可以發現,String 是一個向量 (Vec<u8>)
所以長度可變,而向量也存在 slice 的用法
而也可以知道為何大部分都是使用 &str 而不是 str
因為 str 是寫死在執行檔中,需要透過借用 (&str) 去讀取它的值
此借用是不可變的借用,所以才說 &str 長度固定
修改程式
fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[..i]; // 回傳 slice 而不是 index } } &s[..] } fn main() { let mut s = "Hello World".to_string(); let first_word = first_word(&s); s.clear(); println!("{}", first_word); }
這樣 compile 時就會出現錯誤,而不等到執行時期才 panic
錯誤原因很明顯,因為函式內部會使用到 borrow
但因為 borrow 的規則,不能同時可變、不可變
所以這樣寫程式是錯的,必須要改方式去寫
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src\main.rs:45:5 | 44 | let first_index = first_word(&s); | - immutable borrow occurs here 45 | s.clear(); | ^ mutable borrow occurs here 46 | println!("{}", first_index); 47 | } | - immutable borrow ends here
正確的 main() 裡面應該改成這樣
let mut s = "Hello world".to_string(); { let first_index = first_word(&s); println!("{}", first_index); } s.clear();
習慣
通常用到 String 的 slice 時,函式的寫法傾向於傳入 slice原因就是寫成 slice 就可以處理 String 和 &str
因為 String 轉成 &str 只需要取 slice
反之 &str 轉成 String 則需要用到 to_string(),就會需要額外的資源
所以大部分不寫成這樣:
fn first_word(s: &String) -> &str {
而是
fn first_word(s: &str) -> &str {
呼叫時:
/* s1 是 String */ first_word(&s1[..]); first_word(&s1); /* s2 是 &str */ first_word(&s2[..]); first_word(&s2); first_word(s2); /* 發現不管是 String 或 &str 都可以用 */ first_word(&s);
沒有留言:
張貼留言