where I want to sometimes change the value of x. What’s the best way to deal with this? It seems a bit silly to rebuild a MyType every time I change x, as there might be lots of other fields. So instead, I currently do:
struct MyType
x::Vector{Float64}
some_other_fields
end
where x is a single element vector, which allows me to mutate as much as I want.
Is this wasteful? Is there a more efficient way to handle this situation?
If x just needs to hold a single element, you can use the following.
struct MyType
x::Base.RefValue{Float64}
some_other_fields
end
You can use it as follows.
julia> my_type = MyType(Ref(5.0), 1)
MyType(Base.RefValue{Float64}(5.0), 1)
julia> my_type.x[] = 3.2
3.2
julia> my_type
MyType(Base.RefValue{Float64}(3.2), 1)
julia> my_type.some_other_fields = 2
ERROR: setfield!: immutable struct of type MyType cannot be changed
If you really wanted to retain the syntax, you can use Oscar’s suggestion or do the following.
julia> function Base.getproperty(obj::MyType, s::Symbol)
if s == :x
getfield(obj, s)[]
else
getfield(obj, s)
end
end
julia> function Base.setproperty!(obj::MyType, s::Symbol, x)
if s == :x
getfield(obj, :x)[] = x
else
setfield!(value, s, x)
end
end
julia> my_type.x = 3.1
3.1
julia> my_type.x
3.1
julia> my_type
MyType(Base.RefValue{Float64}(3.1), 1)
Extra note:
Base.RefValue{T} is similar to a 0-dimensional array, Array{T, 0}. Both hold a single element.
Accessors.jl lets you do the rebuilding with the simpler syntax of fieldwise mutation. With simpler constructors like the defaults, you won’t need to implement underlying methods to make it work, and the compiler is pretty good at picking structs apart and optimizing constructors so that instantiation doesn’t actually rebuild from scratch. I don’t have any sources comparing performance, but if you don’t need an instance and its changes to be shared by multiple bindings, Accessors lets you avoid the heap allocation overheads of instantiating mutable instances, whether of mutable struct MyType or x::Base.RefValue{Float64}. Such overhead is only significant if you’re instantiating and discarding many mutable instances; nearly zero such overhead occurs at the other extreme where you instantiate 1 MyType instance and mutate it for the rest of the program. I should stress that Accessors still instantiates at each rebuild rather than rote copying, so if the constructor takes a lot of time (you can test this with sleep), then it can be preferable to use mutation to avoid the instantiation overhead.