Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15.4 Drop trait:告别手动清理,释放即安全

15.4.1 Drop trait 的意义

如果某个类型实现了 Drop trait,就可以让程序员自定义值离开作用域时发生的操作。例如释放文件或网络资源。

在某些语言中(比如 C/C++),对于某些类型,程序员每次用完这些类型的实例时都必须写代码来释放内存或资源。如果忘记了,系统可能会过载并崩溃。在 Rust 中,程序员可以指定每当值离开作用域时运行的代码,编译器会自动插入这段代码。

任何类型都可以实现 Drop trait,而 Drop 只要求实现 drop 方法,其参数是对 self 的可变引用。Drop 在预导入模块(prelude)中,因此使用时不必手动引入。看个例子:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}
  • 结构体 CustomSmartPointer 有一个 data 字段,类型为 String
  • 通过 impl Drop for CustomSmartPointerCustomSmartPointer 实现了 Drop trait。在其中实现 drop 方法,参数是 &mut self。这个方法通常用于释放资源,但出于演示目的,这里只打印一句话,并把 selfdata 字段的内容输出出来。
  • main 中创建了两个 CustomSmartPointer 实例:c 存的是 "my stuff"d 存的是 "other stuff"。最后打印 "CustomSmartPointers created."

输出:

CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

程序会先打印 mainprintln! 的内容,也就是 "CustomSmartPointers created."。因为 cdmain 结束时离开作用域,所以程序会按声明的逆序丢弃它们:先 d,再 c。由于这里实现的 drop 会打印一句话,因此这两个值在被丢弃时都会各打印一行。

15.4.2 使用 std::mem::drop 来提前丢弃值

比较遗憾的是,我们很难直接禁用自动的 drop,也没必要这么做。因为 Drop trait 的目的就是自动处理清理逻辑。

此外,Rust 不允许手动调用 Drop trait 的 drop 方法。但你可以调用标准库函数 std::mem::drop 来提前丢弃一个值,这相当于提前调用了 Dropdrop 方法。它的参数就是要丢弃的值。看:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    drop(c);
    println!("CustomSmartPointers created.");
}

main 中,我们手动使用 drop 清理了 c,而 d 仍然是自动清理的。输出顺序应该是 cd 之前。

输出:

Dropping CustomSmartPointer with data `my stuff`!
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!

有些人可能会疑问:如果 c 在离开作用域之前就被丢弃了,那么离开作用域后编译器会不会再调用一次 drop,从而导致二次释放(double free)错误?答案是不会。Rust 的设计是安全的。它的所有权系统会保证引用有效,而 drop 也只会在确定不再使用这个值时被调用一次。