On Tue, Feb 27, 2024 at 8:19 PM Marvin Renich <m...@renich.org> wrote:

> Prior to generics, the type of the
> arguments to == were easily known to the programmer, and so it was
> obvious when this "undefined" exception would raise its ugly head, and
> you just didn't use it for empty struct types.  But now, with generics,
> this can only be classified as a glaring BUG in the spec.


There is pretty much a 0% chance that we'd change the spec in this regard,
at this point. It would mean that variable declarations like
`[1<<30]struct{}` would have to allocate huge chunks of heap, to ensure
that different index-expressions can have different addresses. And while
there shouldn't be any code relying on that not happening for correctness,
there is definitely code out there relying on it for performance (e.g.
there is a pattern of adding struct fields like `_ [0]func()` to ensure a
type is not comparable - such a struct would now change alignment and size).

The optimization that variables of zero size can re-use the same address
has been in Go since before Go 1. Given this, it is pretty much implied
that comparison of those pointers will sometimes have weird results - the
only question is, *which* results are weird. I agree that this is one of
the weirder cases. But I don't think we can practically define `==` for
pointers to zero-sized variables.

I'll also point out that for generics specifically, I'm not sure *any*
action would have a practical effect. If the type argument is not
statically known, we also can't special-case it to take into account that
it's a pointer to a zero-sized variable. Note that the triggered
optimization isn't necessarily "these are pointers to zero-sized variables,
hence I can do whatever I want" - it's "these are pointers to distinct
variables, hence I can assume they are unequal". That is a generally useful
optimization and it would still be applied to generic code.

How can a programmer count on x == y having any meaning at all in code like
> this:
>
> func IsEqual[T comparable](x, y T) bool {
>     return x == y
> }
>
> if the definition of == for empty structs is undefined?


The result is defined for empty structs, just not for *pointers* to empty
structs.
Note that `==` has other edge-cases as well. In particular, for floating
point/complex type arguments, `==` is irreflexive (e.g. NaN is unequal to
itself).
I'm not sure that pointers to zero-sized variables make this significantly
worse.


> If we can at least agree that this ambiguity is no longer desirable,
>

I don't think we can agree on that, sorry.


> let's consider the two possible definitions:
>
> 1. Pointers to distinct zero-size variables are equal:
>
> This allows the compiler to easily optimize virtual address usage, but
> is inconsistent with the non-zero-size definition.
>

Please look at the issue I filed for some discussion of edge-cases we are
unlikely to be able to cover satisfactorily. One obvious case is when
converting them to `unsafe.Pointer`, in which case the compiler no longer
knows that they point at zero-sized variables. Potentially, any such
conversion would have to re-assign them the magic "zero-sized variable"
address, which then would potentially lead to other weird comparison
implications, when code assumes that two `unsafe.Pointer` pointing at
distinct variables should have distinct addresses.

We could probably make *more* such comparisons evaluate to `true`, but it's
unlikely that we could ever cover *all* of them. It would potentially have
prohibitive performance-impact on slicing operations, for example.

2. Pointers to distinct zero-size variables are not equal:
>
> This is consistent with the non-zero-size definition, but the
> implementation would likely require distinct virtual addresses for
> distinct variables.  Whether this would require committed memory
> corresponding to those virtual addresses is unclear to me.
>

I believe it would. In effect, `struct{}` would have to take at least one
byte (as would a zero-sized array).


> Definition 1 removes the distinction between empty struct values and
> empty struct instances, and the only way for the programmer to get that
> distinction back is by using a non-empty struct.
>
> On the other hand, definition 2 preserves the distinction.  If a
> programmer wants to have instances compare as equal, it is often very
> easy to use instances of the empty type rather than instances of a
> pointer to the empty type.  Implement the methods on the type with value
> receivers rather than pointer receivers.
>

I think if these arguments hold any water, the argument "the programmer
just shouldn't use pointers to zero-sized variables, if they want defined
semantics for ==" is just as valid. That is, if they have control over the
type and we are willing to force them to make a decision aligning with our
definition, why not force them to make a decision aligning with there not
being a definition?


>
> ...Marvin
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/Zd41i3rHLskqBuef%40basil.wdw
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHVuxSduyWHG20DjzG1jvE0P06fEqC_FyHdDEWewjOjTg%40mail.gmail.com.

Reply via email to