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 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.

:+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