Struct with property based on other properties

Hi all,

I am learning to use structs in Julia, specifically I have a set of integers that I would like to access throughout some calculations:

struct MyNumbers
   a::Int64
   b::Int64
   c=a+b
end

However, the assignment of c gives me a syntax error, what would be the correct way to do this?

You would use an “inner constructor” to ensure c always has the right value. And an “outer constructor” to create a new instance where c is given the correct value. See the docs here.

julia> struct MyNumbers
           a::Int64
           b::Int64
           c::Int64
           MyNumbers(a, b, c) = begin
               @assert c == a + b
               new(a, b, c)
           end
       end;
               

julia> MyNumbers(a, b) = MyNumbers(a, b, a + b);

julia> MyNumbers(1, 2, 5)
ERROR: AssertionError: c == a + b
Stacktrace:
 [1] MyNumbers(a::Int64, b::Int64, c::Int64)
   @ Main ./REPL[1]:6
 [2] top-level scope
   @ REPL[3]:1

julia> MyNumbers(1, 2, 3)
MyNumbers(1, 2, 3)

julia> MyNumbers(1, 2)
MyNumbers(1, 2, 3)
4 Likes

Or simply

struct MyNumbers
    a::Int64
    b::Int64
    c::Int64
    MyNumbers(a, b) = new(a, b, a+b)
end
6 Likes

Thank you both!

I’ve always used getproperty to accomplish this if using a mutable struct.

function Base.getproperty(x::MyNumbers, sym::Symbol)
    if sym == :c
        returns x.a + x.b
    else
        return getfield(x, sym)
    end
end
4 Likes

If your extra field requires some expensive calculation, then you can precompute it using a constructor, as people have suggested.

However, for simple calculations, the getproperty approach suggested above is nicer because it doesn’t actually use the extra memory and won’t become wrong if you change a or b (although in your example, MyNumbers is immutable so this won’t happen).

getproperty can sometimes be a little tedious to define, though. So my preferred approach (and the most flexible) is to access objects through accessor functions. Personally, I almost never use direct field access outside of defining an accessor. For example:

geta(x::MyNumbers) = x.a
getb(x::MyNumbers) = x.b
getc(x::MyNumbers) = geta(x) + getb(x) # or x.a + x.b
2 Likes