切片和切片引用
关于 str
/ &str
,[u8]
/ &[u8]
区别,你能清晰的说出来嘛?如果答案是 No ,那就跟随我一起来看看切片和切片引用到底有何区别吧。
在继续之前,查看这里了解何为切片
切片允许我们引用集合中部分连续的元素序列,而不是引用整个集合。例如,字符串切片就是一个子字符串,数组切片就是一个子数组。
无法被直接使用的切片类型
Rust 语言特性内置的 str
和 [u8]
类型都是切片,前者是字符串切片,后者是数组切片,下面我们来尝试下使用 str
:
#![allow(unused)] fn main() { let string: str = "banana"; }
上面代码创建一个 str
类型的字符串,看起来很正常,但是编译就会报错:
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:4:9
|
4 | let string: str = "banana";
| ^^^^^^ doesn't have a size known at compile-time
编译器准确的告诉了我们原因:str
字符串切片它是 DST
动态大小类型,这意味着编译器无法在编译期知道 str
类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
也就是说,我们无法直接使用 str
,而对于 [u8]
也是类似的,大家可以自己动手试试。
总之,我们可以总结出一个结论:在 Rust 中,所有的切片都是动态大小类型,它们都无法直接被使用。
为何切片是动态大小类型
原因在于底层的切片长度是可以动态变化的,而编译器无法在编译期得知它的具体的长度,因此该类型无法被分配在栈上,只能分配在堆上。
为何切片只能通过引用来使用
既然切片只能分配到堆上,我们就无法直接使用它,大家可以想想,所有分配在堆上的数据,是不是都是通过一个在栈上的引用来访问的?切片也不例外。
为何切片引用可以存储在栈上
切片引用是一个宽指针,存储在栈上,指向了堆上的切片数据,该引用包含了切片的起始位置和长度,而且最重要的是,类似于指针,引用的大小是固定的(起始位置和长度都是整形),因此它才可以存储在栈上。
有没有可以存储在栈上的
有,使用固定长度的数组: let a: [i8;4] = [1,2,3,4];
,注意看,数组的类型与切片是不同的,前者的类型带有长度:[i8;4]
,而后者仅仅是 [i8]
。
切片引用
那么问题来了,该如何使用切片呢?
何以解忧,唯有引用。由于引用类型的大小在编译期是已知的,因此在 Rust 中,如果要使用切片,就必须要使用它的引用。
str
切片的引用类型是 &str
,而 [i32]
的引用类型是 &[i32]
,相信聪明的读者已经看出来了,&str
和 &[i32]
都是我们非常常用的类型,例如:
#![allow(unused)] fn main() { let s1: &str = "banana"; let s2: &str = &String::from("banana"); let arr = [1, 2, 3, 4, 5]; let s3: &[i32] = &arr[1..3]; }
这段代码就可以正常通过,原因在于这些切片引用的大小在编译器都是已知的。
总结
我们常常说使用切片,实际上我们在用的是切片的引用,我们也在频繁说使用字符串,实际上我们在使用的也是字符串切片的引用。
总之,切片在 Rust 中是动态大小类型 DST,是无法被我们直接使用的,而我们在使用的都是切片的引用。
切片 | 切片引用 |
---|---|
str 字符串切片 | &str 字符串切片的引用 |
[u8] 数组切片 | &[u8] 数组切片的引用 |
但是出于方便,我们往往不会说使用切片引用,而是直接说使用字符串切片或数组切片,实际上,这时指代的都是切片的引用!