How to define `:(==)` on user-defined types for `Set` operations

I want to create a Set{MyType} object, but use a custom Base.:(==) to compare instances of MyType. It seems that the dispatch of in is not making use of Base.:(==) (or Base.isequal for that matter) despite what the documentation says. Here is a MWE:

struct MyType
    a::Int
    b::Int
end

Base.:(==)(x::MyType, y::MyType) = x.a == y.a

x = MyType(1, 2)
y = MyType(1, 3)

@assert x == y

S = Set{MyType}()
push!(S, x)
@assert x in S
@assert y in collect(S)
@assert !(y in S) # this one is unexpected / I would want this to actually return true

push!(S, y)
@assert length(S) == 2 # a new element has been added
@assert y in S # y is now in S

I know there have been a couple threads on this before (here and here) but these seem to be centered around mutable elements in a Set.

My use-case is not so performance critical, so I’ll probably just use a Vector{MyType} and check if y in S before push!ing, but it would be helpful if the docs could be updated to reflect what seems (to the best of my testing/comprehension of the docs) unexpected behavior.

Finally, here’s a MWE also with overloading Base.isequal. The output makes it clear that only after collecting does in use :(==).

struct MyType
    a::Int
    b::Int
end

function Base.:(==)(x::MyType, y::MyType)
    println("USING UPDATED :(==)")
    x.a == y.a
end

function Base.isequal(x::MyType, y::MyType)
    println("USING UPDATE isequal")
    x.a==y.a
end

x = MyType(1, 2)
y = MyType(1, 3)

@assert x == y

S = Set{MyType}()
push!(S, x)
@assert x in S
@assert y in collect(S)
@assert !(y in S) # this one is unexpected / I would want this to actually return true

push!(S, y)
@assert length(S) == 2 # a new element has been added
@assert y in S # y is now in S

versioninfo()

Julia Version 1.11.3
Commit d63adeda50d (2025-01-21 19:42 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: macOS (arm64-apple-darwin24.0.0)
  CPU: 16 × Apple M4 Max
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, apple-m1)
Threads: 1 default, 0 interactive, 1 GC (on 12 virtual cores)
Environment:
  JULIA_EDITOR = code
  JULIA_NUM_THREADS = 

That second topic — Create a user-type set — is exactly your situation. The mutability is irrelevant here. When you re-define ==, you must also symmetrically redefine hash.

2 Likes

Thank you so much @mbauman! I guess what I really needed was confirmation that this was the right approach. Looking at that post again, I decided to use AutoHashEquals.jl and defined

using AutoHashEquals

@auto_hash_equals fields = (a,) struct MyType
    a::Int
    b::Int
end

And it’s working perfectly!