Wrong terminology used in Rust (drop/destructor/finaliser)

BTW why you use a term “method” (like in Java) instead of “member function” or just “function” in the error message about a direct call to drop? If you’re good with this Java term why you’re not with the finalizer one?

Ha, that's no member function; This is a member function!:

struct Struct<F: Fn()> {
    member_function: F,
}

fn main() {
    let s = Struct { member_function: main };

    (s.member_function)();  // contrast with s.method()
}

...or maybe that's a "function field..." :duck:


A more serious response to your question would be, even a broken clock gives the right time twice a day. It's not like we're going to say, "well, crud, we can't call these methods because that's what Java does and Java is wrong."

3 Likes

C++ destructors work pretty much like the Rust one. The difference is that
you have to manually destroy members that are owned raw pointers, but you
have to do that in Rust as well, we just don't use raw pointers that much.

Generally "finalizer" is used in garbage collected contexts for the user
hook that may be called once or not at all.

10 Likes

drop_in_place is Rust's .~T()

1 Like

I was referring to things in the context of safe Rust. But yeah, once you leave safe Rust you can pretty much do all the things C++ can.

So-called 'modern C++' uses RAII all the time, and strongly discourages manual use of new and delete. So there's not that much difference.

5 Likes

Nope, the main difference between a Java finalizer and C++/Rust destructor is when (if at all) it is run. In Java, this is non-deterministic.

Nowadays it is verry common and it is considered idiomatic in modern C++. Manual memory management is typically considered an anti-pattern these days in C++ (except for cases where smart pointers et al. are unfeasible, but that is not typical).

You seem to be confused as to how destruction works in C++. Consider the following example code:

class Foo {
    int *bar;
};

You can see there's a raw pointer named bar. In a destructor of this class, you'll probably want to free the memory pointed to by this pointer (if any). However, this is orthogonal to the fact that the destructor of bar itself (not the memory bar points to) will run no matter what you do in the destructor. Using smart pointers / RAII relies exactly on this principle. By using smart pointers the semantics of the destruction do not change at all (smart pointers are not a language feature), the freeing of the pointed memory is simply moved from the destructor of the class Foo into the smart pointer class's destructor. Basically it's just an application of the DRY principle.

This is very much the same as with Rust. You could use the same raw pointer in Rust as well:

struct Foo {
    bar: *mut i32,
}

Now if you allocate memory for the bar pointer (eg. in constructor or somewhere), you need to manually free it in the drop implementation, otherwise it will leak just like in C++ (and just like in C++ the destructor for bar itself will run no matter what you do in drop).

2 Likes

In C++ a destructor must delete member objects that otherwise will leak.

No, not at all. That only applies if you have semantics which requires additional actions to be performed upon object destruction, e.g. ownership through a raw pointer. If you have a member embedded in a struct by value, or you are using smart pointers, then in fact the very same thing happens in C++ as in Rust: each such member gets its destructor run and is subsequently deallocated automatically.

It's the same in Rust: Drop fields/variants will have their drop() method called, and every field/variant is deallocated automatically. And if you are implementing a type which does its own memory management through raw pointers, e.g. Vec, then you also need to free memory that was allocated in a way not understood by the type system. (This is not specific to memory management, by the way — if you need to perform another action which is unrelated to memory management, you can also do that in drop.)

So there's really no significant difference between the semantics of destructors in C++ and Rust, and thus it's entirely appropriate to call Drop::drop as such. I think the reason you might be confused is that in C++, before the advent of smart pointers in the standard library, it was a popular practice to express owning pointers implicitly, via raw pointers. However, that is/was neither necessary or considered particularly good design, and even then it was possible and good practice to use other kinds of smart pointers (e.g. those from the Boost library).

By the way, the general observation that one can't explicitly call Drop::drop() in Rust is true, but one can still prematurely terminate the lifetime of an object by mem::dropping it.

1 Like

My 2 cts:

The terms Drop and Destructor are both fine by virtue of being unambiguously defined within the Rust community. You may not like it, but Drop is the name, and change it will not as the cost to the ecosystem as a whole would simply be unreasonably great.

As for the term finalizer: analogously to the paragraph above, pretty much everyone I've asked associates that term with managed runtimes e.g. the JVM. That is regardless of where it actually originated. Accepting the term to refer to JVM style finalizers, the crucial thing that makes them different from destructors is that the JVM simply doesn't guarantee that finalizers are run at all.
This makes them basically useless in practice, as you can't actually do cleanup with it: If the finalizer doesn't run for whatever reason, you've got a leak now.

I'm not completely unsympathetic to the argument: a peeve of mine is hearing about the umpteenth DDoS "perpetrated by hackers". Hmpf.

Still, ultimately the terms themselves are as arbitrary as why a "door" is called a "door", and discussing (potential) changes to them seems at best without utility to me and at worst counterproductive, as discussions like these pretty much never get above the bikeshedding level in importance.

1 Like