Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

As an experienced Go dev, this is literally not a problem.

Golang code has a rhythm: you do the thing, you check the error, you do the thing, you check the error. After a while it becomes automatic and easy to read, like any other syntax/formatting. You notice if the error isn't checked.

Yes, at first it's jarring. But to be honest, the jarring thing is because Go code checks the error every time it does something, not because of the actual "if err != nil" syntax.



Just because you can adapt to verbosity does not make it a good idea.

I've gotten used to Javas getter/setter spam, does that make it a good idea?

Moreover, don't you think that something like Rusts ? operator wouldn't be a perfect solution for handling the MOST common type of error handling, aka not handling it, just returning it up the stack?

  val, err := doAThing()
  if err != nil {
    return nil, err
  }
VERSUS

  val := doAThing()?


I personally have mixed feelings about this. I think a shortcut would be nice, but I also think that having a shortcut nudges people towards using short-circuit error handling logic simply because it is quicker to write, rather than really thinking case-by-case about what should happen when an error is returned. In production code it’s often more appropriate to log and then continue, or accumulate a list of errors, or… Go doesn’t syntactically privilege any of these error handling strategies, which I think is a good thing.


This. Golang's error handling forces you to think about what to do if there's an error Every Single Time. Sometimes `return err` is the right thing to do; but the fact that "return err" is just as "cluttered" as doing something else means there's no real reason to favor `return err` instead of something slightly more useful (such as wrapping the err; e.g., `return fmt.Errorf("Attempting to fob trondle %v: %w", trondle.id, err)`).

I'd be very surprised if, in Rust codebases, there's not an implicit bias against wrapping and towards using `?`, just to help keep things "clean"; which has implications not only for debugging, but also for situations where doing something more is required for correctness.


Well we are in a discussion thread about a language that does just that :)

I see two issues with the `?` operator:

1. Most Go code doesn't actually do

    return nil, err
but rather

    return nil, fmt.Errorf("opening file %s as user %s: %w", file, user, err)
that is, the error gets annotated with useful context.

What takes less effort to type, `?` or the annotated line above?

This could probably be solved by enforcing that a `?` be followed by an annotation:

  val := doAThing()?("opening file %s as user %s: %w", file, user, err)
...but I'm not sure we're gaining much at that point.

2. A question mark is a single character and therefore can be easy to miss whereas a three line if statement can't.

Moreover, because in practice Go code has enforced formatting, you can reliably find every return path from a function by visually scanning the beginning of each line for the return statement. A `?` may very well be hiding 70 columns to the right.


For the first point, there are two common patterns in rust:

1. Most often found in library code, the error types have the metadata embedded in them so they can nicely be bubbled up the stack. That's where you'll find `do_a_thing().map_err(|e| Error::FileOpenError { file, user, e })?`, or perhaps a whole `match` block.

2. In application code, where matching the actual error is not paramount, but getting good messages to an user is; solutions like anyhow are widely used, and allow to trivially add context to a result: `do_a_thing().context("opening file")?`. Or for formatted contexts (sadly too verbose for my taste): `do_a_thing().with_context(|| format!("opening file {file} as user {user}"))?`. This will automatically carry the whole context stack and print it when the error is stringified.

Overall, what I like about this approach is the common case is terse and short and does not hinder readability, and easily gives the option for more details.

As for the second point, what I like about _not_ easily seeing all return paths (which are a /\? away in vim anyways), is that special handling stands out way more when reading the file. When all of the sudden you have a match block on a result, you know it's important.


It might just be me, but I find both of those to be massively less readable. More terse is not the same as more readable (in fact, I find the reverse).

I'm a huge fan of keeping things simple; my experience has shown me that complex things have lots of obscure failure points, while simple things are generally more robust.


You always have the option of using a match block if you don't like those chained calls. But I do agree, it's a bit bolted on and kinda ugly.

> More terse is not the same as more readable (in fact, I find the reverse).

I generally agree, but I also find that "all explicit" also hinders readability because it tends to drown the nitty-gritty details. As always it's a matter of balance :) And I think that neither go nor rust are great in this matter as one is verbose and the other falls in the "keyword soup" with the chain call, the closure, and the format macro. I'm pretty sure something in between could be found.


Actually this is precisely same cadence as in good old C. As someone who writes lots of low-level code, I find Go's cadence very familiar and better than try-catch.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: