crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Should `Pointer(T)#==` always compare the address, regardless of `T`?

Open ysbaddaden opened this issue 5 months ago • 7 comments

I'm wondering if Pointer#== should always compare the address, regardless of the pointer type. They're partially identical because they point to the same start address. The exact memory size may differ between types, though. And interpretation differs anyway. 🤔

But it's not about the objects themselves, it's about pointers to them. And pointers are primarily defined by their address.

Originally posted by @straight-shoota in https://github.com/crystal-lang/crystal/issues/16412#issuecomment-3574665363

ysbaddaden avatar Nov 25 '25 09:11 ysbaddaden

Note: a pointer is basically an integer after all.

ysbaddaden avatar Nov 25 '25 09:11 ysbaddaden

The funny thing is that comparison is generally not strict on types (#10277). But Pointer#==, which is a potentially unsafe type, is very strict about the type...

straight-shoota avatar Nov 25 '25 09:11 straight-shoota

An alternative could be to disallow equality checking between different pointer types. That would avoid bugs like #16412 as well.

straight-shoota avatar Nov 25 '25 09:11 straight-shoota

Disallow equality checking between different pointer types

That sounds quite good 👍

ysbaddaden avatar Nov 25 '25 09:11 ysbaddaden

It's a bit more complicated when considering different, but compatible types. The situation is already a bit more lax, but only partial:

class Parent; end
class Child < Parent; end

x = Child.new
pointerof(x) == pointerof(x).as(Parent*) # => false
pointerof(x).as(Parent*) == pointerof(x) # => true

straight-shoota avatar Nov 25 '25 09:11 straight-shoota

Pointer(T) includes Comparable(self), so it defines a #==(other : self) method; since this self is equivalent to Pointer(T), and since this T is covariant in a type restriction, the second line will match Comparable(Parent*)#==. In contrast, the first line will not match Comparable(Child*)#==, and thus falls back to Value#==. Including Comparable(T*) would have the same issue.

This is probably why other generic types do it like Comparable(Array) or Comparable(Tuple) instead, to avoid subtle reflexivity issues like this. Pointer is the only standard library type that uses Comparable(self).

HertzDevil avatar Nov 25 '25 18:11 HertzDevil

Yes, the implementation of relaxing the type requirement would be to change Comparable(self) to Comparable(Pointer), and #<=>(other : self) to #<=>(other : Pointer). That would also affect the other method from Comparable. But I think that's fine. If equality only on the address is acceptable, then comparing pointers by their address should be good as well.

straight-shoota avatar Nov 25 '25 22:11 straight-shoota