Here’s my understanding:
In version 0.6, Julia now keeps track of all the callers of a given function (called backedges) so it can go back and invalidate the previous code with any updated methods. This invalidation can only happen at “safe” points within the process — you cannot trash a piece of currently running code and expect everything to go well. You cannot even go back and update a function pointer since the old method be inlined or the new one might change the inferred optimizations. So the old code isn’t trashed completely; a new version is created alongside it. Of course, this means that Julia needs to manage all these different versions of the same code and which should be called at any given point. This is the world age. Any running code is frozen in its world age — even though the Julia process might have gone on to define more methods later.
A simple example with tasks:
julia> f() = 1
t = @task while true
try
@show f(1)
catch ex
@show ex
Base.showerror(stdout, ex) # showerror is what the REPL uses to display errors
end
wait()
end
Task (runnable) @0x000000011303a1d0
julia> schedule(t); sleep(1); # Compiles and runs the task, freezing its world age
ex = MethodError(f, (1,), 0x0000000000005535)
MethodError: no method matching f(::Int64)
Closest candidates are:
f() at REPL[1]:1
julia> f(::Int) = 2
f (generic function with 2 methods)
julia> schedule(t); sleep(1);
ex = MethodError(f, (1,), 0x0000000000005535)
MethodError: no method matching f(::Int64)
The applicable method may be too new: running in world age 21813, while current world is 21814.
Closest candidates are:
f(::Int64) at REPL[3]:1 (method too new to be called from this world context.)
f() at REPL[1]:1
That third argument in the MethodError
? That’s the world age. The REPL error display knows about world ages and explicitly knows it should look “into the future” and see if any methods have been defined in a newer age. That’s how it’s able to helpfully tell you that a newer method is defined even though your existing code refuses to execute it. I always hate it when a computer knows what you’re trying to do but refuses to do it, but in this case it’s the other way around: it refused to do some action first and then looked to see if that might be the problem. And in fact, this refusal is one of the things that allows Julia to make some of its best optimizations.