CRust 學習筆記:生命週期 - 2

本系列文章是 Jon Gjengset 發佈的 CRust of Rust 系列視頻的學習筆記,CRust of Rust 是一系列持續更新的 Rust 中級教程。

讓我們接着上一篇文章繼續學習 Rust 的生命週期。在上一篇文章中的代碼基礎上,加入如下的函數和測試用例:

fn until_char(s&str, cchar) -> &str {
    StrSplit::new(s, &format!("{}", c))
        .next()
        .expect("StrSplit always gives at least one result")
}

#[test]
fn until_char_test() {
    assert_eq!(until_char("hello world", 'o'), "hell");
}

編譯器會報如下錯誤:

error[E0515]: cannot return value referencing temporary value

這裏的臨時值是 & format!("{}",c),從代碼中可以看出,參數 s、c 和 next() 之後的值要擁有相同的生命週期,因此返回值與臨時值的生命週期相同。但是這個臨時值的生命週期在函數執行完後就結束了,所以編譯不通過。

有一種解決辦法是使用 String

#[derive(Debug)]
pub struct StrSplit<'a> {
    // 使用Option
    remainderOption<&'a str>,
    delimiterString,
}

但是使用 String 是有兩個問題的,我們先來比較一下 str,&str,String 之間的區別:

String 轉換 & str 相對容易一些,因爲已知字符串的起始位置及長度。而 & str 轉換成 String 就複雜一些,需要先在堆上分配一段空間,然後再通過內存拷貝 (memcpy) 把字符 copy 到堆上。

因此使用 String 的第一個問題是性能問題;第二個問題是不能兼容嵌入式系統,大多數嵌入式系統沒有堆內存。

我們選擇更好的解決方案,定義多個生命週期

#[derive(Debug)]
pub struct StrSplit<'haystack, 'delimiter> {
    // 使用Option
    remainderOption<&'haystack str>,
    delimiter&'delimiter str,
}
impl<'haystack, 'delimiter> StrSplit<'haystack, 'delimiter> {
    /**
     * 新構建的StrSplit與傳入的參數haystack,delimiter 擁有相同的生命週期
     */
    pub fn new(haystack&'haystack str, delimiter&'delimiter str) -> Self {
        Self {
            remainderSome(haystack),
            delimiter,
        }
    }
}

impl<'haystack> Iterator for StrSplit<'haystack, '_> {
    // 迭代的結果也要與StrSplit擁有相同的生命週期,是因爲要在StrSplit的成員remainder上做迭代。
    type Item = &'haystack str;

    fn next(&mut self) -> Option<Self::Item> {
        let remainder = self.remainder.as_mut()?;
        if let Some(next_delim) = remainder.find(self.delimiter) {
            let until_remainder = &remainder[..next_delim];
            *remainder = &remainder[next_delim + self.delimiter.len()..];
            Some(until_remainder)
        } else {
            self.remainder.take()
        }
    }
}

執行 cargo test,測試通過。

泛型化 Delimiter

在這裏我們將分隔符進行泛型化,使得 StrSplit 更加通用。

pub struct StrSplit<'haystack, D> {
    // 使用Option
    remainderOption<&'haystack str>,
    delimiterD,
}

impl<'haystack, D> StrSplit<'haystack, D> {
    pub fn new(haystack&'haystack str, delimiterD) -> Self {
        Self {
            remainderSome(haystack),
            delimiter,
        }
    }
}

定義一個 trait,包含一個 find_next() 方法,用於返回分隔符在字符串中的起始位置和結束位置

pub trait Delimiter {
    // 返回分隔符在字符串中的起始位置和結束位置
    fn find_next(&self, s&str) -> Option<(usize, usize)>;
}

迭代器修改如下:

impl<'haystack, D> Iterator for StrSplit<'haystack, D> 
where 
    DDelimiter
{
    // 迭代的結果也要與StrSplit擁有相同的生命週期,是因爲要在StrSplit的成員remainder上做迭代。
    type Item = &'haystack str;

    fn next(&mut self) -> Option<Self::Item> {
        let remainder = self.remainder.as_mut()?;
        if let Some((delim_start, delim_end)) = self.delimiter.find_next(remainder) {
            let until_remainder = &remainder[..delim_start];
            *remainder = &remainder[delim_end..];
            Some(until_remainder)
        } else {
            self.remainder.take()
        }
    }
}

然後爲 & str 和 char 分別實現 Delimiter trait:

impl Delimiter for &str {
    fn find_next(&self, s&str) -> Option<(usize, usize)> {
        s.find(self).map(|start| (start, start + self.len()))
    }
}

impl Delimiter for char {
    fn find_next(&self, s&str) -> Option<(usize, usize)> {
        s.char_indices()
            .find(|(_, c)| c == self)
            .map(|(start, _)| (start, start + self.len_utf8()))
    }
}

函數 until_char() 修改爲:

pub fn until_char(s&str, cchar) -> &str {
    StrSplit::new(s, c)
        .next()
        .expect("StrSplit always gives at least one result")
}

執行 cargo test,測試通過。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Air9iZvHV3vhGFezMfLZSA