Slice 类型 总结

切片(slice) 是 Rust 提供的一种特殊引用,它让你可以安全地引用集合(如字符串、数组)中的一段连续元素,而不需要拥有这些数据的所有权。slice 的引入,漂亮地解决了“用单独的索引表示子序列”带来的数据不同步问题。


1. 没有 slice 的痛点:索引与数据脱钩

如果写一个 first_word 函数,返回字符串中第一个单词结尾的索引

fn first_word(s: &String) -> usize {
    // 查找空格,返回索引,或整个长度
}

你会得到一个 usize,但这个数字和原字符串没有任何绑定关系。之后原字符串被清空或修改,索引依然存在,使用它就会产生逻辑错误,而编译器却无法察觉:

let mut s = String::from("hello world");
let word = first_word(&s); // 5
s.clear();                 // s 变为 ""
// word 仍然是 5,但已毫无意义 —— 编译器不报错!

这种“飘忽不定的索引”极易滋生 bug。


2. 字符串 slice:&str

字符串 slice 是 String 中一部分的引用,写法是 &s[开始..结束]

let s = String::from("hello world");
let hello = &s[0..5];   // "hello"
let world = &s[6..11];  // "world"
  • slice 内部存的是指向原数据起始位置的指针长度,并不拥有数据。
  • 类型写作 &str(读作 “string slice”)。

省略写法:

  • &s[0..2] 可简写为 &s[..2] (从头开始)
  • &s[3..len] 简写为 &s[3..] (到末尾)
  • &s[..] 是整个字符串的 slice

3. 用 slice 重写 first_word —— 数据与引用绑定

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];    // 返回开头到空格前的 slice
        }
    }
    &s[..]                     // 整个字符串
}

现在返回的 slice 和原始 String绑定在一起的。如果有人在持有 slice 时尝试修改原字符串,Rust 的借用规则会在编译时直接阻止:

let mut s = String::from("hello world");
let word = first_word(&s);
s.clear();                         // 编译错误!
println!("the first word is: {word}");

错误信息:cannot borrow s as mutable because it is also borrowed as immutable。编译器确保 word 这个不可变引用存在时,不能对 s 进行可变借用。这样,“清空字符串导致索引失效”这类 bug 在编译期就被彻底消灭了。


4. 字符串字面值就是 slice

let s = "Hello, world!";

这里的 s 类型正是 &str——它是指向二进制文件中硬编码数据的 slice。所以字符串字面值天然不可变,它本身就是一种不可变引用。


5. 更通用的函数签名:&str 作为参数

有经验的 Rustacean 会这样写:

fn first_word(s: &str) -> &str { ... }

这比 &String 更灵活,因为:

  • 可以直接接收字符串字面值
  • 可以接收 &String(Rust 会自动通过 Deref 强制转换将其变为 &str
  • 可以接收字符串的一部分 slice

无论传入什么形式的字符串数据,都能正常工作,API 通用且零成本。


6. 其他类型的 slice

数组也可以取 slice:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];   // 类型 &[i32]
assert_eq!(slice, &[2, 3]);

和字符串 slice 一样,存的是第一个元素的指针和长度。所有集合类型(如 Vec<T>)都支持这类 slice。


核心结论

  • slice 是一种引用,让你借用集合的一部分,而不用获取所有权。
  • 它让子序列的引用和原数据在编译时保持强绑定,杜绝了“索引与数据脱节”的错误。
  • 字符串字面值本质就是 &str
  • 参数用 &str&String 更通用、更符合 Rust 惯例。
  • 所有权、借用、slice 三者协同,使得 Rust 能在编译期就保证内存安全,且没有任何运行时开销。