4 ms·
It's much worse, because there is auto-conversion between concrete types and interfaces you can't reliably do e.g. "if err != nil { SendString(err.Error()) }" ―
by Joker_vD 5y ago
It's much worse, because there is auto-conversion between concrete types and interfaces you can't reliably do e.g. "if err != nil { SendString(err.Error()) }" ― it can panic with SIGSEGV. I've ran into it a month ago in actual production code: condensed version is at [0]. Yes, it's a bad design on the 3rd-party library's part... or perhaps it is a defect of the language: "pit of success" and all of that, you know?
[0] https://go.dev/play/p/D_M1geUtJTP https://go.dev/play/p/D_M1geUtJTP
- throwaway894345 5y agoI don't think this is a different (and thus not "worse") case--it seems like a specific instance of the same case we've been discussing. Ultimately it comes down to this: error.Error((*CustomErrorType)(nil)) You're putting a nil `*CustomErrorType` into an `error` interface. This definitely causes some people confusion and results in bugs, but the question under discussion is whether the original claim ("an interface can be simultaneously nil and not nil") is true. Of course, it's not true as this example illustrates--the interface is not nil but the underlying data (of type `*CustomErrorType`) is nil.
- anonymoushn 5y agoI think the original claim is that it can be inner nil or outer nil and those are two different things and often you should check for both.
- throwaway894345 5y agoThe original comment was: > A great innovation of Go is that it includes a built-in reference type with both inner nils and outer nils. Which the OP later clarified by saying an interface can be both nil and not nil at the same time. So it’s pretty clear that we’re debating about whether interfaces have three states (nil, valid, and both) i.e., multiple kinds on nullity (inner vs outer).
- anonymoushn 5y ago> Which the OP later clarified by saying an interface can be both nil and not nil at the same time. I don't think I made any such clarification...
- throwaway894345 5y agoYou aren’t the OP. I was referring to this: > typed nils are a single value which is both nil and non-nil at the same time.
- anonymoushn 5y agothe "original poster" is someone other than the person who made "the original comment"?"
- TheDong 5y agoI think there is a point to be made here. Let's say I have the following code: func open() (*os.File, *os.PathError) { return os.NewFile(1, "stdout"), nil } // in main // (some lines omitted) fi, err := open() if err == nil { fmt.Println("no error") So, my question is, does that print 'no error' or not? Normally, you would say "no, it does not, 'err' is a nil '*os.PathError' so it does not. However, I didn't mention what lines were omitted, and those lines actually matter! // main _, err := os.Executable() fi, err := open() if err == nil { fmt.Println("no error") Having that line before it changes the output, and the error is no longer detected as nil (since it's now a non-nil error interface referencing a nil struct). Why does this matter? Well, it's extremely common in go (idiomatic in fact) to reuse the "err" variable name, and it's also extremely common to use it in multiple assignment with ":="... where despite using the declaration syntax, you can re-assign to existing variables without their types changing. I think that's what the parent was pointing out: a combination of type inference and automatically "converting" a struct into an interface for you (both in assignment, as I showed above, and when you return a value, as shown in the parents example) means that you can end up with this issue without doing anything as obviously wrong. In other languages, returning concrete error types means the caller has more type-system information and is good. In go, returning concrete error types gives the caller more information, but opens them up to accidentally implicitly casting it to an interface and blowing their foot off. As such, we have almost completely untyped error returns, and any time a gopher thinks "this is dumb, why don't I use the type system for this?" they're actually just loading a shotgun and pointing it at their foot. > I don't think this is a different (and thus not "worse") case All the above was just to say, I think it's the same case, but it's pointing out that it's "worse" because it interacts poorly with other parts of the go language spec. Playground: https://go.dev/play/p/4MYEZr8D5Qv https://go.dev/play/p/4MYEZr8D5Qv
- throwaway894345 5y agoI agree that this is a problem, but the problem is not that interfaces have multiple kinds of nility. The issue here is that you’re putting a pointer type into an interface and it’s easy to forget that the runtime type is effectively a reference to a reference, both of which can be nil. Any preferred solution (which will never happen due to compatibility guarantees) would be to eliminate zero types altogether and use Rust-like enums instead.