电子说
本系列文章是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,测试通过。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !