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}

在第 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