I find this behaviour very fortunate, but also black magic:
a = [5]
u = [a, a, a]
v = deepcopy(u)
v[1][1] = 8
v # [[8], [8], [8]] # My naive 'deepcopy' would have produced [[8], [5], [5]] instead.
I expected deepcopy to be naive and produce three independent copies of a within v . But it seems much smarter and somehow figures that all three values in u are aliases of each other and then it reproduces the same aliasing relations within v . This is very fortunate, but how does it do so?
Calling deepcopy on an object should generally have the same effect as serializing and then deserializing it.
But again, my naive u -> serialize -> file -> deserialize -> v roundtrip would have produced three independent values within v, and the result would have been [[8], [5], [5]] again.
Is it specified which kind of “serialization” is clever enough for specifying deepcopy?
I looked at it sometime ago, if I remember correctly the object is explored in a recursive fashion and an IdDict is used to keep track of which objects have already been seen so that to be able to preserve object identities.
This IdDict trick is also how Functors.jl works, to handle elements appearing more than once. For your serialisation, you could consider doing something like this, to keep only one copy of a before proceeding:
julia> a = [5];
julia> u = [a, a, a];
julia> fmapstructure(identity, u, prune=nothing)
3-element Vector{Union{Nothing, Vector{Int64}}}:
[5]
nothing
nothing