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‌:资源获取即初始化,‌资源释放即清理。

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

相关推荐
IT笔记2 小时前
【Rust】 Rust宏学习笔记
笔记·学习·rust
tianxingjian20192 小时前
从欧盟电池法新规看QFD:如何将合规需求转化为技术特性?
笔记
网络与设备以及操作系统学习使用者2 小时前
路由器如何实现跨VLAN通信
运维·网络·学习·华为·智能路由器
喜樂的CC2 小时前
NestJS图解笔记
笔记
182******20832 小时前
2026年学C语言还有出路吗?学习需要报班吗?
c语言·开发语言·学习
智者知已应修善业3 小时前
【51单片机数码管驱动2位显示0-99按键3短按+1长按+10按键4短按-1长按清零,按键不影响数码管显示】2023-8-16
c++·经验分享·笔记·算法·51单片机
whyTeaFo3 小时前
MIT 6.1810: xv6 book Chapter5: Page faults 笔记
笔记
rime_neko3 小时前
开发部署笔记
笔记
东方佑3 小时前
可学习破坏策略:实现大语言模型二倍推理加速的统一自洽框架
人工智能·学习·语言模型