3 ms·
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
by Cthulhu_ 2y ago
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.
- lexicality 2y agoWhenever 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...
- dontlaugh 2y agoHowever, you can make a call without assigning the result at all, entirely ignoring it. That isn’t possible in Rust.
- amenhotep 2y agoRust 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.
- TheDong 2y agoYeah, 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 https://paulgraham.com/avg.html#:~:text=The%20Blub%20Paradox
- amenhotep 2y agoI 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 :)
- TheDong 2y ago> 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.