Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

11–20 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#11

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs):

      let greeting_file_result = File::open("hello.txt");

      let greeting_file = match greeting_file_result {
          Ok(file) => file,
          Err(error) => // handle err
      };
It isn't much different from:

      file, err := os.Open("hello.txt")
      if err != nil {
          // handle error
      }
I've come to appreciate Go's simple solution as you get pretty much 90% of what you want from an operation that might produce an error (either the value or an error), and the flow control aspect of it is more explicit than with exceptions.

Maybe I don't get Monads, but it seems pretty much equivalent for the common use case.

Re: Gopher Wrangling: Effective error handling in Go

#12
post #11

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: fil…

The killer feature of Rust's Result isn't actually the monad itself, it's the ? operator. Being able to concisely say "if there's an error, bail out by returning it" gets you pretty close to exception-level convenience with just a bit more explicit syntax showing where an error might come from.

Re: Gopher Wrangling: Effective error handling in Go

#13

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

> F#, Swift and Rust have a perfect error handling mechanism. Rust's is good but not perfect. I often find myself missing stack traces (there are solutions but they're not easy to use), and you're still constrained to a single type of error per function, which means you see a proliferation of specialized error types that are mutually incompatible and have to be converted back and forth.

Interesting. That’s fair. To be completely honest, out of the 3 I listed, I’m only familiar with putting an F# service into production. We’re a very heavy C# shop (legacy Java shop, but anything since 2015 has been in C#) and I thought I could slip in an F# project in there. And I was able to until I needed to hand it over to another team that wrote wrappers to all the F# code in C# and did all new development in C#. Swift and Rust error handling reminded me of how nice error handing felt in F#. But I only did very simple toy projects in them.

Re: Gopher Wrangling: Effective error handling in Go

#14

"Always handle errors" sounds good until you remember that every read or write can potentially fail. My go programs are littered with unchecked fmt.Printf or Println statements.

That seems like the kind of thing you'd want to wrap and panic on rather than ignore.

If fmt.Print doesn't work, you should probably just kill the process.

Re: Gopher Wrangling: Effective error handling in Go

#15
post #11

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: fil…

Rust's error handling and option types actually aren't monads, they just have similar ergonomics for end users. There's some tricks that have to be done to make them work in a eager evaluation context, and as a result implementing iterator combinators does not feel like working with modads.

Re: Gopher Wrangling: Effective error handling in Go

#16

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

> F#, Swift and Rust have a perfect error handling mechanism. Rust's is good but not perfect. I often find myself missing stack traces (there are solutions but they're not easy to use), and you're still constrained to a single type of error per function, which means you see a proliferation of specialized error types that are mutually incompatible and have to be converted back and forth.

> you're still constrained to a single type of error per function

This is true, but the ? operator expands into a form that does `.into()` conversions of the error variants. If there's a implementation of `From`/`Into` between the error type you're unwrapping and the error type on the function, it automatically converts. This is aided by the "thiserror" crate which provides a derive macro that can generate these automatically.

Re: Gopher Wrangling: Effective error handling in Go

#17
I've mostly evolved to making err a named return parameter, and inverting the err != nil check. For example:

  func foo() (err error) {
    var x any
    if x, err = bar(); err == nil {
      err = baz(x)
    }
    if err == nil {
      err = bat()
    }
    if err != nil {
      err = fmt.Errorf("%w doing foo ", err)
    }
    return
  }
This feels somewhat cleaner to me, in particular by combining error handling (in this case just a simple wrap) in a single place at the end of the function.

Re: Gopher Wrangling: Effective error handling in Go

#18

For #3 you can also use errgroup from sync/errgroup. It's a nice recent addition to the stdlib. For #4 wrapping your errors creates pretty and logical error messages for free. It should be done in most cases.

It looks like sync/errgroup is a proposal, but not in the standard library (not yet, at any rate): https://github.com/golang/go/issues/57534

Re: Gopher Wrangling: Effective error handling in Go

#19
post #11

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: fil…

Result is better because it actually encodes the correct situation. You either get a file or an error. Not neither, not both.

Go's encodes instead "you may or may not have a file" and "you may or may not have an error". Not the same thing, and extremely rarely what you want, IME.

Other languages also do a better job of helping you verify that you actually handled both cases too.

By the way I wouldn't say we need Monad here, I'd be happy if Go could at least encode Sum types.

Re: Gopher Wrangling: Effective error handling in Go

#20
post #11

There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grass…

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: fil…

The biggest difference is in rust you have to handle the error case, but in go you can accidentally ignore it.
Post reply on HN