4 ms·
It's not that the ship has sailed, it is that if you sit down and sketch out what people think they want it is logically incoherent. What Go does is the logical
by jerf 1y ago
It's not that the ship has sailed, it is that if you sit down and sketch out what people think they want it is logically incoherent. What Go does is the logically-coherent result of the way interfaces work and the fact that "nil" values are not invalid. It is perfectly legal for a "nil" pointer to validly implement an interface. For instance, see https://go.dev/play/p/JBsa8XXxeJP https://go.dev/play/p/JBsa8XXxeJP , where a nil pointer of "*Repeater" is a completely valid implementation of the io.Reader interface; it represents the "don't repeat anything at all" value.
In light of that fact, it would cause the interface rules to grow a unique wart that doesn't accomplish anything if interfaces tried to ban putting "nil" pointers into them. The correct answer is to not to create invalid values in the first place [1] and basically "don't do that", but that's not a "don't do that because it ought to do what you think and it just doesn't for some reason", it's a "don't do that because what you think should happen is in fact wrong and you need to learn to think the right thing".
Interfaces can not decide to not box nil values, because interfaces are not supposed to "know" what is and is not a legal value that implements them. It is the responsibility of the code that puts a value into the interface to ensure that the value correctly implements the interface. Note how you could not have io.Reader label itself as "not containing a nil" in my example above, because io.Reader has no way to "know" what my Repeater is. The job of an io.Reader value is to Read([]byte) (int error), and if it can't do that, it is not io.Reader's "fault". It is the fault of the code that made a promise that some value fits into the io.Reader interface when it doesn't.
In Go, nil is not the same thing as invalid [2] and until you stop forcing that idea into the language from other previous languages you've used you're going to not just have a bad time here, but elsewhere as well, e.g., in the behavior of the various nil values for slice and map and such.
One can more justifiably make the complaint that there is often no easy way to make a clearly-invalid value in Go the way a sum type can clearly declare an "Invalid/None/Empty/NULL", or even declare multiple such values in a single type if the semantics call for it, but that's a separate issue and doesn't make "nil" be the invalid value in current Go. Go does not have a dedicated "invalid" value, nor does it have a value of a given type that methods can not be called on.
(You can also ask for Go to have more features that make it harder to stick invalid values into an interface, but if you try to follow that to the point where it is literally impossible, you end up in dependently-typed languages, which currently have no practical implementations. Nothing can prevent you, in any current popular language, from labelling a bit of code as implementing an interface/trait/set of methods and simply being wrong about that fact. So it's all a question of where the tradeoffs are in the end, since "totally accurately correct interfaces" are not currently known to even be possible.)
[1]: https://jerf.org/iri/post/2957/ https://jerf.org/iri/post/2957/
[2]: https://jerf.org/iri/post/2023/value_validity/ https://jerf.org/iri/post/2023/value_validity/
- rowanseymour 1y agoI don't know why every time people complain about this there is an assumption that we just don't understand why it is the way it is. I get that x can implement X and x can have methods that work with nil. I sometimes write methods that work with nils. It's a neat feature. What's frustrating is that 99.99% of written go code doesn't work this way and so people _do_ shoot themselves in the foot all the time, and so at some point you have to concede that what we have might be logical but it isn't intuitive. And that kinda sucks for a language that prides itself on simplicity. I also get that there's no easy way to address this. The best I can imagine is a way to declare that a method Y on type x can't take nil so (*x)(nil) shouldn't be considered as satisfying that method on an interface.. and thus not boxed automatically into that interface type. But yeah I get that's gonna get messy. If I could think of a good solution I'd make a proposal.
- jerf 1y agoBecause in the last dozen times I've handled this question the root cause is lack of understanding of why. Inductively it is logical to conclude that's the reason next time. It is probably also the case the bulk of readers of this conversation are still in the camp that don't understand the problem correctly. If you understand that there isn't really a fix and just wish there was one anyhow, while I still disagree in some details it's in the range I wouldn't fuss about. I understand that sort of wishing perfectly; don't think there's ever been a language I've used for a long time that I've had similar sorts of "I just wish it could work this way even though I understand why it can't." Maybe someday we'll be "blessed" with some sort of LLM-based language that can do things like that... for better or for worse.
- rowanseymour 1y agoI can't think of good way to give programmers control over boxing without adding a bunch of complexity that nobody wants.. but it doesn't seem out of the realm of possibility that the linter could detect issues like this. It should be able to spot methods that aren't nil-safe and spot nil values of those types ending up in interfaces with those methods. Then you'd have less explaining to do!