Hacker News new | ask | show | jobs
by motorest 382 days ago
> This seems like an extremely low bar.

Is it, though? Most mainstream languages fail to support anything resembling RAII, at least as first-class support. Do you actually have an example of a language that does a better job at resource management than C++?

5 comments

Rust simply because the constructor being a static method on the type that can return basically anything including optional and result types and copy / move semantics are handled very easily through macros instead of constructor / assignment operator overrides.
> Rust simply because the constructor being a static method (...)

This assertion makes no sense. RAII is not defined by whether you use factory functions or special member functions to initialize an object. In fact, RAII is only incidentally related ro memory management. RAII is a strategy to manage any and all types of resources, ranging from memory management to even files and TCP connections, by leveraging assurances that the runtime provides regarding scopes and object life cycles. None of this changes if you employ factory functions to initialize resources.

Not that I agree with the condescending parent (e.g. "you're still stuck with a C++ programmer you need to figure out how to socialize with"), but...

> Most mainstream languages fail to support anything resembling RAII

Wikipedia says: "RAII is associated most prominently with C++, where it originated, but also Ada,[3] Vala,[4] and Rust"

> Do you actually have an example of a language that does a better job at resource management than C++?

They don't necessarily offer a pattern like RAII, but what about "try-with-resources" in Java, or "use" in Kotlin that goes with `AutoCloseable`?

And what about "using" in C#?

What about "defer" in Swift?

I find those simpler than RAII.

> Wikipedia says: "RAII is associated most prominently with C++, where it originated, but also Ada,[3] Vala,[4] and Rust"

From those you listed, only Rust can be described as mainstream. Do you think that one out of a couple dozens refutes the statement that "most mainstream languages fail to support anything resembling RAII"?

> They don't necessarily offer a pattern like RAII, but what about "try-with-resources" in Java, or "use" in Kotlin that goes with `AutoCloseable`?

Try-with-resources and the disposable pattern offer similar features but they still fall a bit short. Unlike RAII, they are not thread-safe, require specialized syntax and boilerplate code, and require manually specifying scopes.

But even if you consider try-with-resources and Disposable pattern as a perfect replacement of RAII, now point out how many mainstream languages support them. You have Java and JVM languages, you have C# and .NET languages, Python, and...

> I find those simpler than RAII.

Arguably this boils down to personal taste, but RAII actually ensures your resources will be released, and you can tell exactly when this will happen. Disposable patterns don't, and screwing up a using with statement is all it takes to get your application to leak resources and fail silently.

One caveat though, you need to pair those with static analysis tooling, if you want to ensure developers don't forget to make use of them.
I agree, this is a real benefit of RAII compared to defer. That said, there are disadvantages of making the resource freeing part invisible. Not having to debug destructors that do network IO (e.g. closing a buffered writer that writes to the other side of the world) or expensive computation (e.g. buffered IO writing to a compressor) is a define plus. Don’t get me started on error handling…
> I agree, this is a real benefit of RAII compared to defer. That said, there are disadvantages of making the resource freeing part invisible.

RAII doesn't make resource freeing invisible. It makes it obvious, deterministic, and predictable. The runtime guarantees that, barring an abnormal program termination that's not handled well, your resource deallocation will take place. This behavior is not optional, or requires specifying ad-hoc blocks or sections.

How does RAII solve that? Developers can "forget" to use RAII, right? Or are you saying that it's easier to spot because RAII requires quite a bunch of boilerplate (whereas a one-liner like "defer" is easier to forget)?

Not criticising, really trying to understand :-).

RAII when supported natively by the language, is done at the implementation level, not the usage point.

So in languages like Rust, D, Ada, Swift, C++, the compiler will do the rest unless you go out of your way to avoid the call to take place, like placing a value type on the heap using plain pointers.

With the other approach, even if you implement IDisposable, AutoCloseable, ContextManager and similar, you have to remember to manually write the code pattern that takes care of calling close(), or whatever the method/function happens to be called.

In languages with good support for FP patterns, like trailing lambdas, currying and such, there is another pattern, that is much safer, in case you don't want a static analysis tool to track resource usage, the with pattern.

You do something like withDBConnection connection (fun db -> all related db operations).

Assuming the lambda doesn't do naughty things to have db parameter escape the scope, the withDBConnection function will take care of handling the whole connection lifecycle.

When I switched from C++ to a bunch of other languages, I missed RAII initially. However, I quickly learned that other languages just do it differently and often even better (ever had to check an error when closing a resource?). Nowadays, I think that RAII is a solution to a C++ problem and other languages have a better solution for resource management (try-with-resources in Java, defer in Go, with in Python).
> However, I quickly learned that other languages just do it differently and often even better (ever had to check an error when closing a resource?).

I don't think that's even conceptually the same. The point of RAII is that resource deallocation is ensured, deterministic, and built into the happy path. Once you start to throw errors and relying on those to manage resources, you're actually dealing the consequences of not having RAII.

> try-with-resources in Java, defer in Go, with in Python

Or 'goto error8;' in C. Still RAII is much more convenient, especially for cases where you allocate a lot of interdependent resources at different time points. It keeps deallocation logic close to allocation logic (unlike, say, defer), makes sure deallocation always happens in the reverse order of allocations and doesn't force you to create a nested scope each time you allocate a resource

Obviously a language with linear types is just better at this, such as Austral.

In terms of languages you'd actually deploy today Rust is better both at this narrow feature and more broadly.

Ada/SPARK, with better ergonomics than Rust, but its use will stay niche in high integrity computing.