Deriving equality for Julia structs with mutable members

Viewed 140

In the Julia code below, we have equality not working for T{BigInt}, due to BigInt being a mutable struct. == is explicitly defined by BigInt for themselves, however.

julia> struct T{X}
           x :: X
       end

julia> T{Int64}(1) == T{Int64}(1), T{Int64}(1) === T{Int64}(1)
(true, true)

julia> T{BigInt}(1) == T{BigInt}(1), T{BigInt}(1) === T{BigInt}(1)
(false, false)

julia> T{BigInt}(1).x == T{BigInt}(1).x, T{BigInt}(1).x === T{BigInt}(1).x
(true, false)

Is there a way to either:

  • autogenerate an == for these sorts of structs which just recurses == on each field,
  • or have immutable versions of mutable structs as members (as with const in C++), other than using an immutable equivalent to BigInt?

My goal is to avoid boilerplate in a package with a large number of such structs.

1 Answers

This should work:

function my_equals(a::S, b::S) where S
    for name in fieldnames(S)
        if getfield(a, name) != getfield(b, name)
            return false
        end
    end
    return true
end

I tried overloading == via

import Base.==

function ==(a::S, b::S) where S
    for name in fieldnames(S)
        if getfield(a, name) != getfield(b, name)
            return false
        end
    end
    return true
end

That has the intended behavior, but (unsurprisingly) seems to break things (i.e. you can't even call exit() after redefining == like that).

If you want to use ==, then you could have all your custom structs inherit from some abstract type, like this:

abstract type Z end

struct T{X} <: Z
    x::X
end

struct S{X} <: Z
    x::X
    y::X
end

import Base.==

function ==(a::V, b::V) where V <: Z
    for name in fieldnames(V)
        if getfield(a, name) != getfield(b, name)
            return false
        end
    end
    return true
end

Then you can use

julia> T{BigInt}(1) == T{BigInt}(1)
true

julia> S{BigInt}(2, 5) == S{BigInt}(2, 5)
true

julia> T{BigInt}(1) == T{BigInt}(2)
false

julia> S{BigInt}(2, 5) == S{BigInt}(2, 3)
false

That doesn't interfere with existing ==.

Related