Generic implementation of Base.copy method for custom Types (using iternation over fieldnames?)

Hello,

This question has probably been answered several times, but oddly enough this morning I can’t get the proper answer.

Context: I have a code base with several mutable custom parametric types (i.e. structures which in the application context I call “components”), each having quite many fields (actual code base is Microgrids.jl/src/components.jl at main · Microgrids-X/Microgrids.jl · GitHub):

mutable struct CompA
    p1::Float64
    #...
    pn::Float64
end
# CompB, CompC...
mutable struct CompZ
    p1::Float64
    #...
    pn::Float64
end

Then on one morning I realize I may need an implementation of the Base.copy method. For one component, I write it manually, but then I realize I may need to do it for all of them…

I found a discussion about iterating over fieldnames in How to copy all fields without changing the referece?, where a warning is given about a performance impact (and I’m not impacted by performance issue for my potential usage of copy). But oddly enough, I didn’t find a generic recipe to avoid manually typing each field name.

Here is a recipe I ended up with:

function Base.copy(c::CompA)
    T = typeof(c)
    args = (getfield(c, name) for name in fieldnames(T))
    return T(args...)
end

I can duplicate this definition for each component for which I need copy. Also, I think that this copy definition is generic enough so that if some types inherit from one common super type, I just need to define it for the supertype, correct?

I’ll be glad to get feedback on the topic, or simply links to relevant previous discussions. I also had in mind there existed a package with macros specifically meant for this use case.

I was too optimistic about the handling of sibling types. I believe the correct way to handle all the inherited types of some SuperComp supertype is instead:

function Base.copy(c::Comp) where {Comp <: SuperComp}
    T = typeof(c)
    args = (getfield(c, name) for name in fieldnames(T))
    return T(args...)
end

This should be both generic and performant:

using ConstructionBase

Base.copy(x::YourType) = constructorof(typeof(x))(getfields(x)...)

Thanks a lot for the reference to GitHub - JuliaObjects/ConstructionBase.jl: Primitives for construction of objects which I didn’t know.

In the end, I’ll probably trade performance against not adding an extra dependency, but it’s still interesting to see how the specific methods were implemented:

So I see that for the constructor, the “secret sauce” is to return Base.typename(T).wrapper rather than T itself. I didn’t know this syntax, and I have no idea why it may be more performant.

For getfields, I understand that the key point is to construct a type-specific NamedTuple to host the arguments which looks pretty neat.