5 ms·
I am delighted with the error handling in Go. Overall I would prefer to Go with the current features and not add more, maybe even deleting some inconsistencies
by bilinguliar 4y ago
I am delighted with the error handling in Go.
Overall I would prefer to Go with the current features and not add more, maybe even deleting some inconsistencies in Go v2.
- nerdwaller 4y agoWhat kinds of inconsistencies are on your mind?
- bilinguliar 4y agoI never use new(). It gives a way to allocate, that I would prefer to avoid in favor of declaring zero-value explicitly. Maybe I am missing the point of new().
- 37ef_ced3 4y agoIt's used like this p := new(int) since you can't say p := &int{}
- bilinguliar 4y agoIndeed, this may be the use case. I am curious how often you may need a pointer to an int.
- marwatk 4y agoIn things like rest APIs you need them quite a bit to distinguish between a value being the zero value and not present at all. Most libraries I've seen have an IntPointer or similar function exposed globally.
- bilinguliar 4y agoThis is true. In the case of REST, it is your “encoding/json” who will allocate for int pointer. So I do not see why new() is needed.
- 37ef_ced3 4y agoAre you seriously suggesting that the language should not have notation for allocating zeroed primitive types and receiving the address of the allocation? var ( p1 = new(int) p2 = new(*int) p3 = new(**int) p4 = new(complex64) p5 = new(complex128) ) I feel like you must be joking. Should we say var ( c complex128 p = &c ) every time?
- bilinguliar 4y agoIn five years, I needed to do the latter only once or twice because a library I was using demanded a pointer to a primitive. Forgive my arrogance, but why does one need a pointer to a zero-value primitive in Go? I sincerely believe there is a use case for it, but I never needed this.
- deleted 4y ago[deleted]
- nerdwaller 4y agoI don't particularly find that inconsistent, though it's usage may not be super common (depending on the type of work you do in go). If you ever need to do atomic operations, it's rather helpful. Though they have been adding new features in go 1.19+ that help there (Int32[1] type, for example). As an aside, for those using atomics - Uber's wrapper library[2] is pretty pleasant to use. [1] - https://pkg.go.dev/sync/atomic#Int32 https://pkg.go.dev/sync/atomic#Int32 [2] - https://pkg.go.dev/go.uber.org/atomic https://pkg.go.dev/go.uber.org/atomic
- closeparen 4y agoDo you write primarily HTTP / RPC services? I've found Go's error handling very tedious when "return an error to the caller" is almost always the answer, but for background stuff, daemons, etc. it's nice.
- bilinguliar 4y agoI write HTTP/RPC and some TCP servers, implementing specs to parse files and data processing pipelines based on PubSub, NATs, etc. Whenever the server fails to do what is needed - in most cases, it will retry with a backoff. In case it is not retryable - most often, it will exit, an alert will fire, and I will start handling an incident. Background jobs run in a goroutine and send a non-retryable error to the channel. go func() { errChan <- s.ProcessData(ctx) }() Followed by a blocking select: select { case err := <- errChan: log.Printf(“Server failed: %s”, err) case <- ctx.Done(): log.Println(“Shutting down”) } Something along these lines.
- closeparen 4y ago>Whenever the server fails to do what is needed - in most cases, it will retry with a backoff. In case it is not retryable - most often, it will exit, an alert will fire, and I will start handling an incident. I don't follow. Requests have deadlines, so you can't keep trying forever. Often a persistent failure means the request itself is bad. Either invalid, or exposing an edge case that the system can't currently handle. These kinds of errors are always present at background levels in a high-scalability system; we have SLAs like 99.99% success rate to decide when there's really an incident. Crashing the entire server process because of one failed request sounds crazy.
- bilinguliar 4y agoMy bad. I misread your comment. Please see the response one level up.
- bilinguliar 4y agoI have misread your comment. So, your experience with handling errors that must be returned to the caller is tedious. Hmm, I do not feel it is tedious. I will likely bubble it up the stack, report it and return a relevant code. I appreciate that this is almost linear, without mental complexity of exception, where you occupy another part of your brain with accounting for exception handling.
- rahen 4y agoLikewise. Go was engineered for simplicity mostly in reaction to the complexity of C++. As anticipated, now that we got generics, some developers keep asking for more and more features. Let's not turn Go into another C++ when its goal is to stay a straightforward, simple, modernized C.
- codegeek 4y agoI have been writing Go code as a hobby for 2 years now (built in house tool as well but nothing production critical yet). I have found the verbose error handling to be very easy to understand and I prefer it that way. Checking for if err != nil is better for me because I know exactly what to do with errors. Am I missing something ? I am also used to Try/Catch in PHP and many years ago, Java.
- kitkat_new 4y agoI find that interesting, and like the Rust way of error handling much more. Go error handling blows the code up a lot and makes the code less comprehensible. res1, err1 = canFail() if err1 != nil { return err1 } res2, err2 = canFail() if err2 != nil { return err2 } return res1 + res2, nil becomes res1 = canFail()? res2 = canFail()? return Ok(res1 + res2) guess what is more readable
- movedx 4y agoAs someone who's not a day-to-day developer/software engineer: the Go code is more readable. The Rust is interesting, for sure, but I don't know what "?" is doing. I like the use of the question mark as it makes the act of calling the function a question - did it work or not? But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit. Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? (What happens if I do that and there's an error? This isn't explicit enough.) Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go. And of course, Go gives you an error object you can work with (something I'm sure Rust does too, but it's not obvious to me as a none Rust developer.) I prefer my languages to be statically typed, statically linked, and very explicit in their syntax. It results in a bit of extra work up front for compile and run time safeties, and the ability to easily re-read the code at a later date.
- wtetzner 4y agoI’m not sure that optimizing readability for someone unfamiliar with the language is the right tradeoff. Rust code uses a Result<T, E> type for error handling: it can either be Ok(T) or Err(E), never both. The question mark operator tries to extract the value wrapped in Ok. If the Result is Err and not Ok, it returns the error. It’s not “hidden magic”, it’s a well-defined operator. > I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? If you don’t unwrap the value from the result, then you still have a Result<T, E> instead of a T, and will get a type error.