Hacker News new | ask | show | jobs
Ask HN: What should have been the term for RAII?
36 points by beaukin 1482 days ago
Bjarne Stroustrup himself said he was very busy when he came up with the name for this concept, that it’s not a very good name, and joked he’s not good at “marketing“.

What would be a better terminology?

As I’ve been learning modern C++, I admit that I’ve struggled to understand why these terms make sense. To me, I think of it like “Resource Acquisition Promising Release”. Does this betray that I am not truly understanding Strourstrup’s principle?

21 comments

I’m a Rust programmer but I would call it scope-based or scope-bound resource management, a popular alternative term in the C++ community.
> I would call it scope-based or scope-bound resource management

RAII isn't about scope, it's about lifetimes that can persist beyond the instantiating scope. More like object-bound resource management.

Many RAII objects really are tied to a simple scope - such as std::lock_guard. So it's not just about persisting beyond the initial scope. I don't recall the word "lifetime" being used in the C++ world before Rust came along. The word "scope" was often used in the same way we talk about lifetimes now. And if you think of the space between an object's constructor and its destructor as a scope, then anything that it owns is tied to its own scope. In this way, calling it "scope-based" is rather intuitive. The only wrinkles would be objects that are tied to multiple scopes (reference counted), and objects that change scopes (move semantics), but even then it's not hard to reason about. Regardless, the term RAII is widely understood, so I'm not sure it needs a new name.
> I don't recall the word "lifetime" being used in the C++ world before Rust came along.

"Lifetime" is a normative term in ISO C. In the 1999 standard, it is given as: "The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime." (6.4.2 Storage Durations of Objects)

"Lifetime" appears three times in the Index of the 1988 edition of Compilers: Principles, Techniques and Tools by Aho, Sethi and Ullman (a.k.a. the Red Dragon Book).

Lifetime, of a temporary 480

Lifetime, of an activation 391, 410

Lifetime, of an attribute 320-322, 324-329

It appears I was simply not exposed to the terminology then. But thanks for the information.
Scope is a necessary part of it too though. There’s always going to be one owning scope for an object in Rust (unless you’re using Rc) and that scope is responsible for calling the destructor. You can change which scope is responsible by moving it, but scope is still important. Without scope being an implicit part of resource management, you have a feature like try/finally or with in Python.
You're still trying to make this about rust without bothering to learn what the C++ users here are talking about. I'm not sure what to tell you.
Rust got the concept from C++ and it’s not a C++-specific concept, despite having originated there. You seem to have some fundamental disagreement with what I’m saying that you’ve yet to identify. What does RAII look like without scope, if it’s not about scope? How would RAII work in a dynamically scoped language?
Specifically, RAII plays really nicely with move semantics -- you can move objects between parent/child scopes, and sometimes that happens transparently (which may be an abomination, or may be heavenly, depending on your worldview). By moving objects (which can be forbidden explicitly or implicitly), you pass their associated resources around, leaving an empty husk to "clean up" when execution hits the end of a scope, which often compiles down to nothing. You need to be mindful of scopes, of course, but RAII isn't about scopes -- it's about being certain that your object is fully capable of satisfying its contracts the moment you've left the constructor.
RAII isn't just about automatic ("scope-based") variables. RAII refers to the way the language ties together the construction and allocation of the object.
Are there languages that do SBRM but not RAII?
Plenty of languages don't have RAII have SBRM. Scope based resource management predates the idea of constructors by decades. It's common in Lisp and Scheme. Haskel doesn't have RAII, but scope-based resource management is critical.

``` (call-with-output-file some-file (lambda (out) (write 'hello out))) ```

D has constructs:

    scope(exit) foo(); // Call foo at the end of the scope
    scope(success) foo(); //Call foo if all goes well
    scope(failure) foo(); //Call foo if the wings fall off the plane.
Python's with keyword maybe?

You can do

    with lock:
       # lock is held
    # lock released
    with lock:
       # lock is held again
Also Go's defer keyword, which is bound to the current function (defer until function returns).
Right, but those are opt-in features. In Python you can lock something and not release it by mistake. In Rust this pattern is enforced, the destructor will always release it at the end of the enclosing scope unless there is a panic or infinite loop. I think this is a substantive difference.
It's not enforced in Rust, but it's automatic unless you actively prevent it from happening. So you could say it's an opt-out feature.

std::mem::forget is considered safe, as explained in its docs: https://doc.rust-lang.org/std/mem/fn.forget.html

Rust doesn’t have initializers in the C++ sense. And I mean, even in C++ many classes like shared_ptr or even lock_guard have constructors that don’t acquire the resources yet. When the resource is acquired is an API design concern; the important part is where it’s released.
This sounds like an argument that C++ isn't RAII.
C++ _isn't_ RAII. RAII is a design pattern you can apply, leveraging C++ language features, in C++ in certain cases to avoid a certain class of bugs.
Yes. Eg if you look at Java try-with-resources there is an exact scope during which resources live and can be used. At the end of the scope the .close() method will be called.

For C++ and Rust there might not be a well defined scope since objects can be „moved“. Eg a method constructing an object can return it to the caller without the destructor being called.

It's well-defined, it's just more flexible than Java. The difference is that returning a value moves it, and resources are owned by the creating scope by default. Java doesn't have the same concept of ownership, or rather, you can't move values in Java.
Can we use some wilderness phrases like “Pack It In, Pack It Out” or “Leave No Trace”?
Hah, my vote is for LNT!
DRR, destructor resource release. At least that's the relevant part to me...
”For dust thou art, and unto dust thou shall return”
And for short, perhaps "dusting".
Constructor Acquires, Destructor Releases.
I too was going to say this!

The earliest mention of this idea (CADR / CADRe) I’ve found is from 2012: https://groups.google.com/a/isocpp.org/g/std-proposals/c/Una...

Unfortunate acronym, you're going to get a bunch of lisp docs...
You are right, I do program in LISP and love the C[AD]+R idea.

I even had my own proposal 5 years ago for improving that!

> That's right, but I would go one step further and have F(irst) and R(est), with obvious composition as follows: (using kruhft's examples from another thread)

    > (ff  x) == (caar  x) == (car (car x))

    > (rrf x) == (cddar x) == (cdr (cdr (car x)))
> I would argue that it's worth sacrificing the 'f' and 'r' symbols for such a common construct.

> …

> Yes, I realize I'm 55 years late to the party.

https://news.ycombinator.com/item?id=13259344

Now we only need to implement both changes at the same time! :-P

The only problem is that the car and cdr are not the first and rest of anything, when they are just used for tree structure.

Only when the tree structure conforms to certain conventions and intent of representing a list is the car "first" and the cdr "rest".

So of course those names are fine for nested lists: (ff '((a b c) d)): the "first of the first". But in, say, an assoc list ((a . 3)) 3 is not the "rest" of anything; it's the value of the key a.

The proposed functions go with the first and rest functions, rather than replace the cddr ones.

Now let's talk about something else: the order. In caddr, the order is just a condensation of the nested application of (car (cdr (cdr ...))), in the same order: it's easy to convert between the two, both actually and mentally. However, in left to right threading syntax, it's backwards:

   (flow value car car cdr (+ 1))
corresponds to

   (+ 1 (cdr (car (car value))))
so it condenses like

   (flow value cdaar (+ 1))
You can see someone wanting a variant which has the letters in the opposite order.

With f and r functions, you can do:

   (flow value f f r (+ 1))
which is almost the backwards "ffr" you might want.
It's also pretty pointless; it's just repeating the job description of the constructors and destructor of any object that manages a resource.

Who is that for? Dummies who get it backwards? Oh, look, you're releasing in your destructor and acquiring in the destructor. Did you forget your CADR?

In C++, if the construction fails (with an exception), no other member function - not even the destructor - gets called and the resource is not acquired.
This is one reason many people think exceptions in C++ are harmful, they cause unclear implicit behavior. Much better to handle a failure explicitly outside the constructor
This is one reason I use Pascal

If the constructor throws an exception, the destructor is called immediately.

The most important part for me is not construction,but the guaranteed destruction. So what about:

Resource Is Getting Guaranteed to End up Destructed.

I try to have RIGGED as backronym, but the words are not completely right. Even so, the slogan could work like this: Prefer using RIGGED resources in C++

Nice!!
BTW, a lot of people in this thread compare destructors to Java's try-with-resource (similarly, C#'s using), or Go's defer.

Here's one important difference: Destructors work in standard data structures. If for whatever reason you want to build a map<string, list<fstream>>, and the map goes out of scope, all files are correctly closed. (Rust's drop semantics work the same way.) That's a lot more work in Java or C#.

Won't Java or C# run the destructor eventually when they GC the equivalent of fstream?

Yes, that means they hang around a lot longer, and that's sometimes problematic, but that's the GC way.

To be technically correct, it’s called a finalizer in that case to distinguish it from deterministic destructors.

For resources like memory, where it’s merely a question of performance, I would agree. But some resources have correctness implications. Files, for example, you often want to close deterministically. It’s simply a terrible experience if a user can’t save because a file handle from a previous operation still lingers in a GC queue somewhere.

That’s also the reason why language features like try-with-resources were added in the first place.

Something with scopes? "Scoped resources" ?
Wow, such good ideas here. I guess no one is keen on my "RAPR" proposal :) But just to reflect a bit more, I think my curiosity/confusion stems from the "is initialization" wording, and how the word "initialization" in the context of C++ will imply a guaranteed freeing step?

I've tried reconciling how its defined here:

https://en.cppreference.com/w/cpp/language/initialization

However, it seems like wording describing ideas around how a resource is created, and not how it's completed.

cppreference defines it as:

> Resource Acquisition Is Initialization or RAII, is a C++ programming technique[1][2] which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the lifetime of an object.

I’d crib the name from Rust, this is ownership, where the object owns the resource.

After not much thought on the matter, I'd call it Resourceful Instantiation.
I think scope or similar is good.

Also, see related video here

  CppCon 2015: Andrei Alexandrescu “Declarative Control Flow"
  https://youtu.be/WjTrfoiB0MQ?t=1046
I would just call it "destructors". It's a little imprecise because some GC languages also have destructors, but arguably they shouldn't.
Resource Encapsulation
I like this one. Very straightforward and unambiguous.
I also know it as 'janitor', but maybe thats not a known term or something different?
I tend to use "deterministic finalizers" for the part that (in my understanding) is the most critical
I always just referred to it as "deterministic destruction." I think that's reasonable, anyway.
Managed resource lifetime
it sounds like Disposable
Autofree