18、Destructors(析构函数)
Destructors(析构函数)
既然使用了堆,那么我们应该对这一块内存负责。C/C++中,我们需要手动调用free函数,清理不再使用的内存空间。Java,C#等语言,提供了GC来帮我们管理内存,但是会有性能损失。同样的,Rust的语言特性决定了他也要像C/C++那样,手动管理内存,这在编译时就要决定好,不过好在,我们不需要手动调用各种函数去管理内存,Rust为我们做好了自动析构,释放内存的机制。
管理堆内存+无需手动管理,其实可以通过引入作用域
和析构函数
来实现。
作用域
作用域,就是一个变量有效的代码区域。
变量的作用域从它被声明的地方开始,当发生以下情况时,作用域结束:
-
声明变量的块结束
fn main() { // x还不在作用域里 let y = "Hello".to_string(); let x = "World".to_string(); // x的作用域开始 let h = "!".to_string(); } // x的作用域结束
-
变量的所有权被转移走
fn compute(t: String) { // Do something [...] } fn main() { let s = "Hello".to_string(); // <-- s作用域开始 // | compute(s); // <------------------- ..s作用域结束 // 因为s的所有权移动到了compute()里面的t }
析构函数
当值的所有者超出他自己的作用域时,Rust会为其调用析构函数。
析构函数会清理为这个值分配的任何资源,尤其是给它分配的内存。
我们可以通过将值传递给std::mem::drop
来手动调用值的析构函数,以清理它所占用的资源。
这就是我们经常听到Rust开发人员说的:“that value has been dropped!”,这表示当前超出了值的作用域,或者是已经对它调用了析构函数。
drop的操作
fn main() {
let y = "Hello".to_string();
let x = "World".to_string();
let h = "!".to_string();
}
上面这段代码,等同于下面的代码:
fn main() {
let y = "Hello".to_string();
let x = "World".to_string();
let h = "!".to_string();
// Variables are dropped in reverse order of declaration
drop(h);
drop(x);
drop(y);
}
fn compute(s: String) {
// Do something [...]
}
fn main() {
let s = "Hello".to_string();
compute(s);
}
上面这段代码,等同于下面的代码:
fn compute(t: String) {
// Do something [...]
drop(t); // <-- Assuming `t` wasn't dropped or moved
// before this point, the compiler will call
// `drop` here, when it goes out of scope
}
fn main() {
let s = "Hello".to_string();
compute(s);
}
注意:在main中调用compute之后,我们将所有权转移给了compute里面的t,与此同时,t的drop调用也归compute管。因为所有权归哪个作用域,哪个作用域就管理这个值的drop。main就不会再调用s的drop了,为了避免二次drop的情况。
在drop之后使用值
如果我们在某个值被drop后仍然尝试使用它,就会发生错误
let x = "Hello".to_string();
drop(x);
println!("{}", x);
drop会消耗被调用的值,这意味着该值在调用后不再有效。因此,编译器阻止我们使用它,避免释放后发生use-after-free bugs.
drop引用
在调用drop时,如果变量包含引用怎么办?
let x = 42i32;
let y = &x;
drop(y);
当我们调用drop(y)
什么也没发生。但是如果我们尝试编译代码,就会收到警告
这就是我们之前说的:“我们只想调用一次析构函数”。
我们可以对相同的值有多个引用。如果其中一个引用超出作用域,我们为它所指向的值调用析构函数,其他的引用将会指向一个不再有效的内存位置,即dangling pointer
(悬垂指针),和use-after-free
很相似。Rust所有权机制在设计上就排除了这种错误