Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

61–70 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#61
post #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 t…

Curious why you're using fmt.Fprintf and not fmt.Errorf? Or is that a typo? And I think you're going to have problems with this pattern if you join a team using Go in an organisation. The `if err != nil` pattern is the norm, and everyone's used to it (and the regular cadence of Go code; "do the thing, check the error, do the thing, check the error" is very readable).

That was a typo, thanks for pointing it out. :)

Re: Gopher Wrangling: Effective error handling in Go

#62

Earlier quoted context omitted.

please don't do this it obfuscates the control flow, specifically the value that is actually returned early returns on errors are good, not bad edit you want func foo() error { x, err := bar() if err != nil { return fmt.Errorf("bar: %w", err) } if err := baz(x); err != nil { return fmt.Errorf("baz: %w", err) } if err := bat(); err != nil { return fmt.Errorf("bat: %w", err) } return nil }

I’m all for generating that. I don’t want it in source where rereading it wastes expensive developers’ time and mistakes become possible.

Copilot is pretty good at recognizing and generating the error check ; it will even propose error messages for you. Clearly you may want to change it to your specific case. So I don’t think dev time will be significantly slower. My experience has been positive. I think the go plugin also has some helpers for this pattern, but I don’t recall exactly how they work.

Re: Gopher Wrangling: Effective error handling in Go

#63
post #26

For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.

Kotlin is deliberately trying to stay closer to Java and more approachable than, say, Scala. It does have sum types and a generic Result, but the builtin special cases for nullability and exceptions are a little more ergonomic (and simplify interop with JVM APIs).

Re: Gopher Wrangling: Effective error handling in Go

#64

Earlier quoted context omitted.

Doesn't the producer know best whether the producer failed?

Does the caller care? By day I work with a team in a language that sees errors ride on the exception handling system. Staying within the original example, I see code like this all the time ( too often , even, but that's another topic for another day): try { file = getFile() } catch(/* ... */) { fileUnavailable() } Here, the assumption of getFile that the caller wanted an error was incorrect. A Result-using language w…

But in Rust you can do the same, I do this all the time:

  let _ = fs::mkdir_all() // Error ignored, Rust will not complain because you explicitly assigned to _

Or if the function returns something I need but I don't care about the error:

  let Ok(file) = get_file() else {
    file_unavailable();
    return
  }

  upload_file(file);
Or this:

  let file = get_file().unwrap_or_default()

Re: Gopher Wrangling: Effective error handling in Go

#65
post #62

Earlier quoted context omitted.

I’m all for generating that. I don’t want it in source where rereading it wastes expensive developers’ time and mistakes become possible.

Copilot is pretty good at recognizing and generating the error check ; it will even propose error messages for you. Clearly you may want to change it to your specific case. So I don’t think dev time will be significantly slower. My experience has been positive. I think the go plugin also has some helpers for this pattern, but I don’t recall exactly how they work.

[deleted]

Re: Gopher Wrangling: Effective error handling in Go

#66
post #26

For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.

Kotlin is deliberately trying to stay closer to Java and more approachable than, say, Scala. It does have sum types and a generic Result, but the builtin special cases for nullability and exceptions are a little more ergonomic (and simplify interop with JVM APIs).

I don't understand this. Kotlin has very deliberately made many choices that don't align with Java, that's the whole point. But more and more people are realizing that Kotlins error handling is a failed experiment and are adopting Arrow instead, as they should. There's nothing demanding about Option, Either, Try etc types, they're literally just objects no different to any other, and they enable you to write functions that actually only return what they say they do, unlike with exceptions. It is exceptions that are difficult and demanding lol. And the Result type isn't very good and not even meant to be used by users of the language.

Re: Gopher Wrangling: Effective error handling in Go

#67
post #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 t…

please don't do this it obfuscates the control flow, specifically the value that is actually returned early returns on errors are good, not bad edit you want func foo() error { x, err := bar() if err != nil { return fmt.Errorf("bar: %w", err) } if err := baz(x); err != nil { return fmt.Errorf("baz: %w", err) } if err := bat(); err != nil { return fmt.Errorf("bat: %w", err) } return nil }

[deleted]

Re: Gopher Wrangling: Effective error handling in Go

#68

Earlier quoted context omitted.

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

This seams silly and nitpicks to my. In go, you have to assign the error, and if you assign it you have to use the variable. I've never seen this mistake before in my years of using go.

Going to link my comment here: https://news.ycombinator.com/item?id=36398874

Re: Gopher Wrangling: Effective error handling in Go

#69
post #19

Earlier quoted context omitted.

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 Mona…

The Result approach believes that the producer knows what is best for the caller regardless of who the caller is. The Go approach believes that the producer shouldn't assume it knows the caller. I'm not sure one is better than the other, just different tradeoffs.

This doesn't really make much sense. The producer knows what it's returning. In the _vastly_ common case, it's either returning an error or a success object, but the Go type system is unable to represent that.

The caller trying to pretend that the success object is there isn't a freedom the caller gets in the current system, it's an artifact of the type system not being powerful enough to encode the situation accurately.

In practice (for the success object) it means you need to check for a nil pointer, make sure you don't use a zombie object, or just rely on an assumption that it's not nil, depending on which poor choice the producer function went for.

If you have a function that can return both an object and an error, there still should be a way to represent that (exactly the current way). Having Sum types would just allow a way to represent the common case accurately.

Re: Gopher Wrangling: Effective error handling in Go

#70
post #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 t…

Some other variants I've played with:

  func foo() (err error) {
    var x any
    if x, err = bar(); err != nil {
      goto fooError
    }
    if err = baz(x); err != nil {
      goto fooError
    }
    if err = bat(); err != nil {
      goto fooError
    }
    return

  fooError:
    return fmt.Errorf("%w doing foo ", err)
  }
Or:

  func foo() (err error) {
    defer func() {
      if err != nil {
        err = fmt.Errorf("%w doing foo ", err)
      }
    }()
    var x any
    if x, err = bar(); err != nil {
      return
    }
    if err = baz(x); err != nil {
      return
    }
    err = bat()
    return
  }
Seeking out different patterns is obviously most applicable in cases where error handling is actually doing something useful or more complicated than just wrapping the error.

(My comment was meant to spur first principles discussion from intellectually curious folks, not "nobody does it that way" or "don't do that" edicts. Much of the argument against adding additional language features for error handling is that many of them aren't any better than what can be accomplished already, using existing syntax but different code style conventions. The goto pattern in particular is found all over the stdlib.)

Post reply on HN