CRust 學習筆記:生命週期 - 1
本系列文章是 Jon Gjengset 發佈的 CRust of Rust 系列視頻的學習筆記,CRust of Rust 是一系列持續更新的 Rust 中級教程。
在這篇文章中,我們將研究一個需要顯式註釋多個生命期的例子。我們還將討論不同字符串類型之間的一些差異,以及在自定義的 trait 上引入泛型。這個例子是根據給定的字符串和分隔符對字符串進行拆分。
創建一個新項目:
cargo new --lib strsplit
lib.rs 中寫入如下代碼:
1#[derive(Debug)]
2pub struct StrSplit<'a> {
3 remainder: &'a str,
4 delimiter: &'a str,
5}
6
7#[allow(dead_code)]
8impl<'a> StrSplit<'a> {
9 pub fn new(haystack: &'a str, delimiter: &'a str) -> Self {
10 Self {
11 remainder: haystack,
12 delimiter,
13 }
14 }
15}
16
17impl<'a> Iterator for StrSplit<'a> {
18 type Item = &'a str;
19
20 fn next(&mut self) -> Option<Self::Item> {
21 if let Some(next_delim) = self.remainder.find(self.delimiter) {
22 let until_remainder = &self.remainder[..next_delim];
23 self.remainder = &self.remainder[next_delim + self.delimiter.len()..];
24 Some(until_remainder)
25 }else if self.remainder.is_empty() {
26 None
27 }else {
28 let rest = self.remainder;
29 // 爲什麼空字符串可以賦值給self.remainder ???
30 self.remainder = "";
31 Some(rest)
32 }
33 }
34}
35
36#[test]
37fn it_works() {
38 let haystack = "a b c d e";
39 let letters: Vec<_> = StrSplit::new(haystack, " ").collect();
40 assert_eq!(letters, vec!["a", "b", "c", "d", "e"]);
41}
-
StrSplit 與成員 remainder,delimiter 擁有相同的生命週期
-
使用 fn new() 方法構建的 StrSplit 與傳入的參數 haystack,delimiter 擁有相同的生命週期
-
在實現 Iterator trait 時,迭代的結果也要與 StrSplit 擁有相同的生命週期,是因爲要在 StrSplit 的成員 remainder 上做迭代。
在第 30 行,爲什麼空字符串可以賦值給 self.remainder?這是因爲 self.remainder 的生命週期是 &'a str,空字符串的生命週期是 &'static str,static 的生命週期一直到程序結束。
修復 Bug
這裏有一個 bug,添加如下測試方法:
1#[test]
2fn tail() {
3 let haystack = "a b c d ";
4 let letters: Vec<_> = StrSplit::new(haystack, " ").collect();
5 assert_eq!(letters, vec!["a", "b", "c", "d", ""]);
6}
執行 cargo test:
running 2 tests
test str_split_1::it_works ... ok
test str_split_1::tail ... FAILED
我們將 struct StrSplit 的成員 remainder 定義爲 Option<&'a str> 類型來修復這個 bug:
1/**
2 * StrSplit 與成員 remainder,delimiter 擁有相同的生命週期
3 */
4#[derive(Debug)]
5pub struct StrSplit<'a> {
6 // 使用Option
7 remainder: Option<&'a str>,
8 delimiter: &'a str,
9}
10
11#[allow(dead_code)]
12impl<'a> StrSplit<'a> {
13 /**
14 * 新構建的StrSplit與傳入的參數haystack,delimiter 擁有相同的生命週期
15 */
16 pub fn new(haystack: &'a str, delimiter: &'a str) -> Self {
17 Self {
18 remainder: Some(haystack),
19 delimiter,
20 }
21 }
22}
23
24impl<'a> Iterator for StrSplit<'a> {
25 // 迭代的結果也要與StrSplit擁有相同的生命週期,是因爲要在StrSplit的成員remainder上做迭代。
26 type Item = &'a str;
27
28 fn next(&mut self) -> Option<Self::Item> {
29 // 這裏爲什麼用Some(ref mut remainder),而不用Some(&mut refmainder) ???
30 if let Some(ref mut remainder) = self.remainder {
31 if let Some(next_delim) = remainder.find(self.delimiter) {
32 let until_remainder = &remainder[..next_delim];
33 *remainder = &remainder[next_delim + self.delimiter.len()..];
34 Some(until_remainder)
35 }else {
36 self.remainder.take()
37 }
38 }else {
39 None
40 }
41 }
42}
43
44#[test]
45fn it_works() {
46 let haystack = "a b c d e";
47 let letters: Vec<_> = StrSplit::new(haystack, " ").collect();
48 assert_eq!(letters, vec!["a", "b", "c", "d", "e"]);
49}
50
51#[test]
52fn tail() {
53 let haystack = "a b c d ";
54 let letters: Vec<_> = StrSplit::new(haystack, " ").collect();
55 assert_eq!(letters, vec!["a", "b", "c", "d", ""]);
56}
執行 cargo test,測試通過:
running 2 tests
test str_split_2::tail ... ok
test str_split_2::it_works ... ok
在上面代碼的第 30 行,爲什麼用 Some(ref mut remainder),而不是用 Some(&mut refmainder)?這是因爲在進行 Some(&mut remainder)=self.remainder 模式匹配時,remainder 會被自動解引用成 str 類型,而不是可變的 & str 類型。
另一種寫法
1impl<'a> Iterator for StrSplit<'a> {
2 // 迭代的結果也要與StrSplit擁有相同的生命週期,是因爲要在StrSplit的成員remainder上做迭代。
3 type Item = &'a str;
4
5 fn next(&mut self) -> Option<Self::Item> {
6 // 爲什麼不可以這麼寫???
7 let remainder = &mut self.remainder?;
8 if let Some(next_delim) = remainder.find(self.delimiter) {
9 let until_remainder = &remainder[..next_delim];
10 *remainder = &remainder[next_delim + self.delimiter.len()..];
11 Some(until_remainder)
12 }else {
13 self.remainder.take()
14 }
15 }
16}
17
在迭代器的 next 方法裏嘗試換一種寫法,編譯器檢查通過,但是執行測試不通過。也就是上面代碼的第 7 行,爲什麼不可以這麼寫?
self.remainder 是 Option<&'a str> 類型,這裏的泛型是引用。所以在執行 unwrap(),expect() 或? 時,會將 Option 裏的引用 Copy 一份出來賦值給 remainder,然後在這個新的 remainder 上作可變引用,而 self.remainder 沒有任何變化。
我們可以使用 Option 的 as_mut() 方法,因爲它返回的是 Option<&mut T>:
1fn next(&mut self) -> Option<Self::Item> {
2 // 爲什麼這麼寫不可以???
3 // let remainder = &mut self.remainder?;
4
5 let remainder = self.remainder.as_mut()?;
6 ......
7 }
得到了一個 self.remainder 的可變引用,因此測試通過。
在下一篇文章中,我們通過更多的例子來繼續學習 Rust 的生命週期。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/T9p9sTobjKJb8WA7O4A1Qw