rust语言基础学习: rust中的slice类型
2020-07-25
今天来学习Rust中的slice类型。
为什么需要slice类型 #
为什么Rust会提供slice类型呢?
Rust中的借用(Borrow语义)可以将一个值在其所有权不发生转移的情况下,借给其他变量使用,借用通过创建引用来实现,Rust中创建引用的行为被称为借用。
但对于常用的集合类型例如Vec<T>
, String
(String的底层是Vec<u8>
),这些集合类型的引用类型&Vec<T>
, &String
,引用的将是整个集合的内容。
在一些特定的场景中,我们需要只引用集合中一段连续的元素序列,而不是引用整个集合的内容。 因此Rust提供了一类slice类型,使用slice类型可以只引用集合中一部分连续的元素。。
我们知道引用类型是没有所有权的类型,slice作为"部分引用"也是没有所有权的类型,所以slice通常以"借用"的形式存在。
rust中slice的语法是使用[start_index..end_index]
指定的范围创建一个slice,包含start_index处的元素,而不包含end_index处的元素,rust中切片slice划得的元素个数是end_index - start_index
(在这点上与go语言中的slice十分相似)。
例如有一个集合变量x
,则创建一个基于x的slice的语法是&x[start_index..end_index]
,注意创建的slice是以"借用"形式存在,所以在x前面加了引用操作符&
。
字符串slice: str #
str
是rust核心中内置的字符串slice,它通常以借用&str
的形式存在。
&str
是String
中一部分值的引用。
例1:
1fn main() {
2 let s = String::from("hello world");
3 let hello: &str = &s[..5]; // hello变量的类型可以自动推断, 这里显式给出
4 let mid = &s[4..7];
5 let world = &s[6..];
6 println!("{}", hello); // "hello"
7 println!("{}", mid); // "o w"
8 println!("{}", world); // "world"
9}
需要注意字符串slice指定范围边界的索引必须是有效的UTF-8字符串边界,如果从一个多字节字符的中间位置创建字符串slice,程序将会panic(在这点上与go语言对string类型进行切片操作是不同的,在go中不会panic)。
例2:
1fn main() {
2 let s = String::from("你好 世界");
3 let hello = &s[..1]; // thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '你' (bytes 0..3) of `你好 世界`'
4 println!("{}", hello);
5}
Rust中字符串字面量实际上就是字符串slice &str。
例3:
1let hello :&str = "hello";
上面代码中hello变量的类型是&str,它是一个指向二进制程序特定位置的字符串(程序加载到内的代码区一般在.rodata中的字符串),&str是一个不可变引用,字符串字面值是不可变的。
可以从一个slice创建另一个新的slice.
例4:
1fn main() {
2 let s = String::from("hello world");
3 let hello: &str = &s[..5];
4 let hel1: &str = &hello[..3];
5 let hel2: &str = &s[..3];
6 assert_eq!(hel1, hel2);
7}
更通用的slice类型 #
上面我们学习的字符串slice str
是专门针对字符串的slice类型。Rust中还提供了更加通用的slice类型。
通用的slice类型表示为[T]
,一般以"借用"&[T]
的形式存在。
我们可以从一个数组创建slice,只引用数组的一部分;还可以从Vec<T>
创建slice,引用Vec集合的一部分元素。
例5:
1fn main() {
2 let arr: [i32; 3] = [1, 2, 3]; // arr是数组类型[i32; 3], 类型可自动推导, 这里显式给出
3 let vec: Vec<i32> = vec![2, 3, 4, 5]; // vec变量是Vec<i32>类型, 类型可自动推导, 这里显式给出
4 let s1: &[i32] = &arr[1..3]; // s1是类型为`&[i32]`的slice, 类型可自动推导, 这里显式给出
5 let s2: &[i32] = &vec[0..2]; // s2是类型为`&[i32]`的slice, 类型可自动推导, 这里显式给出
6 assert_eq!(s1, s2);
7 println!("{:?}, {:?}", s1, s2); // [2, 3], [2, 3]
8}
slice类型和Deref trait #
之前我们学习过Deref
和DerefMut
两个trait。
- Deref用于不可变引用的解引用操作,如
*v
。 实现Deref trait允许我们重载不可变引用的解引用运算符*。实现了Deref trait的智能指针可以被当做常规引用来对待。 - DerefMut用于可变引用的解引用操作,如
*v = 1;
。 实现DerefMut trait允许我们重载可变应用的解引用运算符*。实现了DerefMut trait的智能指针可以被当做常规可变引用来对待。
Rust中的String
实现了Deref和DerefMut,String实现Deref中的关联类型Target是str
。因此,如果对String类型进行解引用操作的话,就得到了引用整个String的str
。
例6:
1fn main() {
2 let word = String::from("hello!");
3 let s: &str = &*word;
4 println!("{}", s); // hello!
5
6 // 打印word变量的地址
7 println!("{:p}", &word); // 0x7ffee56ebb98
8 // 打印word变量拥有值的堆内存地址
9 println!("{:p}", s); // 0x7f7f14c05d10
10}
Rust中的Vec<T>
实现了Deref和DerefMut,Vec<T>
实现Deref中的关联类型Target是[T]
。因此,如果对Vec<T>
类型进行解引用操作的话,就得到了引用整个String的[T]
。
例7:
1fn main() {
2 let v = vec![1, 2, 3];
3 let s: &[i32] = &*v;
4 println!("{:?}", s); // [1, 2, 3]
5
6 // 打印v变量的地址
7 println!("{:p}", &v); // 0x7ffee9ce4b98
8 // 打印v变量拥有值的堆内存地址
9 println!("{:p}, {:p}", s, &v[0]); // 0x7f87b1d05c30, 0x7f87b1d05c30
10}
有了本节对slice类型的学习,再来复习一下之前学过的"传参时Deref强制转换",实现Deref的类型的引用,会自动隐式强制转换为通过Deref所能转换的类型。
例8:
1fn main() {
2 let arr = [1, 2, 3];
3 let vec = vec![1, 2, 3];
4
5 foo(&vec); // Deref强制转换
6 foo(&arr);
7
8 foo(&vec[..]);
9 foo(&vec[..]);
10
11 foo(&*vec);
12}
13
14fn foo(s: &[i32]) {
15 println!("{:?}", s);
16}
例8中在设计foo函数签名时,设计参数为slice类型&[i32]
,能够同时适用于&Vec<i32>
和&[i32]
,比将foo函数的参数设计成&Vec<i32>
更加灵活。