Asked on Reddit: Which of Rust, D, Go, Nim, and Crystal is the strongest and why?
Idan Arye via Digitalmars-d
digitalmars-d at puremagic.com
Thu Jun 11 17:50:56 PDT 2015
On Thursday, 11 June 2015 at 21:57:36 UTC, Dave wrote:
> In regards to being faster, I'm not a big fan of exceptions in
> the first place. This probably explains my perspective on them,
> but I am familiar with their typical use case. And it's to
> communicate errors. I'd much prefer something like what Andrei
> presented during one of his C++ talks (Expected<T>). If I were
> to
> design a language today, I might try to incorporate that somehow
> with some semantic sugar and a "handle by default" mentality.
I've reordered your post a bit so I can refer to this part first.
I think you and I refer to different things when we talk about
returned errors, and with the definition I think you have in mind
I do see how my arguments can look like the mumbling of a madman.
So I'm going to clear it out first.
At a previous post
here(http://forum.dlang.org/post/riiuqazmqfyftppmxxgz@forum.dlang.org),
I've said that "I'm not talking about C style
return-error-code-and-write-the-actual-result-into-a-variable-passed-by-reference
- functional languages like Haskell, Scala and Rust have shown
that monadic sum types of result/error are both safe(...) and
easy to use(...)"
What I refer by "monadic sum types of result/error" is pretty
similar in concept to Expected<T>, though what I in mind is much
closer to what functional languages have, which makes allows both
easier handling inside expressions and a guarantee that the
underlying result will only be used in a path where it was
checked that there is no error or after the user explicitly said
it's OK to convert it to an exception.
This is what's done in Rust(except it's converted to panics,
which are harder to log and contain than exceptions), it can be
done in D, and of course a new language that chooses this
approach can have syntax for it. Here is an example for how it
would be used in D:
Expected!int parseInt(string txt) {
if (/*parsing successful*/) {
return expected(parsedValue);
} else {
return error();
}
}
// Propogation - functional style
Expected!string formatNextValueText(string origNumber) {
return parseInt(origNumber).ifOK!(
// good path
parsedValue => "After %s comes %s".format(
parsedValue, parsedValue + 1).expected,
// error path
() => error());
}
// Propogation - imperative style
Expected!string formatNextValueText(string origNumber) {
auto parsedValue = parseInt(origNumber);
if (auto parsedValuePtr = parsedValue.getIfOK()) {
return "After %s comes %s".format(
*parsedValuePtr, *parsedValuePtr + 1).expected,
} else {
return error();
}
}
// Convertion to exception
string formatNextValueText(string origNumber) {
// This will throw an exception if the parsing, and
// return the parsed value if it succeeded:
auto parsedValue = parseInt(origNumber).enforceOK();
return "After %s comes %s".format(
*parsedValue, *parsedValue + 1);
}
Now to answer the rest of your post, which actually came first:
>> He is saying that now anything that throws will not only be
>> slow but also have the same limitations as returned errors.
>
> "nothrow by default is combining the slowness of exceptions with
> the limitness of returned errors."
>
> He literally said combine the slowness of exceptions. So I don't
> know how to read that other than he said it's slow. But perhaps
> I
> am just misunderstanding his wording, so perhaps it's best I
> just
> assume I misunderstood him.
I also literally said "with the limitness of returned errors.".
That part is important part of the sentence. My point is that the
exception mechanism is much less limited from the returned value
mechanism, because it lets you handler the error in a higher
level without modifying the middle levels to acknowledge it. The
price for this is slowness.
The benefits of nothrow by default come naturally with returned
errors - the users can't implicitly ignore them, and since the
possible errors are encoded into the return type any tool that
can display the return type can show you these errors.
With nothrow by default, you are paying the performance price of
an exception mechanism, and then do extra work to add to it the
limitations of returned errors, just so you can get the benefits
that come naturally with returned errors. Wouldn't it be better
to just use returned errors in the first place?
>> but also have the same limitations as returned errors
>
> That is a legitimate concern, but I don't think it is correct.
> The transitive nature would enforce that you at least handle it
> at some point along the chain. Nothing would force you to handle
> it right away. Although I think in most cases it's far better to
> do it when the error occurs(but this is my style). But when you
> don't there would be at least a flag saying "this might fail"
> that you and others could see. You can ignore that all the way
> up
> the turtles, but at some point you are going to be like "I
> should
> handle these errors".
>
>> If you have to acknowledge it anyways, then might as well just
>> use returned errors because they are faster.
>
> I guess you could think of nothrow as an acknowledgement. I'd
> view it more like a warning to others. As I said before, I don't
> think you should be *forced* to do anything. Handle it right
> away
> and you don't have to mark your own function as 'throw'. Don't
> handle it, then your function should be marked 'throw', and the
> compiler can help others out and tell them to expect to have to
> handle the exception at some point.
This is not very different than returned errors: you either
handle the error or change the function's signature so it can
pass the error to it's caller.
The big benefit of unchecked exceptions is that they require
minimal effort to use - when I code and encounter a path that you
can't handle, I just throw an exception(I can also use helper
assert/enforce functions). I don't need to interrupt the flow and
start adding `throws` all over the project, making my brain do a
"context switch" that makes me forget the immediate thing I was
working on, and increasing the chances of my commit conflicting
with other concurrent commits(because it was touching general
functions all over the place). No - I throw the exception, and
leave worrying to how it will be handled to later.
The alternative is not that I leave my current task to implement
something that'll handle the exception at the right place - the
alternative is that I continue my current task without throwing
the exception, and only deal with it later on when something
crashes or doesn't work properly.
Why? Because I have a task to do, and if every time I encounter a
place that *might* indicate an error I'll need to make sure that
error is handled, I won't be able to focus on the actual task.
Out of the unhandled error possibilities, exceptions are easiest
to fix when they do happen. Forbidding unchecked exceptions
doesn't mean there are no errors - just that you don't have this
amazing mechanism for tracking and debugging them.
More information about the Digitalmars-d
mailing list