Live data from Hacker News

Declined Proposal: A built-in Go error check function, “try”

github.com

361–370 of 425 posts

Re: Declined Proposal: A built-in Go error check function, “try”

#361
post #309

Earlier quoted context omitted.

> `if err != nil { return err }` is actually bad Go code, and not often written by good Go programmers. Like the people who wrote the Go stdlib? Because that's filled with those - just look at the net/* packages.

Legitimate question: then what does good Go code that is written by good Go programmers do/look like? Wrap `err` in `errors.New("some function failed", err)`?

At a minimum, an error should be logged with appropriate context and execution allowed to continue; or annotated and returned. Error annotation is currently best achieved with pkg/errors as e.g. `errors.Wrap(err, "error doing thing")`. (The xerrors suggestion of `fmt.Errorf("error doing thing: %w", err)` is awkward and hacky.)

Many programs can benefit from a more structured approach to error management. But once you get past the minimum (above) there's no one-size solution for what "a more structured approach" looks like. I really enjoy how upspin.io does their errors package, though it is somewhat esoteric. I'm also reading and generally liking how Cockroach does things, though I don't like the coupling to Protobufs.

Re: Declined Proposal: A built-in Go error check function, “try”

#362

Earlier quoted context omitted.

I really dislike method chaining. I'd much rather have 5 lines than 5 chained methods. If that's too much to read, you can always encapsulate it in a well-named function.

But five functions that return a value and an error would each have to run the if err != nil dance whereas with method chaining it's cleaner

not if each one can fail. What if call #1 and call #3 can return the same error.. how does the caller know which one failed? This is the same as wrapping a bunch of calls with a catch (Exception) ... you lose context of what failed and can't behave differently for different failures. All you can do is perform a generic "something went wrong" behavior.

Re: Declined Proposal: A built-in Go error check function, “try”

#363

Earlier quoted context omitted.

Go the language is surprisingly complex and error prone compared to other GC languages because of its decision to allow explicit references. This adds an extra layer of semantics to nearly every aspect of the language. For example, the semantics of the for-over-collection statement in Java are that it repeats the block of code, with the loop variable holding each value of the collection in turn. In Go, the equivalent…

You largely missed the point of my post. I was showing that Go values simplicity and consistency. The examples I used to illustrate that were exactly that: examples. They were not a list of features that were novel to Go, as you seem to have interpreted. > Go the language is surprisingly complex and error prone compared to other GC languages because of its decision to allow explicit references. It's true that having…

> It's true that having value types in addition to reference types (while other GC languages often only have reference types) adds some complexity to the language, it's not much and it's still much less complex and error prone than other GC languages. Also, C# has value types and Java desperately wants them, so I think it's pretty clear that they're worth the extra bit of complexity.

My point was not about value types, but about taking the address of some thing; neither C# (except the extremely rarely used `unsafe` subset) nor planned versions of Java have this ability. To be fair though, C# does have something somewhat equivalent - lambdas may capture a local variable such as a loop variable, in which case you do need to know if each iteration creates a new variable or changes the value of the same variable. Java doesn't allow this at all. Even in C#, you can't capture a part of a structure, so the complexity is more limited.

There are other aspects to Go's complexity as a language - multiple syntaxes for declaring a variable, multiple other ways of declaring constants, iota, named return variables, function scope VS local scope, the limits of what can constitute a map key, what kind of structs can be compared for equality,special syntax that looks like multiple assignment but isn't, and probably others. My point is that Go is not a very simple or consistent language. Java for example is still simpler. C# is more complex, but more consistent.

> I don't see why every language should have a build tool that can build it + small bits of other languages. A build tool should build that language well, and extend it for bigger projects using a wrapper tool like Make or Bazel depending on use case. Unix philosophy and all that.

The reason is tracking dependencies. If I have a pure Go project, I can rely entirely on modules to manage my dependencies. If I need one python package, I now need to find a new tool to declare my dependency and orchestrate things. With Maven, for example, I can just export the python package as a Maven module and keep using Maven for my entire build.

To me, a dependency management system that is strictly language specific is only useful as a starter tool - almost certainly, once you are working on a real project, you will drop that tool entirely and have to use soemthing else, as dependencies are a cross-language problem.

That said, it is nice to have a dependency tool available as you're getting your feet wet, so I shouldn't really be complaining that Go offers this.

> Every language (even C++) needs another compiler to compile C code before it can be called into. And once it's compiled, it's no more difficult to call into it from Go than from Java or C# or Python or etc.

First of all, I must admit that I was wrong about how Go handles CFFI. I was under the wrong impression that cgo is an alternative to the standard go compiler, and that you have to build your program with cgo instead of go build in order to be able to link to C code. Since I now understand that cgo is simply a tool to generate the necessary boilerplate to interoperate C and Go, you're right, it's actually much nicer than what Java or Pyhton offer. Note, I am aware that you need a C compiler to build your C code; I just thought that there are 2 Go compilers, one for pure Go, and a different one for Go plus dynamic linking to C.

C# is still simpler, since it doesn't need any external tool or C compiler - you simply declare the C# headers for the C functions you want to invoke, and annotate them to declare the shared library they should be searched in, and the CLR handles the glue for you.

---

There were a few other points, like gofmt where we simply have different experiences and I don't think it's productive to argue. There are others where I had misunderstood you as claiming Go is especially good at, and I understand your point was simply that it checks those boxes, which I agree with.

Re: Declined Proposal: A built-in Go error check function, “try”

#364

Earlier quoted context omitted.

Java also makes you handle every possible error condition, unless of course you chose to use an escape hatch. Rust allows the same. By the way, Go is much happier to crash than Java - for example, a simple array index out of range will cause a program crash in a typical Go program, where it would only cause a request failure in a typical Java program. Not sure how Rust handles this. Finally, choose that isn't tested…

Rust's use of Result is very different from try/catch and exceptions in Java, even if you opt-in to checked exceptions. The big difference is ergonomics and what patterns are used in underlying libraries - opting out of the idiomatic way in Rust feels wrong if you try doing it. Rust handles your out of range scenario the same way Go does. If any of this matters to you, the good news is that Kotlin's sealed classes (a…

So how did Rust handle the case of a function that may return an error or nothing? Can you forget to check the error, or does it force you to explicitly handle it in some way?

I understand how Result forces you to handle the possibility of an error when you want to access the actual return value, but I don't know what happens if you aren't planning on accessing the return.

I'm also curious how Result-based error handling composes. For example, if I want to sort a list of structs where my comparison function may fail, can I use the built-in sort function, and still get any error that may have occurred back?

With (unchecked) exceptions, this is trivially easy - the sort() function doesn't need to be aware of exceptions in order for them to be propagated through it. With checked exceptions in Java, you need to go through a little dance of wrapping and unwrapping, but it can still be done. If I understand correctly, in Haskell this can be done with the Either monad and liftM, though I can't claim to understand the specifics.

Is there a Rust solution?

Re: Declined Proposal: A built-in Go error check function, “try”

#365

Earlier quoted context omitted.

Rust's use of Result is very different from try/catch and exceptions in Java, even if you opt-in to checked exceptions. The big difference is ergonomics and what patterns are used in underlying libraries - opting out of the idiomatic way in Rust feels wrong if you try doing it. Rust handles your out of range scenario the same way Go does. If any of this matters to you, the good news is that Kotlin's sealed classes (a…

So how did Rust handle the case of a function that may return an error or nothing? Can you forget to check the error, or does it force you to explicitly handle it in some way? I understand how Result forces you to handle the possibility of an error when you want to access the actual return value, but I don't know what happens if you aren't planning on accessing the return. I'm also curious how Result-based error hand…

You get a warning if you don’t check the error.

Yes, the default semantics is that it will stop when the first comparison fails and give you that error. You can write slightly different codebase if you want a list of all errors instead.

Re: Declined Proposal: A built-in Go error check function, “try”

#366

I suspected this was coming, and it's unfortunate. If Go had implemented try, then in a year everyone would be happy using it and the controversy would have died down. I've seen this happen before. Unfortunately, the community that has sprung up around Go is more or less opposed to new language features on principle.

The controversy may have died down, but the impact on code written would have been permanent. There's a lot of value to there being "one way to do things", even when it's not the best way from any particular point of view. Go holds this principle higher than most other languages and I think that should be either embraced, or one should look elsewhere - and I'm saying that as someone who "looked elsewhere".

Where did you look?

Re: Declined Proposal: A built-in Go error check function, “try”

#367
post #289

Earlier quoted context omitted.

One foundational principle of Go is that the sad path is at least as important, and maybe more important, than the happy path. The best Go programmers I know write the sad path of their programs first, and then backfill the happy-path logic. So: > you only need to write [error checking] when you actually have something meaningful to do. Although it's the subject of a lot of ridicule, `if err != nil { return err }` is…

> Errors in Go are, at a minimum, annotated with contextual information before being returned. What surprised me when I last wrote Go was that there was no out-of-the-box solution to adding a stack trace to the error.

Stack traces are, for me, too much noise, and not enough signal. I prefer reading annotations added (prefixed) by programmers deliberately. File and line information for the call stack leading to the error maybe provide value in the dev cycle (e.g. when fixing tests) but basically don't in logs in production.

This is all to say: I can understand why they aren't more naturally part of errors, and I think it also helps explain why they are part of panics.

And with all that said, I wouldn't object to making stack traces easier to add to errors, as long as it was opt-in.

Re: Declined Proposal: A built-in Go error check function, “try”

#368

Earlier quoted context omitted.

Packages extensively using reflect + interfaces together can be a bit of a pain to work through. :/

Absolutely. But those are clear code smells. Any org with competent code review would flag them and kill them before they proliferated.

Have you seen the internals of the Go http library? :)

Re: Declined Proposal: A built-in Go error check function, “try”

#369

Earlier quoted context omitted.

In some contexts, exceptions are just cleaner and easier. If I’m writing a web backend, my error handling is going to almost always be, “stop trying to do things and generate a 4xx/5xx response”. Throwing an exception from anywhere and then handling it at the top of the request handling does that without having to tediously carry errors all the way up the call stack.

In some contexts, exceptions are just cleaner and easier. Definitely. The question is, are the benefits in those contexts worth the cost of the potential abuses? As a codebase and company get larger and larger, the higher the probability that a problem will get into the codebase. There is an analogy here with C++ templates and with method_missing meta-programming. There are some contexts where they make things a lot…

While true, HTTP services in particular are a very, very common context.

Re: Declined Proposal: A built-in Go error check function, “try”

#370

Earlier quoted context omitted.

> Go doesn't even really force you to check your errors, it just makes it harder to accidentally not check them. Quite the opposite. You have to explicitly ignore exceptions, whereas it’s easy to accidentally miss an error in Go. For example, how many times have you seen this? defer f.Close() Close() returns an error; this code ignores it.

To be fair, reporting that cleanup also failed with a different error is inherently complicated, and so many people got the try-finally version wrong that Java finally added try-with-resources and Throwable#addSuppressed.

Definitely true. Mostly I just avoid using defer with Close, but I sometimes end up writing terrible stuff like:

    defer func() {
        err2 := f.Close()
        if err == nil {
            err = err2
        }
    }()
Not perfect, but better than nothing. I could use Wrapf to add information is err is already set. :-p
Post reply on HN