Rust slice(切片)
slice(切片)让你借用数组或 vector 的一段,相当于看向其中一部分的“视图”,既不复制数据,也不拿走所有权。如果你已经对单个值用过借用,那么 slice 就是把同样的思路用在一段连续的元素上。
从数组里取 slice
Section titled “从数组里取 slice”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // 索引为 1、2、3 的元素 println!("{:?}", middle);}输出:
[20, 30, 40]1..4 就是你在 for 循环里见过的那种范围:从索引 1 开始,在索引 4 之前停下,所以取到的是索引 1、2 和 3。
下面几种简写经常会用到:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // 从开头到索引 3 之前 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // 从索引 2 到末尾 -> [30, 40, 50] println!("{:?}", &numbers[..]); // 全部 -> [10, 20, 30, 40, 50]}字符串 slice
Section titled “字符串 slice”其实你早就在用 slice 了:&str 就是 String(或另一个字符串)的一个 slice!这也是这两种类型配合得这么好的原因:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}这里的句子特意保留了英文:字符串 slice 的范围按字节计算,而一个汉字在 UTF-8 里占 3 个字节。换成中文的话,0..3 只能取到第一个汉字,如果范围切在某个汉字的中间,程序还会 panic。
为什么用 slice,而不是复制?
Section titled “为什么用 slice,而不是复制?”你也许会想,把需要的那一段复制成一个新的 Vec 或 String 不就行了?slice 完全不需要这样做:它只是指向现有数据的一部分,就像引用(&)指向单个值一样。这意味着没有额外的内存分配,没有复制,而且你在借用那一课学到的规则照样适用。
接收 slice 的函数
Section titled “接收 slice 的函数”slice 特别适合用作函数参数,因为数组和 vector 都能传进去:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] 既能接收来自数组的 slice,也能接收来自 Vec 的 slice:一个函数,两种调用方式都能用。
通过可变 slice 修改数据
Section titled “通过可变 slice 修改数据”到目前为止,我们的 slice 都只是读取数据。可变 slice(&mut [T])可以让函数修改传给它的元素,但也只能修改这些元素:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // 只有索引 1 和 2 println!("{:?}", scores);
double_all(&mut scores); // 整个 vector println!("{:?}", scores);}输出:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]第一次调用传入的是 &mut scores[1..3],所以 double_all 只能看到索引 1 和 2。这个范围之外的元素,它碰都碰不到。在函数内部,slice 的索引又从 0 开始:对 double_all 来说,它拿到的只是一个有两个数字的列表。
这样做之所以安全,靠的是你会在借用那一课正式学到的一条规则:函数持有 &mut slice 的时候,别的代码都不能使用这些元素,所以代码里永远不会有两个地方同时修改它们。另外,vector 本身也必须声明为 mut,否则 Rust 不允许你可变地借用它。
1. 一分为二
Section titled “1. 一分为二”创建 vector vec![3, 6, 9, 12, 15, 18]。把它的前一半和后一半作为两个 slice 分别打印出来。提示:先算出 let mid = numbers.len() / 2;,再用 ..mid 和 mid..。
查看答案
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("前一半:{:?}", first_half); println!("后一半:{:?}", second_half);}输出:
前一半:[3, 6, 9]后一半:[12, 15, 18]因为 mid 是根据 len() 算出来的,所以增加或删除数字之后,这段代码照样能用。如果长度是奇数,多出来的那个元素会被分到后一半。
2. 任意列表里的最大值
Section titled “2. 任意列表里的最大值”写一个函数 largest(numbers: &[i32]) -> i32,返回其中最大的数字。分别用一个数组、一个 vector,以及只包含 vector 第一个元素的 slice 来调用它。提示:从 numbers[0] 开始,然后遍历其余的元素,就像处理数组时那样。
查看答案
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}输出:
93510遍历 slice 时,你拿到的是每个元素的引用(&i32),而不是数字本身。*n 的意思是“n 指向的那个值”,所以 *n > biggest 比较的是两个普通的数字。这个版本遇到空 slice 时会 panic,因为 numbers[0] 不存在。以后你会学到,这种情况可以返回一个 Option。
- slice(
&[T])借用数组或 vector 的一段,而不复制它。 1..4选中索引 1 到 3;省略..的某一边,就表示从开头开始或一直到末尾。&str本身就是一段文本的 slice,所以字符串和 slice 有那么多共同的行为。- 想让函数同时接收数组和 vector 时,参数类型优先用
&[T]。 - 可变 slice(
&mut [T])让函数只修改传给它的那些元素。
小测验
1. slice 能让你对数组或 vector 的一部分做什么?
2. &numbers[1..4] 包含哪些索引?
3. 为什么 &[i32] 很适合用作函数的参数类型?
得分: 0 / 3
- Slice 类型 —《Rust 程序设计语言》:用“获取字符串的第一个单词”这个例子,一步步引出 slice。
- 数组和切片 — 通过例子学 Rust:把数组借用为 slice 的可运行示例。
slice— 标准库文档(英文):slice 的众多方法,比如first、split_at、contains和sort。- Slice types — The Rust Reference(英文):精确的定义,适合想弄清
[T]和&[T]到底是什么意思的你。