6 ms·
> I compiled the code and … no error message. Everything went fine. But?! I just added a field to a struct, the compiler should say that my code is not good any
by dev_dull 7y ago
> I compiled the code and … no error message. Everything went fine. But?! I just added a field to a struct, the compiler should say that my code is not good anymore because I’m not initializing the value where it should be!
This is your big hangup with Go? You added a new field, didn’t use that field anywhere, and the compiler didn’t complain? I’ve heard a lot of valid criticisms of the language but this is a new one for me.
- whateveracct 7y agoIt's a pretty big annoyance with Go though. I hate that all struct fields are optional.
- mdanger007 7y agothere are times when a nil struct field is useful, like for trees and json. very easy to require struct fields by using a constructor
- Nullabillity 7y agoAbsolutely, and Option<T>s are great for modelling that. It just shouldn't be the default.
- whateveracct 7y agoIf it's useful, then the person creating the struct can explicitly set it to nil.
- arendtio 7y agoFWIW, I never had that problem, but I also tend to use constructors for my structs.
- whateveracct 7y agoWhen you use constructors you lose named arguments though. One use-case where this is really painful: I like to encode sum types in Go using an interface with a private dummy method + structs that implement that. I then have a function "match" that takes one function per struct. It's nice to be able to pass these functions as a record since it gets ugly, but due to Go allowing omitted functions to be nil, if I add a case to the sum, I open myself up to NPEs everywhere. If Go had a warning for missing struct fields, it would tell me all the places I'd need to handle a new case.
- AlexCoventry 7y agoIsn't this something you could catch with a linter, if you wanted to?
- whateveracct 7y agoDefinitely but I'm not sure if there's a linter for this. If so, I'd love to use it.
- fauigerzigerk 7y agoI think there is a wider issue. Go simply does not provide a way to restrict the state of public struct fields. Enforcing the initialization of all fields achieves relatively little given that public fields could easily be modified elsewhere. Requiring initialization to anything more specific than an automatic zero value is only useful in languages that have read-only fields. So the only way to handle this in Go is to make struct fields private or accept that all combinations of public field values are valid (including the zero value) and deal with it at the point of use (or rather at all points of use).
- deleted 7y ago[deleted]
- apta 7y ago> I’ve heard a lot of valid criticisms of the language but this is a new one for me. We've had bugs in prod because of this "feature". It's another bad design decision in golang.
- estebarb 7y agoActually is pretty good. The world have a lot of bugs in prod because C doesn't zero newly allocated memory (heap or stack). There are APIs with autogenerated structs that span a lot of fields (even thousands)... Imagine having to initialize each field when you just want... the default zero value...
- steveklabnik 7y agoThere are more options in the design space than those two extremes. Rust does not do either of these two things, for example. It will not let you use something you haven’t initialized, but it also doesn’t force you to declare a default value for each type. If a default value makes sense, you can make one, but you still have to explicitly ask for a default, rather than it implicitly happening. I think each of these three languages have made the correct choice, given their design ideals.
- apta 7y agoI appreciate your work on Rust, I find it to be a very interesting language. > I think each of these three languages have made the correct choice, given their design ideals. I feel that's kind of side-stepping the issue though. The approach that golang took could match their design goals, but it doesn't mean that those goals aren't a bad idea in the first place.
- tptacek 7y agoFor any set of correctness-improving primitives in a language, you can practically always come up with some disjoint set of additional primitives, and then make the argument that the lack of those additional primitives constitute "bad design". Sometimes you'll even be right (it's not clear to me in this case). But it will almost always be a boring argument.
- sytelus 7y agoI wouldn't have left Go just because of these but before you write this off as minor thing, I would suggest you look this as one of the core language design decision. It reveals two important language philosophies: 1. Go doesn't follow "you pay for what you use" model. 2. Go isn't too deeply invested in compilation as means for catching errors. Above are not binary language decisions. If you go in one direction strongly then there are consequences in other. Therefore all languages chose some balance point (aka compromises). Go has chosen a balance point that is weaker than Rust (and may be even C++) but stronger than Python.
- 0815test 7y ago> Go doesn't follow "you pay for what you use" model. To be fair, isn't that implied since Go uses a GC? The "pay for what you use" model is only really viable for languages that don't rely on a fixed GC runtime. (You could have "pluggable" GC libraries like the Boehm collector for C/C++, but those imply very different tradeoffs.)
- stcredzero 7y agoGo doesn't follow "you pay for what you use" model. It's not "you" in the traditional formulation. It's "CPU." You, the programmers, often have to pay continuously for "you pay for what you use."
- rhinoceraptor 7y ago> 2. Go isn't too deeply invested in compilation as means for catching errors. Except when you've imported a package you're not currently using, that is a mortal sin :)
- jimbo1qaz 7y agoI believe Go is weaker than Python. Python dataclasses/attrs (effectively structs) raise an error instead of silently doing the wrong thing, if you omit a constructor keyword parameter. You can still supply default values, which makes the corresponding constructor parameters optional. Keep in mind that dataclass defaults suffer from the same "mutable default arguments" issue as function parameters.
- jerf 7y agoGo has two mechanisms for initializing structs, name based and position based. type Sample struct { A int B int } can be initialized as Sample{A: 1, B: 2}, or Sample{1, 2}. The name-based method allows arbitrary elision of fields, which will be initialized to their zero value. This includes Sample{}, which will be interpreted as a name-based initialization that specified no fields. The position-based method requires all fields to be specified, in order, with the correct type, or it's a compile-time fail. So if the author of the article could have gotten the latter behavior with a slight syntax tweak. This would not necessarily entirely satisfy, though, since you can't do any mix-and-match, and in particular, it can be nice to have names on the larger structs even if you want them to be fully initialized, but there is no in-between. But, nevertheless, if you are willing to pay for the behavior that the compiler will complain on any type-changes to the struct, including growing or shrinking, Go has that. It seems to be the Go community's "best practice", above my objections, to claim that all struct initializations MUST use the name-based initialization and that positional-based inits is a mistake, on the theory that if structs change and add fields it's important for all uses of structs to continue compiling without changes. If the author's introduction to Go came from a tutorial from someone who believed that, they could easily have picked that up mistakenly as a characteristic of the language itself. My objection is that there's a time and a place for each behavior, and there have been plenty of times I've been grateful for the compiler pointing out every place I need to change my struct to include a new member because I could tell it was a "tuple-like" struct that should always be initialized with all the fields. You can argue with the putative "best practice" or agree with it, but fortunately it doesn't really matter because you can do the one you want regardless of what the community thinks. There is no chance either method is ever going to be removed.
- rifung 7y agoThe problem with using the positional method is that 1) It's much more difficult to read since you have to know the position of elements of the struct 2) You swap one compile time error for another: in the case you add and remove a field of the same type, it's entirely possible the program will compile fine if you use the positional initialization instead of the named one. #2 makes me think that the author would still be unsatisfied
- TACIXAT 7y agoYes, this would be pretty easy to solve with an initialization function and using that everywhere. Change the arguments in the function prototype and the compiler will definitely complain!
- phkahler 7y agoI was confused too. At the end they say they'd rather do python than Go. And yet it was going so well until this issue which is not any better in Python.
- saghm 7y agoThe criticism is that a struct literal allows you to not specify some (or all!) of the field. The argument is that this is brittle since, as with what was described in the post, if you add a new field, you might forget to use the field in all instances of the struct being constructed.
- eridius 7y agoYou're focusing on the wrong detail. The lack of compiler message isn't the actual problem, it's just a symptom of the fact that in Go, everything has an implicit zero value, and this leads to bugs. Hell, just the other day I complained to my backend team that the PubSub event was passing null for a non-optional array, and it was traced to the Go code declaring a map variable without ever initializing it. The compiler never caught that, it wasn't until it hit my Swift code that it became apparent.