I'm going to answer for C++, since as far as I know it's the only major language with exceptions and RAII. Correct me if I'm misunderstanding your post.
> It's not easy to guarantee that you deallocate on destructors exactly the resources that were allocated at the constructor when both of them can stop their execution at any time.
I disagree; let's take this one case at time to keep it simple:
1. Destructors: within C++, if you're in a destructor, the object was fully constructed, and thus you know the exact set of resources requiring destruction. It is idiomatic C++ that a destructor should not throw; I'll discuss why below.
2. Constructors: these certainly can throw at any moment, as resource acquisition is often fraught with failures. That said, idiomatic C++ provides mechanisms (RAII, such as std::unique_ptr) to manage the partially constructed set of resources in a constructor, such that if something goes wrong, they will be automatically released by virtue of the variable going out of scope. Once you have the resource acquisition completed, you transfer ownership of the objects to the object you're constructing, which is practically guaranteed to be exception-free, since it's usually just moving a pointer under the hood.
> Finally clauses are technically enough
I don't really think you can both stand by the fact that destructors can throw at any moment and that finally clauses are enough, without making what amounts to an apples to oranges comparison. Take, for example, this function, where we assume releasing a resource can fail:
Foo() {
SomeResource resource;
// Assume the destruction of a SomeResource can fail.
// Other actions take place, some of which may raise/throw.
}
In this example, if the other actions throw an exception that causes Foo to itself abort, then SomeResource resource must be destructed. If we're assuming that destructor can also throw, we've now got two exceptions, and how do you handle two exceptions? (It's language dependent. Some discard an exception, some chain them, some, like C++, just terminate.)
If we translate this to using some sort of "finally" construct, say in a garbage collected language:
def foo():
resource = aquire_some_resource()
try:
# other actions that may raise/throw.
finally:
resource.release() # but we're assuming this can also raise/throw.
You still have the same problem at the resource.release(): up to two exceptions can occur at a given point in the program, and you then need to know what your language does in that situation.
The general gist of this is that if the "release" of some generic resource can fail, then you have to make harder decisions about what happens during a stack unwind due to some other error because now you have two errors. Do you ignore it? Log it? (can you log it?)
If releasing a resource cannot fail, destructors (and finally clauses in languages lacking RAII-style resource management) cannot fail.