JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.73k stars 5.48k forks source link

Base.(===) doesn't appear to obey its specification #49254

Open gafter opened 1 year ago

gafter commented 1 year ago

I don't understand ===. The doc says "First the types of x and y are compared. If those are identical, mutable objects are compared by address in memory and immutable objects (such as numbers) are compared by contents at the bit level." But...

julia> t1 = ("a", 1)
("a", 1)

julia> t2 = ("a", 1)
("a", 1)

julia> pointer(t1[1]) == pointer(t2[1])
false

julia> t1 === t2
true

julia> versioninfo()
Julia Version 1.8.2
Commit 36034abf260 (2022-09-29 15:21 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin21.3.0)
  CPU: 12 × Apple M2 Max
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-13.0.1 (ORCJIT, apple-m1)
  Threads: 1 on 8 virtual cores
Environment:
  JULIA_SSL_CA_ROOTS_PATH = 

It appears t1 === t2 should be false, as they are not bitwise equal (they contain different pointers).

Either the documentation is wrong (a bug that should be fixed) or the implementation is in error. I hope it is the documentation, which I suspect should say "First the types of x and y are compared. If those are identical, mutable objects are compared by address in memory, primitive objects (such as numbers) are compared by contents at the bit level, and immutable objects are compared field-by-field using ===."

ctarn commented 1 year ago

As documented here:

If all the fields of an immutable structure are indistinguishable (===) then two immutable values containing those fields are also indistinguishable: ...

I agree that similar sentences should be added the doc of ===.

StefanKarpinski commented 1 year ago

The doc and behavior is correct: tuples are immutable and calling pointer on a tuple gives you something that may or may not be a meaningful number. If anything, the bug here is pointer not erroring on a tuple.

vtjnash commented 1 year ago

FWIW, the pointer call demonstrated there is on the String object, and we make few guarantees about how meaningful that value is and what can be done with it, but it is commonly necessary for C interop.

StefanKarpinski commented 1 year ago

Ah, yes, pointer(::String) is a bit unusual—it gives a pointer to memory that has the string data at it, but there's no guarantee of which pointer to such data or that it has anything to do with object identity.

bvdmitri commented 1 year ago

The doc also says that "in the sense that no program could distinguish them", but the example in the issue showed that in some cases you could distinguish objects (by using pointer) even when === evaluates to true.

vtjnash commented 1 year ago

As Stefan said, it does not uniquely distinguish the object. It gives an arbitrary such pointer, but not one that is necessary derived from the object in any meaningful way; it is not always repeatable; and it may or may not be unique.

oscardssmith commented 1 year ago

specifically pointer(x)===pointer(x) may return false.

gafter commented 1 year ago

specifically pointer(x)===pointer(x) may return false.

@oscardssmith Can you please refer to where in the docs I can find information sufficient to infer this? If so, I will close this issue.

I found https://docs.julialang.org/en/v1/base/c/#Base.pointer but it doesn't give any hint of that.

https://docs.julialang.org/en/v1/base/c/#Base.pointer_from_objref says that this function may not be called on immutable objects, and it does indeed produce an error for must such. But it happily produces a pointer from a string.

oscardssmith commented 1 year ago
julia> pointer("a")
Ptr{UInt8} @0x00007f77fe8b9dd0

julia> pointer("a")
Ptr{UInt8} @0x00007f77fe8ba298

This function is "unsafe"

is somewhat a hint (although we should probably add the specific weirdness with strings and malformed types).