I think the problem is more that the inner function is both capturing the value of x and modifying it, and modifying a captured variable (which, thus, is not an argument of the function), is like modifying a variable in global scope (I’m not saying that the compiler in these specific examples could not do better, but in any case seems to be a risky operation). You can solve the problem by simply changing the label used in the inner function, separating clearly the labels of the captured values from the variables of the function scope. For example (not sure if a good example):
Modifying the captured value:
julia> function foo(v)
x = 1
bar(v::Int) = x = v
x
end
foo (generic function with 2 methods)
julia> @code_warntype foo([1,2])
Variables
#self#::Core.Const(foo)
v::Vector{Int64}
bar::var"#bar#14"
x@_4::Core.Box
x@_5::Union{}
Body::Any
1 ─ (x@_4 = Core.Box())
│ Core.setfield!(x@_4, :contents, 1)
│ (bar = %new(Main.:(var"#bar#14"), x@_4))
│ %4 = Core.isdefined(x@_4, :contents)::Bool
└── goto #3 if not %4
2 ─ goto #4
3 ─ Core.NewvarNode(:(x@_5))
└── x@_5
4 ┄ %9 = Core.getfield(x@_4, :contents)::Any
└── return %9
Not modifying the captured value:
julia> function foo(v)
x = 1
bar(v::Int) = y = v
x = bar(v)
x
end
foo (generic function with 2 methods)
julia> @code_warntype foo([1,2])
Variables
#self#::Core.Const(foo)
v::Vector{Int64}
bar::var"#bar#15"
x::Int64
Body::Union{}
1 ─ (x = 1)
│ (bar = %new(Main.:(var"#bar#15")))
│ (x = (bar)(v))
└── Core.Const(:(return x))
julia>
I would say that having closures modifying captured values makes the code hard to follow, besides these problems.