bigywater.tech
Rust · 119

ManuallyDrop

ManuallyDrop 把「什么时候 drop」的决定权交还给你。这篇用带打印的 Drop 验证它的行为,并用三个典型场景(改 drop 顺序、从 Drop 类型里取出字段、拆装 Vec)说明它为什么存在。

0 行
ManuallyDrop 包住的值在作用域结束时的 drop 输出
E0509
从实现了 Drop 的类型里移出字段的错误码
16 字节
ManuallyDrop 与 Noisy 的大小(没有额外开销)

值不会自动 drop

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      size_of::<Noisy>() = 16  size_of::<ManuallyDrop<Noisy>>() = 16
      作用域结束:ManuallyDrop<Noisy(忘记)> 不会 drop(下面不会有它的 drop 行)
      通过 Deref 使用内部值:手动
      Noisy(手动) drop
      ManuallyDrop::drop 之后
      into_inner 取回所有权,恢复正常 drop 规则:
      Noisy(取出) drop

Drop 的规则见Drop Trait 与资源释放。

改变 drop 顺序

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    默认:先 first,后 second
      Noisy(first) drop
      Noisy(second) drop
    用 ManuallyDrop 反过来:先 second,后 first
      Noisy(second) drop
      Noisy(first) drop

从 Drop 类型里移出字段

实测 · 本机执行输出
  源码:
      1 | struct Guard { data: String }
      2 | impl Drop for Guard { fn drop(&mut self) {} }
      3 | fn main() { let g = Guard { data: "x".into() }; let s = g.data; println!("{}", s); }
  编译:失败
    error[E0509]: cannot move out of type `Guard`, which implements the `Drop` trait
     --> <实验目录>/src/u119c.rs:3:57
      |
    3 | fn main() { let g = Guard { data: "x".into() }; let s = g.data; println!("{}", s); }
      |                                                         ^^^^^^
      |                                                         |
      |                                                         cannot move out of here
      |                                                         move occurs because `g.data` has type `String`, which does not implement the `Copy` trait
      |
    help: consider borrowing here
      |
    3 | fn main() { let g = Guard { data: "x".into() }; let s = &g.data; println!("{}", s); }
      |                                                         +
    help: consider cloning the value if the performance cost is acceptable
      |
    3 | fn main() { let g = Guard { data: "x".into() }; let s = g.data.clone(); println!("{}", s); }
      |                                                               ++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0509`.
  编译:成功
  运行(退出码 0):
      into_data 取出 "payload";Guard 自己的 Drop 没有运行(没有上一行的输出)
      Guard::drop 运行了,会 drop 内部的 String

Vec 的原始部件

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      拆出 ptr / len = 4 / cap = 4;原 Vec 被 ManuallyDrop 包着,不会自动释放缓冲区
      from_raw_parts 重新组装:[1, 2, 3, 4](缓冲区只会被释放一次)

未初始化内存见MaybeUninit。

与 forget 的区别

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      ManuallyDrop 之后还能读: ManuallyDrop
      Noisy(ManuallyDrop) drop
      forget 之后值没法再用、也没法再 drop;ManuallyDrop 可以

分步演示

ManuallyDrop
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:ManuallyDrop 内部值被 drop 后再访问的后果(UB,不演示)、panic 时的 drop 行为、ManuallyDrop 与 Pin 的组合、结构体字段 drop 顺序的语言保证条款
    京ICP备2026067502号-1京公网安备11010502063487号