数组是一个容器,它在一块连续空间内存中,存储了一系列的同样类型的数据。
数组中元素的占用空间大小必须是编译期确定的。
数组本身所容纳的元素个数也必须是编译期确定的,执行阶段不可变。
如果需要使用变长的容器,可以使用标准库中的Vec/LinkedList等。数组类型的表示方式为[T;n]。
其中T代表元素类型;n代表元素个数;它必须是编译期常量整数;中间用分号隔开。
下面看一个基本的示例:
既然[T;n]是一个合法的类型,那么它的元素T当然也可以是数组类型,因此[[T;m];n]类型自然也是合法类型。示例如下:
对数组取借用borrow操作,可以生成一个“数组切片”(Slice)。
数组切片对数组没有“所有权”,我们可以把数组切片看作专门用于指向数组的指针,是对数组的另外一个“视图”。
比如,我们有一个数组[T;n],它的借用指针的类型就是&[T;n]。
它可以通过编译器内部魔法转换为数组切片类型&[T]。
数组切片实质上还是指针,它不过是在类型系统中丢弃了编译阶段定长数组类型的长度信息,而将此长度信息存储为运行期的值。示例如下:
从前面的示例中可以看到,数组切片是指向一个数组的指针,而它比指针又多了一点东西——它不止包含有一个指向数组的指针,切片本身还含带长度信息。
Slice与普通的指针是不同的,它有一个非常形象的名字:胖指针(fat pointer)。
与这个概念相对应的概念是“动态大小类型”(Dynamic Sized Type,DST)。
所谓的DST指的是编译阶段无法确定占用空间大小的类型。为了安全性,指向DST的指针一般是胖指针。
比如:对于不定长数组类型[T],有对应的胖指针&[T]类型;对于不定长字符串str类型,有对应的胖指针αstr类型;以及在后文中会出现的Trait Object;等等。
由于不定长数组类型[T]在编译阶段是无法判断该类型占用空间的大小的,目前我们不能在栈上声明一个不定长大小数组的变量实例,也不能用它作为函数的参数、返回值。
但是,指向不定长数组的胖指针的大小是确定的,&[T]类型可以用做变量实例、函数参数、返回值。
通过前面的示例我们可以看到,&[T]类型占用了两个指针大小的内存空间。
Rust中的Range代表一个“区间”,一个“范围”,它有内置的语法支持,就是两个小数点…。示例如下:
在前面的示例中,我们的“索引”都是一个合法的值,没有超过数组的长度。如果我们给“索引”一个非法的值会怎样呢:
编译通过,执行thread’main’panicked at index out of bounds:the len is 5 but the index is10’。
可以看出,如果用/test10,则会出现数组越界,Rust目前还无法任意索引执行编译阶段边界检查,但是在运行阶段执行了边界检查。下面我们分析一下边界检查背后的故事。
在Rust中,“索引”操作也是一个通用的运算符,是可以自行扩展的。
如果希望某个类型可以执行“索引”读操作,就需要该类型实现std::ops::Index trait,如果希望某个类型可以执行“索引”写操作,就需要该类型实现std::ops::IndexMut trait。
对于数组类型,如果使用usize作为索引类型执行读取操作,实际执行的是标准库中的以下代码:
字符串是非常重要的常见类型。相比其他很多语言,Rust的字符串显得有点复杂,主要是跟所有权有关。Rust的字符串涉及两种类型,一种是&str,另外一种是string。
str是Rust的内置类型。&str是对str的借用。Rust的字符串内部默认是使用utf-8编码格式的。而内置的char类型是4字节长度的,存储的内容是Unicode Scalar Value。
所以,Rust里面的字符串不能视为char类型的数组,而更接近u8类型的数组。
实际上str类型有一种方法:fn as_ptr(&self)->*const u8。
它内部无须做任何计算,只需做一个强制类型转换即可。
接下来讲string类型。它跟&str类型的主要区别是,它有管理内存空间的权力。
关于“所有权”和“借用”的关系,&str类型是对一块字符串区间的借用,它对所指向的内存空间没有所有权,哪怕&mut str也一样。比如:
let greeting:&str =“Hello”;
我们没办法扩大greeting所引用的范围,在它后面增加内容。但是string类型可以。示例如下: