在 Rust 中,切片(slice)是一种基本类型和序列类型。在 Rust 官方文档中,切片被定义为“对连续序列的动态大小视图”。
但在rust的Github 源码中切片被定义如下:
切片是对一块内存的视图,表示为指针和长度。
其实这个定义更有帮助。从这里的定义可以知道,切片是一个“宽指针”(fat pointer)。所以基本上,当创建一个数组的切片时,切片包含以下内容:
在 Rust 中,切片可以是对支持的数组的视图,也可以是对其他序列(例如向量或字符串)的视图。如果切片是对字符串的视图,它被称为字符串切片或字符串字面量,并且通常以其借用形式 &str
出现。
以下是一个示例数组和该数组生成的两个切片:
左边和右边展示了对中间显示的数组提供视图的两个切片。数组和切片的定义如下:
let array: [i32; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let slice1 = &array[5..10];
let slice2 = &array[3..7];
从上图可以看到,在 slice1
中,切片的指针指向数组的索引 5。slice1
的长度为 5。这意味着切片将包含数组中的 5 个元素。下面是切片相关的索引和值。切片本身的索引从 0 到 4。
右侧是 slice2
。该切片的指针指向元素 3,且切片的长度为 4。
定义一个数组,然后对数组进行切片操作:
let array: [i32; 7] = [0, 1, 2, 3, 4, 5, 6];
let slice = &array[..]; // [ 0, 1, 2, 3, 4, 5, 6 ]
let slice = &array[0..3]; // [ 0, 1, 2 ]
let slice = &array[..3]; // [ 0, 1, 2 ]
let slice = &array[2..4]; // [ 2, 3 ]
let slice = &array[2..]; // [ 2, 3, 4, 5, 6 ]
上面定义了不可变数组以及创建数组切片的几种方法。在切片定义后的注释中展示了 dbg!(slice); 的输出结果。
之后再创建一个可变的切片:
let mut array: [i32; 7] = [0, 1, 2, 3, 4, 5, 6];
let array_slice = &mut array[0..5]; // [ 0, 1, 2, 3, 4 ]
检查切片的长度并迭代索引/值:
slice.len(); // 5
for (index, item) in slice.iter().enumerate() {
println!("index: {:?} element {:?}", index, item);
}
/*
index: 0 element 0
index: 1 element 1
index: 2 element 2
index: 3 element 3
index: 4 element 4
*/
从切片中检索一个值:
slice[1]; // 1
切片的长度在编译时并不总是已知的。如果访问超出边界的索引值,编译器将不会保存:
slice[100];
会报如下错误:
thread ‘main’ panicked at ‘index out of bounds: the len is 5 but the index is 100’
为了安全地从切片中获取值,可以使用 get() 方法:
slice.get(2); // Some(2)
slice.get(100); // None
在切片中查找值:
slice.iter().position(|v| v == &120); // None
slice.iter().position(|v| v == &4); // Some(4)
改变切片中元素的值:
slice[0] = 100;
dbg!(slice); // [100, 1, 2, 3, 4]
dbg!(array); // [100, 1, 2, 3, 4, 5, 6]
可以从数组、向量和字符串中获取切片:
let array: [i32; 4] = [0, 1, 2, 3];
let array_slice = &array[..2]; // [0, 1]
let vector = vec![1, 2, 3, 4];
let vector_slice = &vector[0..2]; // [1, 2]
let string = String::from("string slice");
let string_slice = &string[0..6]; // "string"
println!("{:?} {:?} {:?}", array_slice, vector_slice, string_slice);
// [0, 1] [1, 2] "string"
之前定义的数组和向量包含 i32 类型,之后可以创建一个同时适用于 vector_slice
和 array_slice
的函数:
fn return_second(n: &[i32]) {
println!("{}", n[1]);
}
return_second(array_slice); // 1
return_second(vector_slice); // 2
字符串切片是一个 &str,因此不能将其传递给 return_second 函数。事实上,字符串切片有点特殊。在 Rust 中,所有的字符串都是 UTF-8 编码的,因此字符的大小可以不同。iter() 方法不能用在字符串切片上,相反,需要使用 chars() 方法。要从切片中取第 n 个字符,则需要使用索引 n。
let string = String::from("Rust is 😍");
let string_slice = &string[..];
fn return_second_char(n: &str) {
println!("{:?}", n.chars().nth(1));
}
return_second_char(string_slice); // Some('u')
for c in string_slice.chars() {
println!("{}", c)
}
/*
R
u
s
t
i
s
😍
*/
for (i, c) in string_slice.chars().enumerate() {
println!("{} {}", i, c)
}
/*
0 R
1 u
2 s
3 t
4
5 i
6 s
7
8 😍
*/
Rust中的宽指针(fat pointers)与窄指针(thin pointers)是指针类型的两种概念。
窄指针(Thin Pointers):指针仅包含目标内存地址信息,不包含其他附加信息。比如,裸指针 *const T
和 *mut T
就是窄指针,它们只存储指向某个类型 T
的内存地址。
宽指针(Fat Pointers):指针除了存储目标内存地址外,还包含其他信息,例如动态数组的长度。切片 &[T]
或者动态 trait 对象 &dyn Trait
就是宽指针的例子,它们除了指向内存的地址外,还存储着长度等其他信息。
宽指针包含更多的信息,但也会带来一些额外的存储开销。窄指针更加轻量,但缺乏一些额外的信息。在Rust中,切片是一种宽指针,因为它包含指向数据的指针和数据长度。
use std::mem;
let array: [i32; 500] = [0; 500];
let slice = &array[..];
let array_pointer = &array;
let slice_pointer = &slice;
let start_of_array_slice = &array[0];
println!("--------------------------------------------");
println!("array_pointer address: {:p}", array_pointer);
println!("slice_pointer address: {:p}", slice_pointer);
println!("start_of_array_slice address: {:p}", start_of_array_slice);
println!("slice occupies {} bytes", mem::size_of_val(&slice));
println!(
"array_pointer occupies {} bytes",
mem::size_of_val(&array_pointer)
);
println!("array occupies {} bytes", mem::size_of_val(&array));
println!("--------------------------------------------");
--------------------------------------------
array_pointer address: 0x9def68
slice_pointer address: 0x9df738
start_of_array_slice address: 0x9def68
slice occupies 16 bytes
array_pointer occupies 8 bytes
array occupies 2000 bytes
--------------------------------------------
数组的总大小为 2000 字节。整个数组的切片(宽指针)占据 16 字节。如果获取数组的指针,得到的是一个占据 8 字节的窄指针。数组指针和切片起始地址的内存地址是相同的。