I was writing some code where I need an inner function to be recursive, something like (highly simplified)
function f1(x::Vector{Int64})
function g(i, val)
i <= length(x) || return
g(i + 1, val)
x[i] = val
end
g(1, 1)
x
end
And when I ran it with @code_warntype
I was surprised to see a Core.Box
:
Arguments
#self#::Core.Const(f1)
x::Vector{Int64}
Locals
g@_3::Core.Box
g@_4::Union{}
g@_5::Union{}
Body::Vector{Int64}
1 ─ (g@_3 = Core.Box())
│ %2 = Main.:(var"#g#17")::Core.Const(var"#g#17")
│ %3 = Core.typeof(x)::Core.Const(Vector{Int64})
│ %4 = Core.apply_type(%2, %3)::Core.Const(var"#g#17"{Vector{Int64}})
│ %5 = %new(%4, x, g@_3)::var"#g#17"{Vector{Int64}}
│ Core.setfield!(g@_3, :contents, %5)
│ %7 = Core.isdefined(g@_3, :contents)::Bool
└── goto #3 if not %7
2 ─ goto #4
3 ─ Core.NewvarNode(:(g@_5))
└── g@_5
4 ┄ %12 = Core.getfield(g@_3, :contents)::Any
│ (%12)(1, 1)
└── return x
From the message it appears that the type instability is caused by g
trying to capture g
itself in its defining environment. This causes a self-referential type to be created and confuses the compiler.
My temporary solution is to use
function f2(x::Vector{Int64})
function h(i, val, self)
i <= length(x) || return
self(i + 1, val, self)
x[i] = val
end
h(1, 1, h)
x
end
then h
would not need to capture itself. This does prevents some memory allocations, but I am unsure if this is the fastest way.
Is there a better way to work around this issue? (while keeping the recursion. In the actual code it is very difficult to convert the recursion into a loop)