If it's possible for the call to fail to return an object, you declare its type as optional (add a ? to it) and then the calling code has to explicitly "unwrap" the return value to get to the actual object - they can't simply go
var pid=fork()
pid.kill()
as that would be a compiler error. Instead, they need to go
pid!.kill()
The idea is that they should check for the pid object being nil before doing the unwrapping. Of course, it's still possible for the coder to ignore that (just as it's possible, and depressingly common, for coders to catch and then ignore exceptions), but that's going to be a conscious decision because the compiler is telling them that there's a possible error condition here.
Exceptions disrupt the program flow at any place, including constructors and destructors.
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. Finally clauses are technically enough, but each allocation needs the same level of attention non-memory resources (e.g. connections, files) get on other languages.
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.
"It is idiomatic C++ that a destructor should not throw;...which is practically guaranteed to be exception-free..."
"Should not", "practically". Your confidence is overwhelming. :-)
Exception safety in C++ may not be quite as much of a black art as it once was (say, before std::unique_ptr), but it is still something the programmer has to do, actively.
"If releasing a resource cannot fail, destructors (and finally clauses in languages lacking RAII-style resource management) cannot fail."
Yupper.
I'm probably unqualified to have an opinion on this, but I believe that the entire hatred for checked exceptions in Java comes from that general piece of idiocy and specifically from JDBC's urge to possibly throw a SQLException from close(). (Just what the hell is anyone supposed to do with that?)
For a long time, Java suffered from issues when throwing an exception during stack unwind. The second exception is the one that is subsequently propagated, and in modern Java the original exception is available and printed in any stack trace.
It's still not particularly pleasant, but it is at least survivable and no information is lost.
And what if $programmer forgets to check what's in err? What would pid contain in that case?
I mention this because I guess you quoted a kind of syntax that matches the one from Go.
So then I'm guessing that Go would simply ignore the error in this case.
However, having a proper exception mechanism, if you don't catch the problem, then it bubbles up, and the program doesn't continue with wrong data (which is a good thing => fail fast!).
Or a general "Choice" sum, perhaps using phantom types so int<err> isn't compatible with int<pid>. But then all of a sudden, instead of a single word being returned, a tag and possibly variably-sized result has to be returned, and that's quite a hassle which doesn't fit well with C.
A "Choice" (Either in Haskell, Result in Rust) wouldn't work for fork() as it can have 3 results, and you'd want the `Child` case cleanly and easily separated from `Pid`.
I think the parent meant only a sum type, not a concrete example of it such as Either of Haskell, which would surely not suffice here. In Haskell you would probably define a new sum type for this occasion, e.g.:
Actually if you want to not handle an error, you have to do either _ = err or just go data, _ = doStuff(), both of which are very visible. You can basically scan your codebase for _'s and find all the unhanded exceptions. If you don't do something with a variable, eg I do myInt := 1 but I don't use myInt go simply refuses to compile.
Technically true but most of the [admittededly modest] Go code I've seen had that error punt all over the place. I really wish they'd learned from C and either banned assigning _ for errors or threw a compiler error if the next line wasn't a _ check.
On second thought, it'd probably avoid some nasty production failures if that behaviour was true for everything which can return an error.