Go version
go1.23rc1
Output of go env
in your module/workspace:
Reproduces in Go playground
What did you do?
https://go.dev/play/p/QpsXKVBXX0_M?v=gotip
What did you see happen?
Go 1.22: ::ffff:11.1.1.12 11.1.1.12 false true
Go 1.23: ::ffff:11.1.1.12 11.1.1.12 false false
What did you expect to see?
Either a release note around behavioral change to netip behavior, or consistent behavior between releases.
TBH I am not really sure if the 1.22 behavior was correct, so not necessarily against the change. We detected this in unit tests only.
Comment From: mateusz834
This happens because the z6noz
has now a field set to true.
https://github.com/golang/go/blob/d79c350916c637de911d93af689a5e4e7ab5a5bb/src/net/netip/netip.go#L70-L71
https://github.com/golang/go/blob/d73b4322ed8105e8cad438868ec8ad7d635799eb/src/net/netip/netip.go#L70-L71
Comment From: ianlancetaylor
CC @mknyszek This was changed in https://go.dev/cl/577035. This is the first report we've seen so I don't think we need to roll back, but this does need a release note.
Looking at the CL, it's not clear to me why the fields of addrDetail
are exported. Not that it makes a difference for this issue.
Comment From: mknyszek
This is the first report we've seen so I don't think we need to roll back, but this does need a release note.
I don't mind sending a release note patch, but in the context of the issue title, would this be considered a breaking change? reflect.DeepEquals
reaches into unexported fields -- it's inherently fragile in that way and it's usage comes with that risk.
Furthermore, the fact that we get a true
deep-equal result for values that don't otherwise compare equal in Go 1.22 seems wrong and unexpected to me. The root cause seems to be that in the old interning API it was possible to construct distinct sentinel values whose internal values were in fact equal (nil), and this behavior was previously exploited by the net/netip
package. I think it might not technically violate internal/intern
's invariants (maybe it stated something like "values produced by Get are equal iff the values passed to Get are equal"), but it's still strange to me. In any case, it's not possible to (safely) do that with unique
.
Looking at the CL, it's not clear to me why the fields of addrDetail are exported.
The reason is testing. To construct an Addr
for testing we need to somehow construct the right addrDetail
, but the tests live in a separate package and can't access unexported structs. So, we use a type alias type AddrDetail = addrDetail
in export_test.go
. But in order to then easily construct an AddrDetail
, the fields of addrDetail
are exported.
Alternatively we can have a constructor function that we export instead, like we do for Addr
-- if you think that's better, and it's better to keep addrDetail
's fields unexported, then that's fine with me, I can send a CL.
Comment From: gopherbot
Change https://go.dev/cl/594295 mentions this issue: _content/doc/go1.23: note netip.Addr change in reflect.DeepEqual
Comment From: ianlancetaylor
Thanks. I agree that this is not a breaking change but it is a change. We should document changes that can affect people's code, even if that code is relying on undocumented properties. Sent CL 594295.
Comment From: ianlancetaylor
I do think the code will be clearer if we don't export the fields of AddrDetail
. It means that people reading the code won't wonder why the fields are exported and what might be reading them. Sent CL 593737.
Comment From: gopherbot
Change https://go.dev/cl/593737 mentions this issue: net/netip: unexport fields of addrDetail
Comment From: bradfitz
TBH I am not really sure if the 1.22 behavior was correct, so not necessarily against the change. We detected this in unit tests only.
I'd actually argue that the 1.22 behavior was incorrect. I didn't consider reflect.DeepEqual behavior at the time. Previously z4
and z6noz
differed only by their pointer address which was sufficient for ==
but insufficient for DeepEqual
.
We should probably even lock in this new behavior with tests and document this change as a bug fix.
Comment From: gopherbot
Change https://go.dev/cl/594375 mentions this issue: net/netip: add test that Compare and reflect.DeepEqual match
Comment From: howardjohn
TBH I am not really sure if the 1.22 behavior was correct, so not necessarily against the change. We detected this in unit tests only.
I'd actually argue that the 1.22 behavior was incorrect. I didn't consider reflect.DeepEqual behavior at the time. Previously
z4
andz6noz
differed only by their pointer address which was sufficient for==
but insufficient forDeepEqual
.We should probably even lock in this new behavior with tests and document this change as a bug fix.
:+1: from me, I was pretty surprised by the old behavior once I found this. This was even more surprising when combined with https://github.com/golang/go/issues/35727, which was the full flow we were triggering in our tests