Slice 切片
本质是引用
- 不持有所有权的一种数据类型
- 指向字符串/数组等连续内存类型中一部分数据的引用
切片语法
| 语法 | 数学表示 | 包含终点吗? | 例子 (1..3) |
|---|---|---|---|
start..end | [start, end) | 不包含 | 1, 2 |
start..=end | [start, end] | 包含 | 1, 2, 3 |
rust
let s = String::from("hello world");
let hello = &s[..5]; // [..5] = [0..5]
let world = &s[6..]; // [6..] = [6..s.len()]
let hello_world = &s[..]; // [..] = [0..s.len()]胖指针(Fat Pointer)
切片在内存中被表示为胖指针,由两部分组成:
- 指针: 指向数据起始元素的内存地址
- 长度: 记录切片包含的元素个数
这使得切片高效且轻量,只是对现有数据的"窗口"视图,无需复制底层数据.
只有指针就是普通指针(瘦指针),指针 + 长度才是胖指针,切片类型总是胖指针
可切片的数据类型
只要数据在内存中连续存储,就可以创建切片:
- 数组:
[i32; 5]→ 切片类型&[i32] - 向量:
Vec<T>→ 切片类型&[T] - 字符串:
String或&str→ 切片类型&str
&str 与切片的关系
&str 本质上是字符串切片:
- 定义: 对
String或字符串字面量中UTF-8字节序列的引用 - 推荐用法: 函数参数优先使用
&str而非&String,因为&str能接收&String、字符串字面量和本地切片
rust
fn print_message(msg: &str) {
println!("{}", msg);
}
let s = String::from("hello");
print_message(&s); // &String 自动强制转换为 &str
print_message("world"); // 字符串字面量直接作为 &str为什么切片只能通过引用
切片类型([T] 和 str)是 DST(Dynamic Sized Type):
- 编译时大小未知,无法直接存储在栈上
- 只能通过引用使用,使其成为固定大小的胖指针
&[T]和&str总是固定 16 字节(指针 8 字节 + 长度 8 字节)
rust
// ✗ 错误: DST 无法直接存储
let slice: [i32] = [1, 2, 3][..];
// ✓ 正确: 通过引用使用
let slice: &[i32] = &[1, 2, 3][..];UTF-8 字节边界
对字符串切片时,索引必须落在合法的 UTF-8 字符边界上,否则会发生 panic:
rust
let s = "你好world";
// ✗ 错误: 在多字节字符中间切片
// let word = &s[0..2];
// ✓ 正确: 在字符边界处切片
let word = &s[0..3]; // "你" 是 3 字节
// ✓ 推荐: 使用迭代器避免索引错误
let chars: String = s.chars().take(1).collect();本质是借用
- 切片是一种借用行为,不获取所有权
- 对应所有权规则中的"引用"
- 受借用规则约束(同作用域同变量只能有一个可变引用或多个不可变引用)