Shallow copy of a struct

I have a set of mutable structures that are derived from an abstract type :

T1 <: AbstractT
T2 <: AbstractT
T3 <: AbstractT
...
Tn <: AbstractT

I need to make a backup of an array of instances of these structures. I could write a copy function (shallow copy) for each struct but I would like to avoid it and write a copy function for the abstract type. The problem is that I don’t know how to create an uninitialized instance of a concrete type. Is there a way to instantiate a structure with undefined fields? so later I can make the assignments.
I was thinking in a copy function like this:

function Base.copy(src::AbstractT)
    dst = make_an_uninit_istance_of(typeof(src)) # <= is there a way to do this?
    names = fieldnames(typeof(src))
    for name in names
        val = getfield(src, name)
        setfield!(dst, name, val)
    end
    return dst
end

Is it possible? Any ideas?

1 Like
julia> Base.@pure _fieldcount(::Type{T}) where T = fieldcount(T)

julia> mycopy(instance::typ) where typ = typ(ntuple(i->getfield(instance, i), _fieldcount(typ))...)

julia> using BenchmarkTools

julia> mutable struct foo
       x1::Int
       x2::Float64
       end
julia> arr=[foo(1,2.0) for i=1:1000];
julia> arr2=copy(arr);
julia> @btime arr2 .= mycopy.(arr);
  8.671 μs (1002 allocations: 31.28 KiB)

julia> othercopy(x)=foo(x.x1, x.x2);
julia> @btime arr2 .= othercopy.(arr);
  8.582 μs (1002 allocations: 31.28 KiB)
1 Like

Thanks @foobar_lv2 for the clever suggestion. In my case the structures in the array are of different types (all derived from an abstract type) and have defined constructors thus instantiating by doing typ(args_tuple...) is not available. Also, I’d like to define this copy function for the abstract type in order to avoid writing a copy function for each derived type.

I found that deepcopy.jl uses

    y = ccall(:jl_new_struct_uninit, Any, (Any,), T)

to instantiate an uninitialized instance of T.
Is it ok to use such instruction in a conventional code? Is this interface likely to change in a near future?

I’d warn against taking a naive shallow copies of types with inner constructor. Reason is that inner constructors are used to figure out which fields can be uninitialized, and often contain pointers to external resources that are allocated on construction and cleared by finalizers (memory in external libraries, file descriptors, …). If you take a naive shallow copy then you get into trouble (classic use-after-free: original gets garbage collected, and your copy still holds a reference).

That being said,

julia> ex=:(_genobj(typ)=$(Expr(:new, :typ))); eval(ex)
_genobj (generic function with 1 method)

julia> mutable struct some_typ
       a::Int
       b::Vector{Int}
       end
julia> _genobj(some_typ)
some_typ(1, #undef)

This is somewhat invalid (but your ccall scheme should be just as invalid): We are lying to the compiler about uninitialized fields. The lack of inner constructor told the compiler that b is always initialized; but it isn’t because _genobj is not quite valid julia:

julia> arr=[_genobj(some_typ) for i=1:100];
julia> t(arr,i)=arr[i].b
julia> t(arr,5)

signal (11): Segmentation fault

Just dispatch Base.copy(x::AbstractT) = mycopy(x) for whatever scheme you decide on.

1 Like