Remix.run Logo
troupo a day ago

It's actually very rare that it should be the caller who has to handle the errors.

Go, however, forces you to spread your error handling over a thousand little pieces with zero overview or control of what's happening.

Rust eventually realised this and introduced try! and ? to simplify this

koito17 a day ago | parent | next [-]

More importantly, Rust has the notion of a result type and it is designed to be both generic and composable.

A problem I often face in Go and TypeScript code is code that ignores errors, often unintentionally. For instance, many uses of JSON.parse in TypeScript do not check for the SyntaxError that may be thrown. In Go, it is common to see patterns like

  _ := foo.Bar()
  // assume Bar() returns error
This pattern exists to tell the reader "I don't care if this method returns an error". It allows one to avoid returning an error, but it also stops the caller from ever being handle to the error.

Also, the position of the error matters. While the convention in the stdlib is to return errors as the final value, this isn't necessarily followed by third party code.

Similarly, errors are just an interface and there is no requirement to actually handle returned errors. Even if one wants to handle errors, it's quite awkward having to use errors.As or errors.Is to look into a (possibly wrapped) chain of errors.

The benefit of Rust's Result<T, E> is that

- position doesn't matter

- there is strong, static type checking

- the language provides operators like ? to effortlessly pass errors up the call stack, and

- the language provides pattern matching, so it's easy to exhaustively handle errors in a Result

The last two points are extremely important. It's what prevents boilerplate like

  if err != nil {
    return nil, err
  }
and it's what allows one to write type-safe code rather than guess whether errors.As() or errors.Is() should be used to handle a returned error.
DanielHB a day ago | parent | next [-]

I am pretty sure if it were for the Typescript creators they would not allow exceptions in the language, but they had to work within the confines of Javascript. Heck they even refused to make exceptions part of the type-system.

It is unfortunate that many of Typescript developers still rely on throwing exceptions around (even in their own typescript code). Result types are totally doable in Typescript and you can always wrap native calls to return result types.

quotemstr a day ago | parent | prev [-]

Why would you "check" for TypeError being thrown? Just let exceptions in general propagate until they reach one of the few places in the program that can log, display, crash, or otherwise handle an exception. No need to "check" anything at call sites.

90% of the criticism of exceptions I see comes from the bizarre and mistaken idea that every call needs to be wrapped in a try block and every possible error mentioned.

Yoric a day ago | parent | prev | next [-]

> Rust eventually realised this and introduced try! and ? to simplify this

That was prototyped around Rust 0.4, so I wouldn't say "eventually" :)

liotier a day ago | parent | prev [-]

Unsure if this is the right place to ask, but this conversation inspires me this question:

Is there in practice a significant difference between try/catch and Go's "if err" ? Both seem to achieve the same purpose, though try/catch can cover a whole bunch of logic rather than a single function. Is that the only difference ?

slau a day ago | parent | next [-]

Try/catch can bubble through multiple layers. You can decide/design where to handle the errors. If you don't `if err` in Golang, the error is skipped/forgotten, with no way to catch it higher up.

theshrike79 a day ago | parent | prev | next [-]

You can decide not to catch a thrown exception, it travels upwards automatically if you don't catch it.

I think that's the biggest difference.

With Go you need to specifically check the errors and intentionally decide what to do, do you handle it right there or do you bubble it upwards. If you do, what kind of context would the caller want from this piece of code, you can add that too.

stouset a day ago | parent | next [-]

> With Go you need to specifically check the errors and intentionally decide what to do, do you handle it right there or do you bubble it upwards.

Is this really all that interesting or worth the LOC spent on error handling when 99.9999% of the time in practice it’s just bubbled up?

And any “context” added is just string wrapping. Approximately nobody types golang errors in a way that lets you programmatically know what went wrong, to be able to fix it in-line.

I think I would be more empathetic to the arguments defending golang here if I’d ever worked or seen a project where people actually handled errors instead of spending 2/3 of their time writing code that just punts on any error.

LinXitoW a day ago | parent | prev [-]

I'd argue that at least checked exceptions also require a conscious decision from you. You either need to add the Exception type to your throws clause, or your catch clause.

Compared to Go, this is actually better because the type system can tell you what kind of errors to expect, instead of just "error".

Too a day ago | parent | prev [-]

“if err” doesn’t catch all types of errors. Some errors are colored different from others and instead cause the program to immediately crash, sorry panic. But don’t worry! it’s just very rare errors, like nil dereference and index out of bounds, that throw unrecoverable errors like this!