Personally I’am not a huge friend of storing redundant data, and you can avoid any illegal instance by just storing the essential information once. Found this originally here and meanwhile using this quite extensively:
mutable struct Square
side
end
# Acutally not strictly needed just for completeness
# see https://docs.julialang.org/en/v1/base/base/#Base.propertynames
propertynames(p::Square,private=true) = begin
fields = fieldnames(typeof(p))
(fields...,:area,:diagonal)
end
#Dispatch the gets
Base.getproperty(p::Square,n::Symbol) = getproperty(p::Square,Val{n}())
Base.getproperty(p::Square,::Val{S}) where {S} = getfield(p,S) #generic fallback
Base.getproperty(p::Square,::Val{:area}) = p.side^2
Base.getproperty(p::Square,::Val{:diagonal}) = √2*p.side
#Dispatch the sets
Base.setproperty!(p::Square,n::Symbol,x) = setproperty!(p,Val{n}(),x)
Base.setproperty!(p::Square,::Val{S},x) where {S} = setfield!(p,S,x) #generic fallback
Base.setproperty!(p::Square, ::Val{:area},x) = setfield!(p,:side,√x)
Base.setproperty!(p::Square, ::Val{:diagonal},x) = setfield!(p,:side,x/√2)
julia> s = Square(√2)
Square(1.4142135623730951)
julia> s = Square(√2)^C
julia> s.diagonal
2.0000000000000004
julia> s.area=4
4
julia> s.diagonal
2.8284271247461903
julia>
It is also easier to maintain this way and you can easily add up more implicit properties without stressing your memory too much.