Looping over struct fieldnames in a type-stable way

I have a function that updates all fields of a struct in a field-specific way. Here an MWE:

julia> mutable struct Foo
           a
           b
end

julia> calc_new_value(x, y) = x*y
calc_new_value (generic function with 1 method)

julia> function update!(foo, rules)
           for name in fieldnames(Foo)
             setfield!(foo, name, calc_new_value(getfield(foo,name), getfield(rules,name)) )
          end
end
update! (generic function with 1 method)

julia> foo = Foo(1,2)
Foo(1, 2)

julia> update!(foo, (a=1, b=2))

julia> foo
Foo(1, 4)

where rules may be a struct, a NamedTuple, or something else. I mean “may” not in the sense that the code must be flexible enough to deal with all possibilities but in the sense that I can make the best design choice here.

Right now, update!() is not type-stable.

I think there’s some way I can use @generated to make it type-stable but I can’t figure out how to do that. Any advice?

Foo’s fields are implicitly annotated ::Any so they must be type-unstable. I checked and if you annotate the fields with ::Int, the example becomes type-stable.

1 Like

You’re right, of course! My MWE was a bit too simple.

A more realistic scenario – and the one I actually face – declares Foo as:

mutable struct Foo2{T}
    a::T
    b::T
end

This still yields instability when looping over fieldnames(Foo2) because T isn’t known until an instance of Foo2 is created.

But looping over propertynames(foo) works to get rid of instability.

Thanks for pointing me in the right direction!

Oh that’s interesting, but not exactly what you said. fieldnames ideally works for Foo2 the same way it did for Foo because the field names are the same. Thing is, Foo is a concrete type, an instance of DataType, while Foo2 is a parametric type, an instance of UnionAll. Before computing the field names, unwrap_unionall extracts a DataType from the UnionAll’s body::Any field, hence the uninferrability. I’m not sure if the method or field could be patched without causing problems.

Your fix is a good idea, but use fieldnames(typeof(foo)) to work with fields instead of properties. Those can diverge, for example public properties computed from private fields. Since you’re setfield!ing, it makes more sense to work with fields.

1 Like