4 ms·
type T struct { Foo string Bar int Qux string } t := T{"example", 123} // doesn't compile t := T{Foo: "example", Bar: 123} // OK I f
by barosl 11y ago
type T struct {
Foo string
Bar int
Qux string
}
t := T{"example", 123} // doesn't compile
t := T{Foo: "example", Bar: 123} // OK
I find this behavior somewhat surprising. Contrary to the author's claim, I would expect both to fail to compile.
Reading the Go specification, it seems to be a valid behavior.
> An element list that contains keys does not need to have an element for each struct field. Omitted fields get the zero value for that field.[1]
I understand zeroing out the "unused" fields is common in low level programming, but Go's behavior in this specific case feels a bit too implicit for my taste.
[1] https://golang.org/ref/spec https://golang.org/ref/spec
- NeutronBoy 11y ago> Contrary to the author's claim, I would expect both to fail to compile. I wouldn't - the first example is undefined (is 123 Bar or Qux? I guess technically it should be Bar as without quotes it's an int... but it's not unusual for int's to be cast into strings on assignment) and I would expect it to fail. The second example explicitly states which variable is being assigned. edit: Thinking more about it, I guess it could go either way, but it's just one of those things that once you do it once you know it.
- barosl 11y agoI understand that in the latter case, what fields are being assigned is clearer (albeit the former case can also adopt a similar order-based strategy). What I disagree is the "zeroing out" part. I don't want 0 or empty string to be more "special" than ordinary values. Letting the compiler decide what values are suitable if I omit them can be a cause of potential bug.
- Vendan 11y agoExcept that the spec states that the 0 value is "more special". https://golang.org/ref/spec#The_zero_value https://golang.org/ref/spec#The_zero_value I personally like it this way, and it's better, imho, to have a "set everything not specified to 0" step rather then "leave whatever garbage was there"
- barosl 11y agoHuh, I didn't know Go has a concept of implicit default value. In that aspect, zeroing out in struct literals also makes sense. However I would say that while default zero is better than garbage value, failing to compile is even better than default zero. (e.g. the compiler refuses to compile `var i int` in the first place) But I guess this is a matter of preference.
- reality_czech 11y agoIf the code failed to compile unless you initialized all values, you could never add a field to an existing type without updating all cases where you were creating an instance. This would basically make refactoring impossible in most real-world Go projects. You could potentially solve this problem by forcing all object creation to be through special constructor functions (like Java and Scala do), but that is not the Go way of doing things.
- barosl 11y agoYup, I'm much in favor of using a constructor, or even a trait, if such a breakage can be problematic, because I believe types are part of an API (so they should have been opaque types in the beginning). But I now understand what an idiomatic Go way is.
- wz1000 11y agoThat's why you use specialized constructors.
- pcwalton 11y agoAll ML-like languages as well as Swift and Rust, force you to initialize all values in the struct. Adding new fields isn't a problem in practice, because most of the time structs that you wanted to add fields to had private fields already to begin with, so they're using a constructor. The nice thing about this approach, of course, is that they can omit all the complexity of "zero values" from the language.
- sagichmal 11y ago> What I disagree is the "zeroing out" part. I don't want > 0 or empty string to be more "special" than ordinary > values. Sentinel zero values have been part of Go since day 1, and leveraged to provide some very nice semantics. See e.g. bytes.Buffer. It's a fundamental and useful part of the language.
- coldtea 11y ago>Letting the compiler decide what values are suitable if I omit them can be a cause of potential bug. The alternative is not to let you omit them, or to nil them. Both worse.
- masklinn 11y agoBut nilling them is exactly what Go does no? It initialises every field to their default/zero. Preventing their omission seems markedly better than the alternative, if you want to allow partial initialisations you can provide a constructor function or an explicit defaults construction.
- tracker1 11y agoExcept that without implicit declaration, how do you bring constructor variance into check? For example, if you have an object/class that has a lot of options available, you would have to create a huge number of constructor functions, or other extensions to simply initialize an instance... As I'm more familiar with JS than golang, it seems to me, with the current method, I can do something like... var inst = new CustomObject(param1, param2, { option15: 'bar' ,... }); Where the first two parameters are required, but the last one can be a structure that represents available/optional options as a singular type... This would be much cleaner than supporting every variation of overloads as part of object construction. Let alone where a type can simply be used directly.
- pcwalton 11y agoIn Rust you usually use either method chaining (CustomObject::new(param1, param2).option15('bar')) or the struct update syntax (CustomObject { param1: param1, param2: param2, option15: 'bar', ..DEFAULT_CUSTOM_OBJECT }) for this. No extra complexity of "zero values" necessary (and the concept of a zero value would be incoherent in Rust anyway).
- tracker1 11y agoBut you have either the complexity of multiple closures, or complex mutability (side effects) with that strategy, depending on how you approach your implementation. In either case the outcome could be less than ideal, and have more chance of unexpected results. Is this... var obj = new CustomObject(...); ... obj.option15('bar'); The same as var obj = new CustomObject(...).option15('bar'); IE: does calling option15 create a new closure/space that is separate from the original object (more functional separation) or does it mutate the original object? I tend to prefer to limit mutations as much as possible in my code... it really just depends on your needs.
- agumonkey 11y agoThere are many thing they could have done. Maybe handle both cases the same, using 'in-order' matching for the first unnamed case. Or since they like regenerating source, spitting out : Valid source: t := T{"example", 123, ""} or t := T{"", 123, "example"} Using some kind of unification of set(String, Int, String) with set("example"::String, 123::Int)
- NateDad 11y agohttp://dave.cheney.net/2013/01/19/what-is-the-zero-value-and-why-is-it-useful http://dave.cheney.net/2013/01/19/what-is-the-zero-value-and...