Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

One of Go's problems, relative to Rust, is that error values of functions can be ignored. In rushed corporate code, this means that developers will inevitably keep ignoring it, leading to brittle code that is not bulletproof at all. This is not an issue in Rust. As for static analyzers, their sane use in corporate culture is rare.


You can ignore errors in Rust too, just like any language. And people do, just like with any language.


In Rust you have to explicitly state that you're ignoring the error. There is no way to get the value of an Ok result without doing something to handle the error case, even if that just means panicking, you still have to do that explicitly.

In Go you can just ignore it and move on with the zeroed result value. Even the error the compiler gives with unused variables doesn't help since it's likely you've already used the err variable elsewhere in the function.


True, but running errcheck will catch cases where you accidentally ignore the error. Maybe not as good as having it built-in to the language like Rust, but the goal of error check safety is achieved either way.

And there's a few cases like Print() where errors are so commonly ignored you don't even want to use the "_" ignore syntax. Go gives you the poetic license to avoid spamming underscores everywhere. error linters can be configured to handle a variety of strictness. For non-critical software, it's OK to YOLO your Print(). For human-death-on-failure software you may enforce 100% handling, not even allowing explicit ignore "_" (ie become even stricter than Rust language default)


errcheck will not catch everything. For example, it will not stop you from using a value before checking the error, as long as you check the error later. I've personally broken things in production because of that corner case, despite (usually) being very careful about error handling.


No language or linter will be perfect, and IMO unit tests should be written for what the compiler doesn't do for you. In this case, your unit tests missed code paths, which should show up in a code coverage analysis or fuzz test.


Rich type systems like Rust's do completely prevent this type of mistake. You don't need unit tests to ensure that the error condition is handled somehow because we statically assert it within the type system of the language.


Handling an error is pointless if you don't handle it correctly, and for that you need unit tests anyway, so you still need to write the tests and once you've done that it is impossible to encounter the mistake without knowing it no matter how you slice it.

But your editor can give some more realtime feedback instead of waiting for your tests to run, so I guess that's cool.


You need to give it a try


Give improperly handling an error a try? I’m good. I prefer my software to function correctly.


In Rust it's literally impossible to use a returned value without checking the error. This bug also cannot happen with Java, C# or Javascript exceptions. This particular failure mode is unique to Go.


It's not unique - C has it, and error-code-flavored C++ can emulate it too. ;-)


I would think it's the job of the static analyzer to ignore errors for print. Unfortunately, the proper and consistent use of an appropriate static analyzer is not common in typical rushed corporate settings. Rust avoids this dilemma by enforcing it at the compiler level. There are numerous other types safety reasons too why Rust is safer for careless teams.


I really wouldn't object if some of the rules in the Go world currently living in the broad linter space were to be moved to the compiler. To a point Go's ecosystem has some reasonable defaults but it could be a bit stricter I think.


That's only the case if you consume the return value. It's perfectly legal (though it gives you a compiler warning) to call a function that returns a Result and never check what actually happened.


The question that's needed to ask is whether you'd like the language or its ecosystem to guard against these things, or whether you are a decent and disciplined developer.

For example, Go's language guards against unused variables or imports, they are a compiler error. Assigning an `err` variable but not using it is a compiler error. But ignoring the error by assigning it to the reserved underscore variable name is an explicit action by the developer, just like an empty `catch` block in Java/C# or the Rust equivalent.

That is, if you choose to ignore errors, there isn't a language that will stop you. Developers should take responsibility for their own choices, instead of shift blame to the language for making it possible.


> For example, Go's language guards against unused variables or imports, they are a compiler error. Assigning an `err` variable but not using it is a compiler error.

Unfortunately, Go's language design also enables unused variables without any error or warning. They are only sometimes a compiler error.

Specifically, multiple return interacts poorly with unused variable detection. See:

    func fallable() (int, error) {
       return 0, nil
    }

    func f1() {
       val, err := fallable()
       if err != nil { panic(err) }
       fmt.Println(val)
       val2, err := fallable()
       fmt.Println(val2)
       // notice how I didn't check 'err' this time? This compiles fine
    }
When you use `:=` it assigned a new variable, except when you do multiple return it re-assigns existing variables instead of shadowing them, and so the unused variable check considers them as having been used.

I've seen so many ignored errors from this poor design choice, so it really does happen in practice.


Whenever I see people appealing to developers to be "disciplined" I think about those factory owners protesting that they wouldn't need guards or safety rails if their workers were just more careful about where they walked.

If developers were more disciplined Go wouldn't need a garbage collector because everyone would just remember to call `free()` when they're done with their memory...


Rust genuinely will stop you, though. You can't take a Result<T> and get an Ok(T) out of it unless there's no error; if it's an Err then you can't continue as if you have a T.

It doesn't force you to do something productive with the error, but you can't act like it was what you wanted instead of an error.


Yeah, but now you have to teach your programmers about generics and applicative functors.

I'm just a simple country gopher, but my brain isn't capable of understanding either of those things, nor are any of my coworkers, nor any of the people we hire, and it doesn't really matter how theoretically "nice" and "pure" your generics and results and monads are if us real professional programmers get confused by them.

Errors need to be explicit values, not magic monads. 'if err != nil' is good and easy to think about, 'res.map().or_else()' is incomprehensible line noise no normal programmer can understand.

https://paulgraham.com/avg.html#:~:text=The%20Blub%20Paradox


I think you have ideas about these types that make them seem overcomplicated! Either that, or I'm way smarter than the average programmer and really am massively wasting my talent :) but if you ask me the first is much more likely. I'm awed by the clever things people post about on here and am still not 100% sure I understand what a monad even is, but Result and Option seemed very easy to pick up.

The one liner functor stuff in particular I think is a big distraction. To me the basic building block is the match statement - you match on a result, if it's Ok(T) then you get to do something with T, and if it's Err(e) then you get to do something with e. At that level it's very simple. And as far as I know (no expert though) any clever expression can be decomposed into a series of nested and possibly repetitive match statements, the apparent magic is illusory.

But I do hear you and I get the appeal of Go from that angle, obviously sticking with what you like is a good idea. Just saying Rust isn't that bad :)


However, you can make a call without assigning the result at all, entirely ignoring it. That isn’t possible in Rust.


In my view the core problem is the lack of proper sum types. Using product types to represent results seems fundamentally wrong; the vast majority of procedures only have 2 possible outcomes -- they fail or they don't -- 2 of the 4 outcomes that the type system permits are (almost always) nonsensical. I don't see a good reason to design a modern language in this way, it feels less like an intentional design choice and more like a hack.


I think the issue here is not so much that errors can be ignored in Go (sometimes one doesn't care if something worked, they just want to try), it's more that errors can easily be ignored by accident in Go.

I definitely have seen that, and sometimes have done that myself, but I have to say it hasn't happened in a while since the linting tools have improved


Rust values can be ignored as well.

There is no language to my knowledge that prevent you from ignoring values.


Any language that implements linear types has values that can't be ignored and need to be passed to some "destructor" (could just be a deconstruction pattern assignment/similar).

Examples: Austral, some of these https://en.m.wikipedia.org/wiki/Substructural_type_system#Pr... in particular at least ATS, Alms, Granule, LinearML Though most haven't gone beyond research languages yet.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: