The behavior of a() is due to the way closure works, as all the functions in funcs will return the local variable i as it updates. The second scenario is a bit different because the iterator variable i is new for each iteration. Besides @jling recommendation, itβs also possible to use the let block for the first case.
# a is generated with the second loop
julia> a[1] === a[2]
false
julia> typeof(a[1]) === typeof(a[2])
true
julia> a[1]
#1 (generic function with 1 method)
julia> a[1]()
1
julia> a[2]()
2
Itβs just that they box different values:
julia> dump(a[1])
#1 (function of type var"#1#2"{Int64})
i: Int64 1
julia> dump(a[2])
#1 (function of type var"#1#2"{Int64})
i: Int64 2
Remember, anonymous functions are implemented as callable structs, where each variable that is captured refers to a field. The second loop creates multiple different instances of the same struct, which capture different values in their single field. The first one captures a reference to the same value/binding.