Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

31–40 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#32
post #15
post #11

Earlier quoted context omitted.

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.

In what sense aren't they monads? They have a bind method ("and_then") and a return method (Which is just the variant for constructing the success case, i.e. "Ok" or "Some"). It's more idiomatic to use "map", but that's just a degenerate case of bind.

> There's some tricks that have to be done to make them work in a eager evaluation context...

Monads have nothing to do with laziness, though. In Haskell, IO actions are used with laziness and to work around purity, and IO actions form a monad. But that's just one instance of monad.

Re: Gopher Wrangling: Effective error handling in Go

#33
post #11

Earlier quoted context omitted.

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.

No compiler can force you to handle an error.

Rust might force you to access a value. Big. Fucking. Deal.

Re: Gopher Wrangling: Effective error handling in Go

#34
post #29
post #25

Earlier quoted context omitted.

This seems like a distinction without a difference. In reality, in code, I'm still doing something like `if (!result.failed) { do the thing } else { do the other thing }` Like...this makes no difference to my ergonomics at all. In many cases it's arguably worse because now 1 token is potentially representing two very different types I want to deal with. The primary benefit to me seems like it's more to do with the ea…

In rust the type system enforces you check the error. In go, it doesn't. (Because rust error types are enums/sums and go error types are structs/products). This seems like a huge difference. > In many cases it's arguably worse because now 1 token is potentially representing two very different types I want to deal with. Yes, that's what an enum/sum type is? That's the whole point.

In rust it is not possible to use incorrectly, and in go it is, sure. But whether it is possible or not is only one dimension. Does it matter that it’s possible to misuse errors in Go if it virtually never happens?

I just don’t find the point about what is possible interesting. The other trade offs around readability, ergonomics, and so on seem more impactful.

Re: Gopher Wrangling: Effective error handling in Go

#35
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 Rust code you have isn't idiomatic. It's gar more likely to see:

    let greeting_file = File::open("hello.txt")?l
vs

    file, err := os.Open("hello.txt")
    if err != nil {
          return fmt.errorf("faild to open hello; %w, err)
    }
However, I would argue the distinction isn't just cosmetic. The compiler prevents me from not checking the error and blowing up the application with a nil pointer exception.

The TFA even capes this pattern:

    type Result[T any] struct {
        Value T
        Error error
    }
and I wonder if we will start to see it more now that generics are in Go.

Re: Gopher Wrangling: Effective error handling in Go

#36

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.

No compiler can force you to handle an error. Rust might force you to access a value. Big. Fucking. Deal.

The point isn't that it forces you to access the error. The point is that it prevents you from accessing an invalid return value.

Re: Gopher Wrangling: Effective error handling in Go

#37
post #11

Earlier quoted context omitted.

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.

you don't want exception-style "convenience", that's the whole point

you want to be able to read code and see a single control flow

? subverts that core requirement

Re: Gopher Wrangling: Effective error handling in Go

#38
post #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

Oh that's right. I imported it via golang.org/x/sync/errgroup

Re: Gopher Wrangling: Effective error handling in Go

#39
post #11

Earlier quoted context omitted.

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 Rust code you have isn't idiomatic. It's gar more likely to see: let greeting_file = File::open("hello.txt")?l vs file, err := os.Open("hello.txt") if err != nil { return fmt.errorf("faild to open hello; %w, err) } However, I would argue the distinction isn't just cosmetic. The compiler prevents me from not checking the error and blowing up the application with a nil pointer exception. The TFA even capes this pat…

Where is the error handling in your Rust code?

Re: Gopher Wrangling: Effective error handling in Go

#40
post #19
post #11

Earlier quoted context omitted.

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 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.

Post reply on HN