CRust学习笔记:生命周期-2

电子说

1.3w人已加入

描述

本系列文章是Jon Gjengset发布的CRust of Rust系列视频的学习笔记,CRust of Rust是一系列持续更新的Rust中级教程。

让我们接着上一篇文章继续学习Rust的生命周期。在上一篇文章中的代码基础上,加入如下的函数和测试用例:

 1fn until_char(s: &str, c: char) -> &str {
 2    StrSplit::new(s, &format!("{}", c))
 3        .next()
 4        .expect("StrSplit always gives at least one result")
 5}
 6
 7#[test]
 8fn until_char_test() {
 9    assert_eq!(until_char("hello world", 'o'), "hell");
10}
  编译器会报如下错误:
error[E0515]: cannot return value referencing temporary value
     

 

这里的临时值是&format!("{}",c),从代码中可以看出,参数s、c和next()之后的值要拥有相同的生命周期,因此返回值与临时值的生命周期相同。但是这个临时值的生命周期在函数执行完后就结束了,所以编译不通过。

有一种解决办法是使用String

1#[derive(Debug)]
2pub struct StrSplit<'a> {
3    // 使用Option
4    remainder: Option<&'a str>,
5    delimiter: String,
6}
  但是使用String是有两个问题的,我们先来比较一下str,&str,String之间的区别:

 

str -> [char]:相当于char类型的切片,既可以分配在栈上,也可以分配在堆上,还可以分配在static区。

&str -> &[char]:相当于胖指针,包含指向str的指针和字符串的长度。

String -> Vec分配在堆上的字符向量,在栈上有一个胖指针指向这个堆上的字符向量。

String转换&str相对容易一些,因为已知字符串的起始位置及长度。而&str转换成String就复杂一些,需要先在堆上分配一段空间,然后再通过内存拷贝(memcpy)把字符copy到堆上。

因此使用String的第一个问题是性能问题;第二个问题是不能兼容嵌入式系统,大多数嵌入式系统没有堆内存。

我们选择更好的解决方案,定义多个生命周期

1#[derive(Debug)]
2pub struct StrSplit<'haystack, 'delimiter> {
3    // 使用Option
4    remainder: Option<&'haystack str>,
5    delimiter: &'delimiter str,
6}
 1impl<'haystack, 'delimiter> StrSplit<'haystack, 'delimiter> {
 2    /**
 3     * 新构建的StrSplit与传入的参数haystack,delimiter 拥有相同的生命周期
 4     */
 5    pub fn new(haystack: &'haystack str, delimiter: &'delimiter str) -> Self {
 6        Self {
 7            remainder: Some(haystack),
 8            delimiter,
 9        }
10    }
11}
12
13impl<'haystack> Iterator for StrSplit<'haystack, '_> {
14    // 迭代的结果也要与StrSplit拥有相同的生命周期,是因为要在StrSplit的成员remainder上做迭代。
15    type Item = &'haystack str;
16
17    fn next(&mut self) -> Option {
18        let remainder = self.remainder.as_mut()?;
19        if let Some(next_delim) = remainder.find(self.delimiter) {
20            let until_remainder = &remainder[..next_delim];
21            *remainder = &remainder[next_delim + self.delimiter.len()..];
22            Some(until_remainder)
23        } else {
24            self.remainder.take()
25        }
26    }
27}
  执行cargo test,测试通过。     泛型化Delimiter 在这里我们将分隔符进行泛型化,使得StrSplit更加通用。
 1pub struct StrSplit<'haystack, D> {
 2    // 使用Option
 3    remainder: Option<&'haystack str>,
 4    delimiter: D,
 5}
 6
 7impl<'haystack, D> StrSplit<'haystack, D> {
 8    pub fn new(haystack: &'haystack str, delimiter: D) -> Self {
 9        Self {
10            remainder: Some(haystack),
11            delimiter,
12        }
13    }
14}
  定义一个trait,包含一个find_next()方法,用于返回分隔符在字符串中的起始位置和结束位置
1pub trait Delimiter {
2    // 返回分隔符在字符串中的起始位置和结束位置
3    fn find_next(&self, s: &str) -> Option<(usize, usize)>;
4}
    迭代器修改如下:
 1impl<'haystack, D> Iterator for StrSplit<'haystack, D> 
 2where 
 3    D: Delimiter
 4{
 5    // 迭代的结果也要与StrSplit拥有相同的生命周期,是因为要在StrSplit的成员remainder上做迭代。
 6    type Item = &'haystack str;
 7
 8    fn next(&mut self) -> Option {
 9        let remainder = self.remainder.as_mut()?;
10        if let Some((delim_start, delim_end)) = self.delimiter.find_next(remainder) {
11            let until_remainder = &remainder[..delim_start];
12            *remainder = &remainder[delim_end..];
13            Some(until_remainder)
14        } else {
15            self.remainder.take()
16        }
17    }
18}
    然后为&str和char分别实现Delimiter trait:
 1impl Delimiter for &str {
 2    fn find_next(&self, s: &str) -> Option<(usize, usize)> {
 3        s.find(self).map(|start| (start, start + self.len()))
 4    }
 5}
 6
 7impl Delimiter for char {
 8    fn find_next(&self, s: &str) -> Option<(usize, usize)> {
 9        s.char_indices()
10            .find(|(_, c)| c == self)
11            .map(|(start, _)| (start, start + self.len_utf8()))
12    }
13}
  函数until_char()修改为:
1pub fn until_char(s: &str, c: char) -> &str {
2    StrSplit::new(s, c)
3        .next()
4        .expect("StrSplit always gives at least one result")
5}
  执行cargo test,测试通过。    

 

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分