What is the difference between Rust's Drop and C++'s destructor?

Viewed 912

While reading about the Drop trait, I found a lot of similarities between the drop method of Rust and the destructor in a C++. What is the difference between the two?

2 Answers

In practice, there is no appreciable difference. Both are used to clean up the resources of a type when appropriate.

Resources will be cleaned up irrespective of implementation of the Drop trait, won't they?

Yes. The compiler essentially automatically implements Drop for any type where the programmer does not. This automatic implementation simply calls drop for each member variable in turn.

If you allocate a resource that Rust doesn't know about, such as allocating memory directly from an allocator, Rust won't know that the returned value needs to be dropped or how to do so. That's when you implement Drop directly.

See also:

The only differences I know of are related to features that C++ has but Rust doesn't. Other folks have mentioned inheritance in the comments above, but a simpler example might be types with multiple constructors. In Rust, depending on what we mean by "constructor", we could say that every type has exactly one constructor (the syntax where you name the type and initialize all its fields). But in C++ a type can have multiple constructors, and importantly, one constructor can delegate to another. Here's a quick example:

class foo {
public:
  // the inner constructor
  foo(bool throw_in_inner_ctor) {
    if (throw_in_inner_ctor) {
      throw runtime_error("throw in inner ctor");
    }
  }

  // the outer constructor, which delegates to the inner one
  foo(bool throw_in_inner_ctor, bool throw_in_outer_ctor)
      : foo(throw_in_inner_ctor) {
    if (throw_in_outer_ctor) {
      throw runtime_error("throw in outer ctor");
    }
  }

  // the destructor
  ~foo() {
    cout << "foo dtor\n";
  }
};

int main() {
  try {
    cout << "construct a foo that throws in its inner ctor\n";
    foo(true, false);
  } catch (runtime_error) {}
  try {
    cout << "construct a foo that throws in its outer ctor\n";
    foo(false, true);
  } catch (runtime_error) {}
}

This prints:

construct a foo that throws in its inner ctor
construct a foo that throws in its outer ctor
foo dtor

What we're seeing here is that when the inner constructor throws, the destructor of foo doesn't get invoked. But when the outer constructor throws, it does. The rule is that our foo will get destructed if any constructor returns without an exception, regardless of what other delegating constructors might do. Rust has no equivalent to this rule, both because Rust doesn't have delegating constructors, and because constructors in Rust (if you want to call them that) cannot run arbitrary code and cannot fail.

Related