"First of all, that's not the case in my example above: nil can satisfy every method of `interface{}`."
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.