I hypothesize, but can not prove, that the root problem isn't that there are two nils. The root problem is that people incorrectly think nil is always invalid. It is not. This is perfectly legal (https://play.golang.org/p/71BNAyXIzRv )
type Thing string
func (t *Thing) DoSomething() {
if t == nil {
fmt.Println("nil thing")
return
}
fmt.Println(string(*t))
}
Nil pointers are perfectly legal values. You can call methods on them no problem. I have places where I use them; for instance, I have a memory pooling implementation where the nil pointer simply fills requests via "make" directly and does no pooling, so you can swap it out easily to test if it is causing some other bug.So I think people think of there as being "two equally invalid values", but it's not true. There's the nil interface, which is pretty useless, but an interface containing a nil pointer in it is legal and may very well completely successfully implement the interface. Checking to see whether the interface "contains" a nil pointer is almost always an error. It is not something you should ever do when you have an interface value.
The real error occurs when you put the nil pointer in an interface value that can't correctly implement the interface, which is also nothing particularly special; putting anything in an interface value that can't implement the interface is an Already Lost situation. That the error then propagates around is bad, but the error occurred earlier and it's already too late to fix it by the time some other bit of code receives this broken value. Good code never has any reason to penetrate the interface and interrogate the underlying type for whether or not it is nil; if you are routinely encountering this problem, if you stop putting invalid implementations in an interface value, the problem will go away. I use all these features routinely and don't encounter this problem, because I know to never lie by putting something in an interface value that doesn't implement the interface.
So here's where I do that:
https://github.com/gwd/session-scheduler/blob/master/handle_...
The pattern is basically:
var foo = interface{}
switch (something) {
case A:
foo = Foo()
case B:
foo = Bar()
}
Foo and Bar both return different concrete types which are nil if the request can't be handled. I want to do something if either Foo or Bar return 'nil', and also if neither case A nor case B are taken. So I've got: if foo == nil || reflect.ValueOf(foo)IsNil() {
// handle the error
}
// Pass foo elsewhere
I mean yeah, I certainly could arrange things differently; but in general I think that's not an unreasonable thing to expect to be able to do.> The real error occurs when you put the nil pointer in an interface value that can't correctly implement the interface... putting anything in an interface value that can't implement the interface is an Already Lost situation.
First of all, that's not the case in my example above: nil can satisfy every method of `interface{}`.
Secondly, my compiler refuses to compile if I assign something other than a nil pointer that can't implement the interface. If putting nil into an interface is such a bad idea, the compiler should prevent you from doing it.
Yes, that's true; in this case the lie is elsewhere. You said "interface{}", but you have a contract you expect the value to be able to keep. It's a contract the type system can't enforce, true. But it's a contract you're setting later in your RenderTemplate call, implicitly, and you're breaking it. The error occurs on the lines where you unconditionally set the "display" value to this contract-breaking value without checking whether it fulfills the contract you expect of it later.
(I have a number of places in my code where I document something like "this function takes an interface{} but this value is expected to be able to be passed to encoding/json to be turned into JSON without error". Yes, the type system can't enforce that and it would be nice if it could. Nevertheless, if someone else calls that function and passes it something with a channel in it or something, the bug isn't in the code using the interface{}, it's in the code that passed it something that broke the contract.)
You need to not slam a contract-breaking value straight into the "display" variable without checking it.
The simplest thing is using an intermediate value, which will be typed:
// above the switch
notFound := false
// at the display line
userDisplay := UserGetDisplay(user, cur, true)
if userDisplay == nil {
notFound = true
} else {
display = userDisplay
}
// use the notFound value here to decide to display the not found page.
The probably-better thing over all is to use polymorphism instead of a switch statement. I don't have a great Go-specific link, but here's a simple example: https://www.codementor.io/@uditrastogi/replace-conditional-s... I'd write a method that can return a notFound status or something instead, or an error I can turn into "not found", and never try to write any code that splits between different types with interface{}.It'll probably make you happier in the long run if you learn to do that. I find it a very common pattern in HTTP handlers, because it's very common for the HTTP interface to simply not directly map to an object hierarchy cleanly.
"I mean yeah, I certainly could arrange things differently; but in general I think that's not an unreasonable thing to expect to be able to do."
Most languages pride themselves on handing you a ton of tools and telling you to do whatever you want. You then don't have to be an expert in each individual tool, just know how to use the toolbox to get the whole job done. Go hands you a smaller toolbox, and expects you to be an expert in each one. In my experience, it can generally get the job done just fine, but you need to be ready to use each tool very well.
I say this without sarcasm or harsh intent: If you're looking at my suggestions and you're saying "yeah, but I don't want to write that way", you're going to be fighting Go forever. There are in fact good ways to write that sort of code in Go. I do all the time; I don't think I have a single instance of the pattern you're using here in my entire codebase. But you need to lean in to the tools and let them guide you, not try to force them to do what you want. If you don't want to do that... and that's is perfectly valid, I'm not saying that's bad, but if you don't... you should probably stop using Go, to save your own sanity.
Also... to be clear... this is all a software engineering discussion moreso than a Go defense. It is a good idea in all languages to keep track of contracts and be sure not to put things in the variables that can't fulfill the contract. It is never fun to break a contract in some bit of code, and then try to pick up the pieces later. While Go's type system is certainly far from the strongest, there aren't any languages short of the dependently-typed languages that can express all the contracts of interest, if even they can. This is not a good pattern anywhere, you just have fewer tools to hack around it in Go.
I disagree with the characterization that "Go expects you to be an expert in each one". That's what C does, with all its arcane implicit type conversion rules, memory models and UB traps lying everywhere. Coming from C and learning Go, it was refreshing, for instance, for integer overflow to be defined, and for it to be impossible to even compare `int32` to `int64` without a cast.
Which is why my actual proposal is along similar lines: The "magic" of comparing an interface to a bare `nil` contains a trap, so disallow it. If you ended up with something like this:
if display == interface{}(nil) {
...
}
It should be pretty obvious that if `display` is `*UserDisplay(nil)`, this comparison will be false. That would prompt you either to use reflection, or to use something else to track whether `display` contained something valid (e.g., a separate boolean variable, like you propose).Furthermore, I do stand by the comment I made in the discussion of my proposal regarding comparing `error` to `nil`: It turns out, probably the single most common pattern in Go is in fact not safe. Anyone anywhere could accidentally return `*MyError(nil)`, and suddenly all the error checking code all the way up the stack is completely broken. Yes, as you say, doing so would clearly be a bug; but the whole point of having a type-checked language is to be able to prevent this type of bug from cascading throughout the system.
Golang is still, in fact, my go-to language for new projects. And in most things it's just great -- I know how to use the type system to prevent all sorts of dumb mistakes, and 75% of the time, if it compiles, it Just Works. It's because Go is so good at preventing stupid errors that I find the "interface trap" so frustrating.
Until you correct your understanding, you will continue to find Go frustrating. nil and invalid are not the same thing. This is not a matter of opinion; it is how the language works. As long as you persist in thinking otherwise, you will remain confused.
If you checked whether err is nil by checking the interface value, and then penetrating the interface to see if the underlying concrete type is a pointer and than pointer is nil, that is a bug in the code doing that. You should never do that. It is never correct. It is objectively wrong to think of them as the same thing and to treat them the same, because they are not.
The correct solution is what I outlined; do not create the lying interface values in the first place. Then they can not propagate through your code and mess you up.
I'm not trying to debate whether Go is "right" or "wrong" here. It is perfectly acceptable to come to a correct understanding of how Go works and still think it is not the best way to build the language. Personally I'd still be happy to get non-nillable pointers, in the same way C# managed to add them later, so you can put me in that category myself. But you still have an incorrect understanding of how Go works, and I'm writing this not to defend Go but to try to save you that frustration. However, if you keep resisting the correct understanding, you will continue to be confused and frustrated and write bugs.