Java/C# – Why Can’t Implement RAII?

cjavaraiiresourcessmart-pointer

Question:
Why can't Java/C# implement RAII?

Clarification:
I am aware the garbage collector is not deterministic. So with the current language features it is not possible for an object's Dispose() method to be called automatically on scope exit. But could such a deterministic feature be added?

My understanding:

I feel an implementation of RAII must satisfy two requirements:
1. The lifetime of a resource must be bound to a scope.
2. Implicit. The freeing of the resource must happen without an explicit statement by the programmer. Analogous to a garbage collector freeing memory without an explicit statement. The "implicitness" only needs to occur at point of use of the class. The class library creator must of course explicitly implement a destructor or Dispose() method.

Java/C# satisfy point 1. In C# a resource implementing IDisposable can be bound to a "using" scope:

void test()
{
    using(Resource r = new Resource())
    {
        r.foo();
    }//resource released on scope exit
}

This does not satisfy point 2. The programmer must explicitly tie the object to a special "using" scope. Programmers can (and do) forget to explicitly tie the resource to a scope, creating a leak.

In fact the "using" blocks are converted to try-finally-dispose() code by the compiler. It has the same explicit nature of the try-finally-dispose() pattern. Without an implicit release, the hook to a scope is syntactic sugar.

void test()
{
    //Programmer forgot (or was not aware of the need) to explicitly
    //bind Resource to a scope.
    Resource r = new Resource(); 
    r.foo();
}//resource leaked!!!

I think it is worth creating a language feature in Java/C# allowing special objects that are hooked to the stack via a smart-pointer. The feature would allow you to flag a class as scope-bound, so that it always is created with a hook to the stack. There could be options for different types of smart pointers.

class Resource - ScopeBound
{
    /* class details */

    void Dispose()
    {
        //free resource
    }
}

void test()
{
    //class Resource was flagged as ScopeBound so the tie to the stack is implicit.
    Resource r = new Resource(); //r is a smart-pointer
    r.foo();
}//resource released on scope exit.

I think implicitness is "worth it". Just as the implicitness of garbage collection is "worth it". Explicit using blocks are refreshing on the eyes, but offer no semantic advantage over try-finally-dispose().

Is it impractical to implement such a feature into the Java/C# languages? Could it be introduced without breaking old code?

Best Answer

Such a language extension would be significantly more complicated and invasive than you seem to think. You can't just add

if the life-time of a variable of a stack-bound type ends, call Dispose on the object it refers to

to the relevant section of the language spec and be done. I'll ignore the problem of temporary values (new Resource().doSomething()) which can be solved by slightly more general wording, this is not the most serious issue. For example, this code would be broken (and this sort of thing probably becomes impossible to do in general):

File openSavegame(string id) {
    string path = ... id ...;
    File f = new File(path);
    // do something, perhaps logging
    return f;
} // f goes out of scope, caller receives a closed file

Now you need user-defined copy constructors (or move constructors) and start invoking them everywhere. Not only does this carry performance implications, it also makes these things effectively value types, whereas almost all other objects are reference types. In Java's case, this is a radical deviation from how objects work. In C# less so (already has structs, but no user-defined copy constructors for them AFAIK), but it still makes these RAII objects more special. Alternatively, a limited version of linear types (cf. Rust) may also solve the problem, at the cost of prohibiting aliasing including parameter passing (unless you want to introduce even more complexity by adopting Rust-like borrowed references and a borrow checker).

It can be done technically, but you end up with a category of things which are very different from everything else in the language. This is almost always a bad idea, with consequences for implementers (more edge cases, more time/cost in every department) and users (more concepts to learn, more possibility of bugs). It's not worth the added convenience.

Related Topic