julia> mutable struct S
x::Vector{Matrix{Float64}}
S() = new()
end
julia> s = S();
julia> s
S(#undef)
julia> s.x
ERROR: UndefRefError: access to undefined reference
Stacktrace:
[1] getproperty(::Any, ::Symbol) at .\sysimg.jl:18
julia> s.x = [rand(1000,1000) for i in 1:100]; # takes up memory
Initially the field x is undefined (taking up basically no memory). After the assignment, some piece of memory is assigned to s.x. Is there a nice way to free up this memory again, that is, for example, get s.x back to be #undef?
Outside of a struct, the typical answer seems to be set x = nothing and perhaps call GC.gc() to free the memory. But this doesn’t work for a field of a struct as the memory could still be accessed (it’s still reachable).
I can set s.x = Matrix{Float64}[] and call GC.gc() to free the memory. I’m just wondering if there is a better approach.
To give you some context, I have a couple of costly operations that I perform in-place in some preallocated memory. To that end I have a struct, say Preallocations, whose fields are being used as preallocated memory. However, the memory assigned to some of those fields might be very large and only needed in some operations. After those operations finish I’d like to free the memory again.
You can use ::Union{Nothing, Vector{Matrix{Float64}}}. IIUC its cost will be ~1 byte, and the performance impact should be negligible for most use cases.
A different variant is s.x=Vector{Matrix{Float64}}[]. If you store many S instances and don’t want to pay the jl_array memory price, it might be advisable to have some const _nil = Vector{Matrix{Float64}}[] and set s.x = _nil.
Then you can also initialize with _nil, and check for initialized-ness by s.x === _nil (triple ===, about as fast as issassigned(s, :x) and much faster than == because you avoid dereferencing the pointer).
By making the field s.x always assigned (by not having an inner constructor) you speed up every single access (if the inner constructor permits unassigned s.x then julia emits nullpointer checks on access).
Downside: If your code is buggy, then someone can forget the s.x === _nil check and actually mutate the global _nil. Nobody will protect you from this bug, because you intentionally removed all guardrails.
If you care more about maintainability than raw speed, then Union{Nothing, Vector{Matrix{Float64}}} is probably more appropriate.