rust语言学习笔记Trait(十五)Drop(释放资源)

1、Drop 的定义

Drop 是 Rust 标准库中一个特殊的 ‌trait‌,它的唯一作用是让你定义一段代码,当 ‌值离开作用域‌ 时自动执行清理工作,就像一个自动触发的"析构函数"。

rust 复制代码
pub trait Drop {
    fn drop(&mut self);
}

你只需要为你的类型实现这个 drop 方法,剩下的交给编译器------它会自动在合适的时机插入调用。

2、为什么需要 Drop

Rust 没有垃圾回收(GC),但通过 Drop 实现了 ‌**确定性资源管理(RAII)**‌,保证资源(内存、文件、网络连接、锁等)在不再使用时立即释放,而不是等 GC 的随机延迟。

例如:

  • Vec<T>Drop 中释放堆内存
  • FileDrop 中关闭文件描述符
  • MutexGuardDrop 中释放锁
  • Rc<T>Drop 中减少引用计数

有了 Drop,你就不用手动调用 close()free(),也基本不用担心资源泄漏(除了少数特殊情况,如循环引用)。

3、什么时候调用 Drop

编译器会在以下时机自动插入 drop 调用:

  1. 变量离开词法作用域‌(最典型)
rust 复制代码
{
    let s = String::from("hello");
    // 离开这个花括号时,自动调用 s 的 drop,释放堆上 "hello" 的内存
}
  1. 变量被重新赋值‌

    (原来绑定的值离开作用域,旧值的 drop 会先执行)

  2. 使用 std::mem::drop 函数主动提前释放

    (注意:这不是 Drop::drop 方法,而是一个标准函数,通过获取所有权然后丢弃来触发析构)

  3. 包含该值的结构体被析构时

    (结构体自身析构前,会先递归调用所有字段的 Drop

4、调用顺序

  • 按声明顺序的逆序析构‌:最后创建的变量最先被清理。
  • 结构体的字段:按定义时的顺序析构(第一个定义的字段最后析构?实际是顺序析构,但官方文档有个例子显示结构体实例本身先析构,然后字段按定义顺序?需要明确:结构体自身的 drop 方法(如果实现)会在字段析构前调用。字段的析构顺序是它们在结构体中定义的顺序。这和变量声明逆序不同。官方例子:struct Outer(Inner);Outerdrop 先执行,然后 Innerdrop 执行。所以规则是:先执行外层类型的 drop,然后按字段定义顺序递归析构字段。)
rust 复制代码
struct MyStruct{name: String}

impl Drop for MyStruct {
    fn drop(&mut self) {
        println!("drop {}", self.name);
    }
}

fn main() {
    let my_struct1 = MyStruct{name: "hello".to_string()};
    let my_struct2 = MyStruct{name: "world".to_string()};
    let my_struct3 = MyStruct{name: "rust".to_string()};
}

// drop rust
// drop world
// drop hello

5、自动关闭打开的文件

rust 复制代码
// 自定义结构体
struct MyFile {
    file: std::fs::File,     // 存放文件对象
    name: String,            // 存放文件名
}

impl MyFile {
    fn new(filename: &str) -> std::io::Result<Self> {
        let file = std::fs::File::create(filename)?;  // 创建文件
        Ok(Self {
            file,                                     // 存放文件对象
            name: filename.to_string(),               // 存放文件名
        })
    }

    fn write(&mut self, data: &str) -> std::io::Result<()> {
        use std::io::Write;
        writeln!(self.file, "{}", data)          // 写入数据到文件
    }
}

impl Drop for MyFile {
    fn drop(&mut self) {                         // 实现 Drop 的 drop 方法
        println!("{} 文件已关闭!", self.name);    // 打印文件名
    }
}

fn main() -> std::io::Result<()> {
    let mut my_file = MyFile::new("test.txt")?;   // 创建文件
    my_file.write("hello world")?;                // 写入数据到文件   
    Ok(())
}

6、注意事项

(1)严禁手动调用 drop 方法

因为 Rust 要保证同一值不会被析构两次(双重释放)。如果想提前释放,用 std::mem::drop(x),它接管所有权然后立即丢弃。

(2)Copy 与 Drop 互斥

如果一个类型实现了 Copy,就不能实现 Drop,反之亦然。原因很简单:Copy 类型允许按位复制,每个副本都会在离开作用域时调用 drop,这可能导致同一资源被释放多次,引发未定义行为。

(3)Drop 中不能获取所有字段的所有权

drop 方法签名是 &mut self,不是 self。如果参数是 self,会在函数结束时再次触发 drop,导致无限递归。因此你只能通过可变引用去修改字段,不能将字段 "move" 出来。如果必须在 Drop 中移出字段值(比如调用某个接收 self 的释放函数),通常可以用 Option<T> 包裹该字段,然后在 drop 中使用 Option::take() 取出。

(4) ‌循环引用会导致泄漏

例如 Rc<RefCell<T>> 形成的循环引用,即使所有外部引用都消失,引用计数也不会归零,Drop 永远不会被调用。这是 Rust 允许的一种"安全"内存泄漏,需要开发者手动用 Weak 打破循环。

(5) ‌Panic 安全

如果在 Drop 实现中发生了 panic,Rust 会直接中止进程(unwind 被中止),以免出现双重 panic 的混乱。所以尽量避免在 drop 中做可能 panic 的操作

7、作用域守卫:计时作用域

rust 复制代码
struct MyTimer {
    start: std::time::Instant,     // 开始时间
    name: String,                  // 计时器名称
}

impl MyTimer {
    fn new(name: &str) -> Self {
        println!("{} 开始计时!", name);       // 打印计时器名称
        Self {
            start: std::time::Instant::now(), // 记录开始时间
            name: name.to_string(),           // 记录计时器名称
        }
    }
}

impl Drop for MyTimer {
    fn drop(&mut self) {
        // 实现 Drop,计时结束时打印耗时信息
        println!(
            "\n{} 计时结束,耗时:{:?} !",
            self.name,
            self.start.elapsed()
        );
    }
}

fn main() {
    // 创建一个计时器实例
    let _timer = MyTimer::new("test");
    for _i in 0..10000000 {
        print!("");
    }
}

main 函数结束时,计算耗时。输出:

bash 复制代码
test 开始计时!

test 计时结束,耗时:769.93ms !

类似地,MutexGuard 会用 Drop 来释放锁,保证锁一定被释放,即使临界区 panic 了。

8、总结

Drop 是 Rust 内存安全和资源管理的基石,它让:

  • 资源释放变得 ‌确定性 ‌ 和 ‌自动化
  • 代码 ‌不易泄漏 ‌,且 ‌无需手动清理
  • 通过 RAII 模式,代码 ‌简洁而健壮
    • RAII‌:资源获取即初始化,‌资源释放即清理。

使用时只要牢记它的调用时机、顺序和限制,你就能写出非常可靠的系统级代码。

相关推荐
寒月小酒10 小时前
第三章 索引构建(2-all -in-rag学习)
学习
天国梦10 小时前
中学生居家英语听力训练深度解析:从核心痛点到AI赋能的全链路方案
人工智能·学习
花褪残红青杏小11 小时前
Rust图像处理第19节-RGB 色彩矩阵变换:矩阵 × 向量 = 像素变换
rust·webassembly·图形学
white_ant14 小时前
15-ASCII 转换工具使用指南
rust·tauri·watools
hengcaib14 小时前
防封号电销外包公司怎么选合规线路与风控核查方法
笔记
胡渠洋15 小时前
postman学习
学习·测试工具·postman
Amazing_Cacao16 小时前
CFCA精品可可产区风土解析(美洲):无情打破风味堆叠假象,建立时间轴上的层次动态阅读系统
学习
六点_dn16 小时前
Linux学习笔记-shell运算符
linux·笔记·学习
其实防守也摸鱼17 小时前
渗透--损坏的对象级别鉴权漏洞(Broken Object Level Authorization, BOLA)
大数据·网络·数据库·学习·tcp/ip·安全·安全威胁分析
念雨思17 小时前
信用卡权益对比:基于HarmonyOS + ArkTS的AI智能金融助手开发实践
人工智能·学习·华为·金融·harmonyos·鸿蒙