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.pid,err = fork()
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!).
This is the biggest downside of GoLang IMNSHO.
match fork() -> | Error(errno) -> ... | Pid(pid) -> ...
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.
data ForkResult = Failure | Parent Int | Childpid,_ = fork()
On second thought, it'd probably avoid some nasty production failures if that behaviour was true for everything which can return an error.
Erlang gets away with it because dynamically typed + pattern matching + uses MRV for tagged returns, the second property makes it work.
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.
> 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.
"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?)
It's still not particularly pleasant, but it is at least survivable and no information is lost.