首页 / Rust 入门教程 / 切片类型

Rust 入门教程

切片类型

本教程共 78 篇 · 第 24 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

RustRust 入门教程切片Slice数组切片引用视图

本节目标:认识切片(Slice)——一种不拥有数据、只”看向”集合中连续一段的引用类型,以数组切片 &[T] 为例,理解它作为”安全视图”的用途,以及为何函数参数优先用切片。

前面讲的引用 & 是指向”整个值”的。但有时你只想引用值的”一部分”——比如数组的前三个元素、字符串的前几个字符。Rust 用**切片(Slice)**表达”指向集合中连续一段”的引用。它本身不持有所有权,只是个安全的”窗口”。理解了切片,你才算真正摸到了 Rust 借用系统的精髓。

24-1 为什么需要切片

设想一个场景:你写了个函数,想找出数组里”最大的那个数”。如果你用数组当参数,就得写死数组长度 [i32; 5],那它只能处理恰好 5 个元素的数组,换成 6 个就编译不过。可你真正关心的逻辑——“比较、挑大的”——明明和长度无关。切片就是为这种”我不在乎整体多长,只想用其中一段”的需求而生的。它让你写一个函数,就能通吃”整个数组”和”数组的任意一段”。

24-2 什么是切片

切片让你借用集合的一段,而不是整个。类型写作 &[T](对于数组),其中 T 是元素类型。它内部其实存了两个信息:一个指向段起点的指针、以及这段的长度。所以切片知道自己有多长,访问越界会在运行时被拦下——这是它”安全”的来源。

Note

切片不是”复制了一份数据”,它只是记着”从哪开始、多长”。原数据一个字节都没动,所以切片的创建是零成本的(不拷贝、不分配内存)。它就像一个贴在玻璃上的取景框,框住你关心那一段,但玻璃后面的东西还是原来那些。

24-3 数组切片示例

fn main() {
    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..4]; // 取索引 1、2、3,不含 4
    println!("切片长度是 {}", slice.len()); // 3
    println!("第一个元素 {}", slice[0]);     // 2(注意从 0 重新编号)
}

&a[1..4] 创建了对 a 中第 1 到第 3 个元素(不含 4)的切片。切片里元素重新从 0 编号:slice[0] 是 2,不是原数组的 a[1] 之外的概念混淆——它就是”这段里的第 0 个”。原数组 a 的所有权没动,切片只是”借看”它的一部分。

24-4 区间语法是左闭右开

切片用区间语法 [起始..结束],和 for 里的区间一样是左闭右开:包含起始索引、但不包含结束索引。这是 Rust 一以贯之的约定,记牢”左边算、右边不算”,能少很多数数的纠结点。

let a = [0, 1, 2, 3, 4, 5];
let head = &a[..3];   // 从开头到 3(不含):[0, 1, 2]
let mid = &a[2..5];   // [2, 3, 4]
let tail = &a[4..];   // 从 4 到结尾:[4, 5]
let whole = &a[..];   // 整个数组当切片

..3 表示”从头到 3(不含)“,4.. 表示”从 4 到结尾”,.. 两端都省就是”整段”。这种省略写法极常用,看到省略的一端就理解为”取到边界”。

Tip

切片区间的左闭右开,和 for i in 0..n 完全一致。一旦习惯”左边算、右边不算”,区间和切片就再也不会搞混。

24-5 切片不持有所有权

切片是引用,没有所有权。原集合(这里是 a)才是所有者。切片失效时,原集合安然无恙。这正符合”借用”的精神:我让你看一段,但不把东西交给你。

这也意味着:只要切片还活着,编译器就保证原集合不会被释放或移动,避免出现”切片指向已消失的内存”。借用检查器管着这事。你不用手动去想”这段内存还有效吗”,编译器在编译期就替你保证了。

24-6 函数接收切片更通用

切片的一大用处:写函数时,如果不关心”整个数组还是一部分”,就用切片做参数,调用方传整个或传一段都行:

fn first_two(s: &[i32]) -> i32 {
    s[0] + s[1]
}

fn main() {
    let a = [10, 20, 30, 40];
    println!("{}", first_two(&a));        // 传整个数组(自动当切片)
    println!("{}", first_two(&a[2..4]));  // 传一段
}

&[i32] 参数既能接收整个数组(自动转切片),也能接收任意一段切片。函数因此更灵活,不用为”整个”和”部分”写两个版本。这是 Rust 里极其常见的写法。

Tip

经验:函数要处理”一串同类型元素”,参数优先用切片 &[T] 而非固定长度数组 [T; N]。前者接受任意长度,复用性强得多。看到函数签名里是 &[T],记住它”借看一段、不拥有”,调用方传 &数组名&数组名[起..止] 都行。

24-7 切片的长度与容量

注意区分:切片有自己的 len()(它看到的这段有多长),它和原集合的总长度可以不同。切片没有”容量”概念(容量是 Vec 的),它就是个定长的视图。切片类型 &[T] 本身不把长度写进类型里(和数组 [T; N] 不同),所以它能表示”任意长度的一段”——这正是它比固定数组灵活、常作函数参数的原因。

24-8 切片也能遍历

切片可以像数组一样用 for 遍历,因为它本质就是”一段元素”:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..4];
for x in slice {
    println!("{x}");
}

输出 2、3、4。遍历切片和遍历原数组写法完全一致,因为 for 要的就是”一个能迭代的东西”,切片天然满足。

24-9 一个返回切片的函数

看个返回”数组前半段”的小函数,体会切片”借看、不拥有”:

fn first_half(arr: &[i32]) -> &[i32] {
    &arr[..arr.len() / 2]
}

fn main() {
    let a = [1, 2, 3, 4, 5, 6];
    let h = first_half(&a);
    println!("{:?}", h); // [1, 2, 3]
}

&arr[..n] 是”从开头到 n(不含)“的切片语法。{:?} 是切片的调试打印格式。注意返回类型也是 &[i32]——切片本身不拥有数据,只是借看原数组的一段。只要 h 还活着,编译器保证 a 不会被释放或移动,避免悬垂。

24-10 字符串切片是特例

切片不止用于数组。最常用的是字符串切片 &str——它指向字符串里的一段字符。因为字符串处理太常见,下一章单独讲 &strString 的所有权关系。这里你只要知道:字符串切片是切片家族的一员,原则和数组切片一模一样——不持有所有权、只借看一段。

Warning

对字符串切片要特别注意 UTF-8:索引是按字节算的。一个汉字占 3 字节,所以 &s[0..1] 截半个汉字会直接运行时 panic。下一章会展开这个坑,这里先留个印象就行。

24-11 你可能会踩的坑

  • 坑一:越界访问。切片的 slice[i] 超出它的长度会运行时 panic。好消息是切片存了自身长度,越界会被拦下,不会像某些语言那样”读到非法内存”。
  • 坑二:搞错左闭右开,写 &a[0..3] 以为包含索引 3,结果少取了一个元素。牢记”右边不算”。
  • 坑三:切片和原集合的生命周期混为一谈,以为切片能脱离原集合独立存在——不能,原集合释放了切片就成了悬垂,编译器会拦。
Note

切片和引用是同一套借用规则下的两兄弟:引用指向”整个值”,切片指向”值的一段”。它们都不拥有数据,都受借用检查器保护。理解了引用,切片只是”加了个长度信息的引用”,一下子就通了。

24-12 切片在真实代码里的位置

切片不是教科书概念,它天天出现在真实代码里。字符串处理几乎都走 &str 切片:函数接收文本参数用 &str(能同时接字面量和 String 借用),解析配置、处理日志、比对前缀后缀,底层都是切片。集合算法里,把”对整段数组做的事”改成”对切片做”,函数立刻能处理任意一段子序列,而不必拷贝。比如一个”求最大值”的函数,参数若写成 &[i32],你既能传整个数组、也能传数组的某一段,通用性大增。

Note

切片让函数签名更”多态”:同样的 &[T],背后可能是固定数组 &[T; N]、也可能是 Vec<T> 切片出来的 &[T]——它们都”长得像一段连续的 T”。这是 Rust 用”借用 + 视图”替代”到处拷贝”的典型思路:不拿走数据,只借一瞥。

还有个细节:切片本身只是一个”开始位置 + 长度”的胖指针,打印它看到的是内容(如 [2, 3, 4]),但它并不拥有那些元素。元素的所有权仍在原集合手里,原集合走完生命周期,切片也就随之失效——这正是借用规则在切片上的体现。

24-13 小结

切片 &[T] 是引用集合中连续一段的”视图”,不持有所有权、零复制;区间 [a..b] 左闭右开;函数用切片参数最通用,能同时接收整个数组和任意一段。字符串切片 &str 是它最常见的形态。下一章我们就钻进字符串切片与 String 的所有权纠葛,这是日常编码天天要面对的选择。