Skip to content

Rust 切片(Slice)完全理解指南

一次搞懂 &str&[T]、生命周期和真实内存模型

如果说 Ownership(所有权) 是 Rust 的地基, 那 Slice(切片) 就是你每天都会踩在上面的“地板”。

很多新手在这里卡住的原因只有一个:

切片不是数据,而是“对数据的一段引用”

这篇文章只做一件事: 👉 让你从“内存视角”真正理解切片是什么


一、切片是什么?一句话版本

切片 = 对一段“连续内存”的只读引用视图

它有 4 个核心特性:

  • ❌ 不拥有数据
  • ❌ 不复制数据
  • ✅ 指向原始数据的一部分
  • ✅ 生命周期受原数据限制

Rust 中最常见的两种切片:

原始数据切片类型
String / 字符串字面量&str
数组 / Vec<T>&[T]

二、从一个最经典的例子开始

rust
let s = String::from("hello rust");
let part = &s[..5];

这里发生了什么?

内存视角:

text
堆上的 String 数据:
┌─────────────────┐
│ h e l l o   r u │
└─────────────────┘

  s 拥有整块内存

part = &s[..5]
└── 指向 [0..5) 这段内存

重点:

  • part 只是一个“窗口”
  • 数据仍然属于 s
  • s 一旦被释放,part 立刻失效

三、为什么 &String 能当 &str 用?

rust
fn print_str(s: &str) {
    println!("{}", s);
}

let s = String::from("hello");
print_str(&s);

这背后是 Rust 的一个重要机制:

👉 Deref Coercion(自动解引用转换)

text
&String  →  &str

等价于:

rust
print_str(s.as_str());

设计哲学:

函数参数优先使用 &str,而不是 &String

因为:

  • &str 可以接收:String、字符串字面量、子切片
  • 更通用、更灵活

四、切片可以“再切片”

rust
fn first_word(s: &str) -> &str {
    &s[..5]
}

这里的逻辑是:

  • s 是一个切片
  • &s[..5] 是对切片再切片
  • 返回值仍然是 &str

生命周期本质(编译器自动补全):

rust
fn first_word<'a>(s: &'a str) -> &'a str

👉 返回值生命周期 ≤ 参数生命周期


五、字符串字面量为什么天然安全?

rust
let s = "hello rust";

它的真实类型是:

rust
&'static str

含义:

  • 数据存放在程序只读区
  • 程序运行期间永远有效

所以:

rust
fn print(s: &str) {}

print("hello");

永远安全


六、数组切片:一模一样的模型

rust
let arr = [1, 2, 3, 4, 5];
let part = &arr[1..3];

结果:

rust
part == &[2, 3]

类型是:

rust
&[i32]

字符串 vs 数组切片对照表

项目字符串数组
拥有者String[T] / Vec<T>
切片&str&[T]
内存UTF-8 字节连续元素
是否零拷贝

七、⚠️ 最容易踩坑的点:字符串切片是“按字节”的

rust
let s = String::from("你好 Rust");
let part = &s[..2]; // ❌ 直接 panic

原因:

  • Rust 字符串是 UTF-8
  • 中文字符占 3 个字节
  • 切片必须落在 字符边界

正确做法(按字符):

rust
let result: String = s.chars().take(2).collect();

👉 如果你不确定边界,宁可返回 String


八、什么时候该用切片?

✅ 适合用切片的场景

  • 只读访问
  • 函数参数
  • 高性能(零拷贝)
  • 临时视图

❌ 不适合用切片的场景

  • 需要长期保存
  • 需要修改
  • 需要脱离原数据生命周期

九、终极总结(背这个就够了)

切片不是数据,而是“借用的一段连续内存视图”

  • 没有所有权
  • 生命周期受限
  • 零拷贝
  • 是 Rust 性能与安全并存的关键设计

理解切片 = 真正开始写“地道 Rust”