Consider a (mutable) struct with a bunch of members, and a constructor:
mutable struct Foo
x::Vector{Int}
y::Vector{Int}
z::Vector{Int}
# ... here more members might come
Foo() = new([1], [2], [3])
end
Now, an easy to make mistake is to swap the order of an argument. Or perhaps someone reorders the struct members but forgets to adjust all constructors. If the permuted arguments happen to have the same type, this can be hard to debug. Also, in general, in the above code, it can be difficult for a human to look at the invocation of new
and decide which argument is which.
As a result, some people (including several collaborators of mine) prefer to write constructors that look like this:
function Foo()
foo = new()
foo.x = [1]
foo.y = [2]
foo.z = [3]
return foo
end
That makes it crystal clear which value is assigned to which struct member, and also is resilient against reordering of struct members. However, it comes at a cost: in the former constructor, Julia is apparently able to detect that x,y,z are always assigned a value, hence never are undef
. It then generates optimal machine code for e.g. a function like mylen(foo::Foo) = length(foo)
. Whereas with the alternate constructor, this breaks down, and it starts to insert null pointer checks into the generated code.
I wonder if there is a way to combine the efficiency with the readability, and would like to hear if people have suggestions for this?
One idea I was having was to add a new macro, say @new
, which allows writing a constructor in keyword arg style: say:
function Foo()
return @new (x = [1], y = [2], z = [3])
end
However, I don’t know how a macro would be able to determine the type of surrounding struct, which it would need to determine the positions of x
, y
, and z
in the struct, so that it can now in which order to pass them to new
. But perhaps the type Foo
could be another argument for the macro.:
function Foo()
return @new Foo(x = [1], y = [2], z = [3])
end
Perhaps people have other ideas / solutions for tackling this?