Why won't julia ERROR if a variable is not defined when scheduling?

I come up with these tests to back that statement

function main()
    v = Function[]
    for i = 10:10:20
        for j = i+3:i+4
            push!(v, () -> println("I'm ", j))
            v[end]()
            j = (i)j # modify!
            v[end]()
        end
    end
    println("---see what we got---")
    map(x -> x(), v)
end
main();

function main()
    v = Function[]
    for i = 10:10:20
        for outer j = i+3:i+4
            push!(v, () -> println("I'm ", j))
            v[end]()
        end
        local j = 6i
    end
    j = -9 # irrelevant
    println("---see what we got---")
    map(x -> x(), v)
end
main();

To avoid unintended mutation, do

Update my knowledge here.

Using interpolation, i.e. $, in various macros is literally a rewriting to compute the interpolated expression before whatever the macro does. That is,

@spawn something($expr)

is literally rewritten to something like

...
let uniquename=expr
    t = Task(() -> something(uniquename)
end
...

This is done by Base._lift_one_interp!(...), which works on the syntax tree produced by the parser.

Likewise, a for loop is literally rewritten to a while loop using iterate(...), as described above, and in the docs. Though with unique variable names which your program can’t touch, so it can’t be inadvertently mutated. (Except in an outer for). The result of these rewriting operations can be observed e.g. with the @code_lowered macro, and for the interpolation with a @macroexpand:

julia> @macroexpand Threads.@spawn foo($i)
quote
    #= threadingconstructs.jl:511 =#
    let var"##277" = i
        #= threadingconstructs.jl:512 =#
        local var"#70#task" = Base.Threads.Task((()->begin
                            #= REPL[47]:1 =#
                            foo(var"##277")
                        end))
...

I have a new point of confusion. Open a new topic…