bigywater.tech
Rust · 116

Slice 的胖指针

&v[2..7] 是怎么做到不复制的?切片里到底存了什么?越界会怎样?空切片和零大小元素的切片指针又是什么?这篇把切片拆成「指针 + 长度」逐项验证。

16 字节
&[T] 的大小(指针 + 长度)
0 次复制
切出子切片时被复制的元素数
0x8
空 &[u64] 的指针(非空、对齐的悬空地址)

指针与长度

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      &v[..]             指针 = base +  0 字节  长度 10
      &v[2..7]           指针 = base +  8 字节  长度 5
      &v[5..]            指针 = base + 20 字节  长度 5
      &v[2..7][1..3]     指针 = base + 12 字节  长度 2
      &v[3..3] (空)       指针 = base + 12 字节  长度 0
      数组 [u8; 4] 转 &[u8]:长度 4,数据地址相同 true
      size_of::<&[u32]>() = 16(指针 8 + 长度 8)

数组、切片与 Vec 的区别见数组、切片与 Vec;trait object 的胖指针见Trait Object 的胖指针。

切片是视图

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      改 &mut v[1..4] 的第 0、2 个元素后 v = [1, 20, 3, 40, 5, 6]
      split_at_mut(3) 得到两个不重叠的 &mut 切片,分别改后 v = [100, 20, 3, 400, 5, 6]
  源码:
      1 | fn main() { let mut v = vec![1, 2, 3, 4]; let a = &mut v[0..3]; let b = &mut v[2..4]; a[0] = 1; b[0] = 2; }
  编译:失败
    error[E0499]: cannot borrow `v` as mutable more than once at a time
     --> <实验目录>/src/u116c.rs:1:78
      |
    1 | fn main() { let mut v = vec![1, 2, 3, 4]; let a = &mut v[0..3]; let b = &mut v[2..4]; a[0] = 1; b[0] = 2; }
      |                                                        -                     ^        ---- first borrow later used here
      |                                                        |                     |
      |                                                        |                     second mutable borrow occurs here
      |                                                        first mutable borrow occurs here
      |
      = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0499`.

越界

实测 · 本机执行输出
  编译:成功
  【范围越界】退出码 101:['range end index 5 out of range for slice of length 3']
  编译:成功
  【起点大于终点】退出码 101:['slice index starts at 3 but ends at 1']
  编译:成功
  【str 切在字符中间】退出码 101:["end byte index 2 is not a char boundary; it is inside 'é' (bytes 1..3 of string)"]
  编译:成功
  运行(退出码 0):
      v.get(1..5) = None  v.get(1..3) = Some([2, 3])

零大小与空切片

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      &[()] 长度 1000000,size_of_val = 0 字节(100 万个零大小元素不占内存)
      空切片 &[u64] 的指针 0x8:非空 true,对齐到 8: true(指向「一个对齐的悬空地址」,没有内存可读)

0x8 是这台机器上 u64 空切片的占位地址,是实现细节,换类型或平台可能不同。

拆分操作

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      from_raw_parts(ptr+2, 4) = [3, 4, 5, 6]
      chunks(3):[(0, 3), (3, 3), (6, 2)]
      windows(3) 的前 3 个窗口:[(0, 3), (1, 3), (2, 3)]
      split_first:first = 1,rest 指针偏移 1 长度 7
      上面的 (偏移, 长度) 都是以元素为单位,偏移相对于 v 的起点;没有任何元素被复制

分步演示

切片的胖指针
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:空切片的占位地址是否总等于对齐值(只测了 u64)、from_raw_parts 违反安全条件时的后果、&mut 切片别名的编译器优化(noalias)、切片比较与排序的复杂度
    京ICP备2026067502号-1京公网安备11010502063487号