Destructors and resource cleanup
In C++, a destructor for a class T
is defined by providing a special member
function ~T()
. To achieve the equivalent in Rust, the Drop
trait is implemented for a
type.
For an example, see the chapter on copy and move constructors.
Drop
implementations play the same role as destructors in C++ for types that
manage resources. That is, they enable cleanup of resources owned by the value
at the end of the value's lifetime.
In Rust the Drop::drop
method of a value is called automatically by a
destructor when the variable that owns the value goes out of scope. Unlike in
C++, the drop method cannot be called manually. Instead the automatic "drop
glue" implicitly calls the destructors of fields.
Lifetimes and destructors
C++ destructors are called in reverse order of construction when variables go out of scope, or for dynamically allocated objects, when they are deleted. This includes destructors of moved-from objects.
In Rust, the drop order is similar to that of C++ (reverse order of declaration). If additional specific details about the drop order are needed (e.g., for writing unsafe code), the full rules for the drop order are described in the language reference. However, moving an object in Rust does not leave a moved-from object on which a destructor will be called.
#include <iostream>
#include <utility>
struct A {
int id;
A(int id) : id(id) {}
// copy constructor
A(A &other) : id(other.id) {}
// move constructor
A(A &&other) : id(other.id) {
other.id = 0;
}
// destructor
~A() {
std::cout << id << std::endl;
}
};
int accept(A x) {
return x.id;
} // the destructor of x is called after the
// return expression is evaluated
// Prints:
// 2
// 3
// 0
// 1
int main() {
A x(1);
A y(2);
accept(std::move(y));
A z(3);
return 0;
}
struct A { id: i32, } impl Drop for A { fn drop(&mut self) { println!("{}", self.id) } } fn accept(x: A) -> i32 { return x.id; } // Prints: // 2 // 3 // 1 fn main() { let x = A { id: 1 }; let y = A { id: 2 }; accept(y); let z = A { id: 3 }; }
In Rust, after ownership of y
is moved into the function accept
, there is
no additional object remaining, and so there is no additional Drop::drop
call
(which in the C++ example prints 0
).
Rust's drop methods do run when leaving scope due to a panic, though not if the panic occurs in a destructor that was called in response to an initial panic.
Early cleanup and explicitly destroying values
In C++ you can explicitly destroy an object. This is mainly useful for situations where placement new has been used to allocate the object at a specific memory location, and so the destructor will not be implicitly called.
However, once the destructor has been explicitly called, it may not be called again, even implicitly. Thus the destructor can't be used for early cleanup. Instead, either the class must be designed with a separate cleanup method that releases the resources but leaves the object in a state where the destructor can be called or the function using the object must be structured so that the variable goes out of scope at the desired time.
In Rust, values can be dropped early for early cleanup by using
std::mem::drop
. This works
because (for non-Copy
types)
ownership of the object is actually transferred to std::mem::drop
function,
and so Drop::drop
is called at the end of std::mem::drop
when the lifetime
of the parameter ends.
Thus, std::mem::drop
can be used for early cleanup of resources without having
to restructure a function to force variables out of scope early.
For example, the following allocates a large vector on the heap, but explicitly drops it before allocating a second large vector on the heap, reducing the overall memory usage.
Click here to leave us feedback about this page.fn main() { let v = vec![0u32; 100000]; // ... use v std::mem::drop(v); // can no longer use v here let v2 = vec![0u32; 100000]; // ... use v2 }