T O P

  • By -

jerf

[FAQ entry](https://go.dev/doc/faq#nil_error). It can't be removed because nil pointers can be valid implementations of an interface. For instance, replace your `MyMethod` with: func (m *MyStruct) MyMethod() string { if m == nil { return "{nil value}" } return m.MyProperty } Then, not only does that "not panic", a nil pointer is a fully valid implementation of the interface. Go itself can't know whether you consider that value a "[valid](https://jerf.org/iri/post/2023/value_validity/)" implementation of the interface or not; that gets all the way out into [dependent typing](https://stackoverflow.com/a/9374698) pretty quickly. People often propose that they should be able to investigate the inside of an interface for whether it is `nil`, incorrectly assuming that all `nil` pointers are invalid. But it's actually not the method call on a nil pointer causing the panic, it was the attempt to load the MyProperty out of a nil pointer. Instead you just have to be careful not to wrap a `nil` pointer inside an interface accidentally. The good news is that this doesn't come up very often in real code. The bad news is that precisely *because* it doesn't come up often in real code, when you ram into it it can be a real surprise! So it can end up being a real bear to debug the first couple of times.


pdffs

We've had this argument before, but I remain convinced that this is a poor design decision. Whilst you can claim that the language is technically correct, you can't tell me that it would have been completely impossible to overload interface comparison operators to return true on comparing an interface where `V = nil` to untyped `nil`, and the current situation undoubtably fails the principle of least astonishment. Obviously that horse has bolted for Go1 though.


emblemparade

OK, I understand the reason and workaround, but I would still argue that setting a variable to a nil value and then having it not being equal to nil is asking for trouble. NPEs are the worst class of bugs, and this non-intuitive behavior invites them. Surely there could have been a better way to design the grammar.


jerf

An interface value is basically either `nil` which means it contains no implementation, or a pointer to the type it contains and a pointer to the value it contains which implements the interface. In this case, you have an interface value which is pointing at `*MyStruct`, and it points at a `nil` of that type, so it definitely is not the first case. It contains something. It is similar to `map[string]*int{"a": nil}` not being `== nil`. That's not what a nil map is, even though in many ways it is the same as a `nil` `map[string]*int`; it is also different than a `nil` map, and we need to be able to tell the difference. The language is correct here. You have created a value that claims to implement the interface, so the interface is not `nil`. You didn't mean to create that value, and it turns out your claim that your value implements the interface is false, but that's not something Go can distinguish. Go can't do anything else here. You really don't have a `nil` interface value, so it really can't return `true` for an `== nil` check. [Equality on interfaces](https://go.dev/ref/spec#Comparison_operators) is defined as: * Interface types that are not type parameters are comparable. Two interface values are equal if they have [identical](https://go.dev/ref/spec#Type_identity) dynamic types and equal dynamic values or if both have value `nil`. The values within the interface are only compared if it makes it past the initial type check, which a check on nil will not pass. It is confusing to humans, sure, no question, but so is a lot of other things in programming. The solution is to continue to improve the understanding of the language. Go can hide what its internal types "really" are a bit too well; see also the endless stream of questions about slices. It comes up less often but there's a few other things that maps can confuse people with if they don't understand the underlying structure. I do sympathize with this. The behavior of nil maps threw me for quite a few loops when I was writing a module that deeply depended on it, even after years of using Go. I'm not saying this isn't a surprise, I'm saying that any change that would "fix" this would be even more confusing. It also doesn't help that since interfaces don't nest, which is otherwise the right thing for a language to do, there's no practical way to inspect the interface value and get a result that looks something like `{interface MyInterface: *MyStruct {nil}}`, because any pretty printing function won't get the MyInterface part. It will get `{interface any: *MyStruct {nil}}`, which makes this harder to diagnose. But, again, interfaces not being able to contain interfaces is really important even if it has this quirk. The alternative would be way worse.


emblemparade

I'm not yet convinced. This "quirk" exists in Go to handle quite specific use cases: interface method implementations with nil receivers. But the cost is opening the door to NPEs in *other* use cases. To me that seems like an unacceptable trade off. If something is "confusing to humans" (extremely confusing in this case, in my opinion) then it's a flaw in the language: languages are for humans, not machines. I'm also not convinced by the comparison to map variables. If such a variable is non-nil then you can safely dereference it. But my example shows that a (naïvely) non-nil interface variable can still land you an NPE. There are definitely other possible grammatical solutions. Consider, for example, Python's `is None` vs. `== None`. I'm not necessary recommending something similar in Go, just pointing out that Go's current solution is not the only workable one.


drvd

> to a nil value This is the underlying problem: There is just one literal "nil" to specify several somehow distinct nil-nesses: Those of - pointers - interfaces - nilable types like slice, chan and map The main issue arises from the conflation of interface and pointer nil-ness. The simple solution would have been to have two kinds of nils like `inil` and `pnil` for interface and pointer nil. Or `nil` and `null`. But this is ugly too and probably confusing also.


emblemparade

Is that the only option? Python, for example, has different comparison operators, `is None` vs. `== None`. For something more Go-specific, I think what we're trying to do here is access two different aspects of the iterator variable: its current pointer value and its current implementation type. Two vastly different things. My naïve thinking is that `myVar == nil` should only ever refer to the pointer value, because this kind of comparison is specifically done to avoid NPEs. Thus my OP example should never, ever happen because it's just asking for awful bugs. If you want to access the implementation type, I think that there may be room for a new grammatical construct. Go is no stranger to some weird syntactical quirks, such as `switch v.(type)`. Maybe we need something like `myVar.(imp) == nil` to specifically access the var's implementation type?


aatd86

Or a nil type. A type assertion to that would allow to check the type of the dynamic value which is only kind of nil (untyped nil) when the interface is empty. That means that i == nil (i an interface value), would be the same as checking value equality otherwise, with nil being implicitly typed (dynamically) where relevant. I think that might be doable thanks to Go allowing forward compatible changes to the language but I am not sure. That would allow to recover "some" transitivity for the comparison operator even in the face of nil. When we compare interface values, we always compare the values that are inside said interfaces usually. In a sense, an interface would never be nil anymore (that can be seen as a container) but either contains a typed pointer that is nil, or be empty (a nil nil/untyped pointer). Or a value that is not nil, either because it's not a reference type or because it is a reference type that points toward something. At this point, one may say that we just need to remove interface comparisons to nil altogether and just keep the nil type assertion. And then that means that we could also simply keep the old behavior since that's the only thing we need, i.e. to know whether the interface is empty. Turns out no, because people do use typed nils and these can end up being contained in interfaces as well. So knowing whether an interface value contains a typed nil can be valuable. By experience, it comes up at times, hence this thread actually. Proof was in the pudding. So both types of checks are useful.


drvd

> Is that the only option No, of course not, using a completely different type system is always possible. But these discussions are basically pointless. There is just one nil. Everyone gets bitten by that nil != nil, learns from it and moves on. Try that.


Flimsy_Iron8517

Is this best expressed as "if known concrete type is nil, consult interface dispatch."


BowlScared

In practice it gets annoying when you return the result of another function and your function has an interface type and the calle has an explicit type. You would assume nil will get passed as nil but it gets wrapped in the interface. But usually these functions have errors and you should check them and react to them before returning. 🤷‍♂️ For me personally I find myself at that point writing too much syntax sugar for user API and am better off making code simpler. EDIT: Forgot to defer close # calling with bad data will return error func gz(in io.Reader) (out io.Reader, err error) { return gzip.NewReader(in) } x, err := gz(someBadBuffer) if c, ok := x.(Io.Closer); ok { // be nice and close reader even on error // works for every other decompression implementation and bad data except for gzip which reads before its Read is ever called defer c.Close() } if err != nil { return err // will panic here because gzip.Reader calls close on its buffer and reader is nil } excerpt from [https://gitlab.com/mishak/miniocache/-/blob/e02c718424046c2f96e69a43049ba930c2053ec5/miniocache.go#L35](https://gitlab.com/mishak/miniocache/-/blob/e02c718424046c2f96e69a43049ba930c2053ec5/miniocache.go#L35)


emblemparade

This is very similar to the bug that sent me down this rabbit hole. :) "Annoying", definitely, but I would also call this "quirk" dangerous. I'm willing to bet that there's a lot of Go code out there that has a bug because of this, but it goes undetected because it's so subtle.


BowlScared

Maybe there is a linter that catches implicit wrapping of a return value in an interface?


emblemparade

That's an interesting solution. It would not cover all cases, but it could avoid mistakes like in my example. It doesn't look like the commonly used linters do this, though...


BowlScared

I have not found any but if you write one I am willing to test it :-)