Hacker News new | ask | show | jobs
by jerf 3898 days ago
I should explicitly state I seem to be in the minority in the Go community here, but: You don't get a compiler error if you initialize by name. You do get a compiler error when you initialize by position. In my minority opinion, you can and should use that to your advantage whenever possible. Some structs are clearly "configuration-like", for instance, and you don't want an error if a new option shows up, which will probably default to whatever you had before anyhow. Some structs are clearly data structures, and you'd really like to know if your two-dimensional point suddenly grew a third parameter. Of course it's not a bright shining line, but it's often pretty easy to tell which you have, or which thing you want, and use the correct initialization.

In this case, if you used:

    T{4, "hello", 3.5}}
most future type changes to the T struct will become compiler errors. (It won't be if the types are compatible, for instance, changing the first to a float would still result in a legal struct. If you have richer types in play that is less of an issue.)

golint will then complain at you, but you can pass a command-line switch to turn that off.

(This, amusingly, puts me in the rare position of siding against the Go community, on the side of the language designers. Bet you may not have known there is such a position to take. :) )

3 comments

One non-obvious downside is that the Go 1 compatibility guarantee doesn't apply to struct literals that don't use field names. (I suspect you're aware of this, but other readers might not be.)

So it's possible that a future version of Go could add a field to some struct you're using and your code will stop compiling when you upgrade. It's an easy fix, of course, so it's not that big of a deal, but it's worth realizing.

The point is that if I'm using stuct literals, I want the compiler to stop me for those structs.

I'm explicitly rejecting the idea that all struct changes should be possible without producing compiler errors. Compilers errors when the guarantees your code is based on changes is a feature, not a bug.

That's a pretty risky thing to do.
I just don't get this attitude. I'm asking for the compiler to break my code if something I depended on changes. The alternative is the risky one! This is the safe alternative.

Compiler errors aren't evil. They're a tool. They work best when there is a one-to-one correspondence between problems and errors. That's not possible in the general case, but the closer we get, the better. And the worst case is not when I get a spurious error. That's easy to deal with. The worst case is when I don't get an error I should have. If you're going to worry about "riskiness", that's the risk that should keep you up at night. Not compiler errors for things that turn out to be no big deal, and can quite likely be fixed with one quick go fmt -s.

In all other cases I'd want the compiler to break my code. The problem here is that this technique is very fallible, the chance for false negative, undetected errors is high. It's risky because there's a ton of cases where you won't get a compiler error. It's unduly making you feel safe, which is not a good thing in my opinion. This is mostly why I think it's risky, because you feel safe when you shouldn't.

With keyed-fields, the worst case is that you have uninitialized fields, which typically doesn't cause much problems and get caught quickly where it matters. With unkeyed-fields, you might have code that compiles but sets unexpected fields. Things that would otherwise panic, now just keep working without you noticing, until strange things happen and you have to review all initializations and remember the struct layout every time you see the struct being created.

Personally, I don't like both techniques anyways. It's too error prone. I'll prefer writing a small constructor where I handle initialization deliberately. It's not super Go-ish but at least I centralize all the issues surrounding struct initialization in 1 place: the constructor. Then when I change what fields go in the struct, I change the function signature and the compiler breaks and doesn't let things fall through silently.

I agree. But the industry is hurtling down a tunnel of weak typing and runtime checking. So compiler features are diminishing in relevance at a geometric rate.
I see the exact opposite trend happening. Weak typing is plateauing. It's the last moment of apparent strength before long, slow, but inevitable collapse. Most interesting work is being done on the static side right now, partially because there's no more work to be done on the dynamic side. (A great deal of being dynamic is precisely throwing away all the structure you might build further features on.)

You can also see this in how all the dynamic languages are working on adding "optional" or "incremental" dynamic typing. Static languages, by contrast, generally create one dynamic type, stick it in a library somewhere, and let the small handful of people who really need it use it. Few, if any, of them are adding any dynamic features. The motion trends are clear.

So I get to go back and look at the struct to see the order every time I initialize an instance? Or watch everything break when the noob on the team alphabetizes the struct fields? Yeah, that's a great solution.
If you're doing this, it is, by definition, on structs you choose to do it on. If you lack the judgment ability to decide when you want that, fine, never do it.

And the noob that is so noobish that they change code and don't even compile it to check to see whether it works is a menace well beyond this issue. That's an overpowerful argument; the real problem is the noob that isn't even running the compiler. The noob doesn't "break struct initializations" specially, they break everything.

> If you lack the judgment ability to decide when you want that, fine, never do it.

The choice in question is whether I want code that breaks silently when I add a field to a struct (named fields in initializers), or code that breaks silently when I swap fields of the same type in a struct (positional fields in initializers). Please tell me more about how "judgment ability" makes this anything other than a choice between brittle code and brittle code.

> And the noob that is so noobish that they change code and don't even compile it to check to see whether it works is a menace well beyond this issue. That's an overpowerful argument; the real problem is the noob that isn't even running the compiler. The noob doesn't "break struct initializations" specially, they break everything.

Compilation will not catch all situations where struct fields are reordered. Consider the rather common case where two fields on a struct are of the same type. If a noob swaps the order of these fields, it will compile just fine using your method of struct initialization. It's even quite possible that if unit tests initialize the structs in the same way, this could get past unit tests as well.

This is a pretty obvious case, and the fact that I have to explain it to you is yet another example of having to dumb things down for Go users who don't know the first thing about programming language design.

"Consider the rather common case where two fields on a struct are of the same type."

Or perhaps even the even more complicated case I already mentioned upthread, that an int will still happily initialize a float?

"Consider the rather common case where two fields on a struct are of the same type. If a noob swaps the order of these fields, it will compile just fine using your method of struct initialization. It's even quite possible that if unit tests initialize the structs in the same way, this could get past unit tests as well.... dumb things down for Go users"

What does any of this have to do with Go? All languages with structs have these "problems"! Even Haskell will have the exact same problems (even before you turn on OverloadedStrings). You're reaching so hard to be dismissive of some sort of stereotypical programmer that only exists in your head that you've completely surrendered reason. You should reconsider whether that's really who you want to be.

> What does any of this have to do with Go?

I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question. We're discussing ways of initializing Go structs.

> All languages with structs have these "problems"!

This is completely false, and exactly why I'm dismissing you: we wouldn't be having this conversation if you knew anything about other languages. There are plenty of languages which will warn you when you fail to initialize a struct field when initializing fields by name.