Slice 类型

介绍

slice允许你引用集合中一段连续的元素序列,注意它是一种引用,它没有用所有权。

现在我们用一个题目示例来展示它的功能。
如题:我们要输入一个字符串,找到第一个空格,并输出它的位置,即第一个单词的位置结尾
假设,我们不适用slice直接来解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
// 由于不需要所有权因此用s的引用
fn first_word(s: &String) -> usize {

// 将字符串转为byte集合
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}

结果显然是对的,但是如果我们后续将 s 改变,这个返回的下标数并不会跟随改变,如果强行使用,有可能会出现越界等不安全问题。

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello world");

let word = first_word(s);

s.clear(); // 清空字符串

// 此时就会出现上述的不安全问题
}

再次之后我们编写,第查找第二个单词结尾位时,就会接连着出错。为此,Rust 为我们提供了slice

字符串slice

字符串sliceString的部分值引用。

1
2
3
4
let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

内存图

这个slice的用法有点像 python 的切片,同样可以省略开头与结尾

1
2
3
4
5
6
7
8
9
10
11
let s = String::from("hello");

let slice = &s[0..3];
let slice = &s[..3]; // 效果一样

let len = s.size();
let slice = &s[3..len];
let slice = &s[3..]; // 效果一样

let slice = &s[0..len];
let slice = &s[..]; // 效果一样

根据这个特性,我们可以重写first_word方法,string slice被记作&str

1
2
3
4
5
6
7
8
9
10
11
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

回到我们哪个 bug

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut s = String::from("hello world");

let word = first_word(s);

s.clear(); // 清空字符串,这里会报错,rust的编译器提前发现了bug

println!("the first word {word}");

}

这个报错的原因与所有权有关,slice拿到了String的引用,clear()方法会拿到String的可变引用,将其清空,但是根据上一篇文章讲到的,可变引用和不可变引用时相斥的,因此其他不可变引用应当无效,但是实际上我们在后面使用了这个不可变引用,从而引发了问题。

因此,程序变得跟安全,接下来就可以写获得第二个甚至第三个等等的函数了。

字符串的本质

”字符串字面值就是slice“。

1
let s = "hello world!";

这里s的类型是&str:它是一个指向二进制程序特定位置的slice,这也就是为什么说String不可变的原因:&str是不可变引用。

因此我们可以对程序继续优化

1
fn first_word(s: &str) -> &str {}

更改成这样的签名之后,这个函数就既可以输入&String类型,也可以输入&str类型的参数。

其他类型的slice

效果上大差不差

1
2
3
4
5
let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];

assert_eq!(slice, &[2, 3]);

总结

Rust 的 slice 设计与现代的其他语言很相似,用法上大差不差,但是在使用的时候会更加注重所有权规则,对于我而言,这个引用的规则有点像我们后端遇到的读写锁,比如 Mysql 的 x 锁和 s 锁。读读不排斥,写写排斥,读写也排斥。