The Manual states the following restrictions:
- Generated functions are only permitted to call functions that were defined before the definition of the generated function. (Failure to follow this may result in getting
MethodErrorsreferring to functions from a future world-age.)- Generated functions must not mutate or observe any non-constant global state (including, for example, IO, locks, non-local dictionaries, or using
hasmethod). This means they can only read global constants, and cannot have any side effects. In other words, they must be completely pure. Due to an implementation limitation, this also means that they currently cannot define a closure or generator.
I can’t observe world age issues if I put a yet-to-be-defined function in the generated code:
julia> @generated foo(A) = :(bar(A))
foo (generic function with 1 method)
julia> bar(x) = x
bar (generic function with 1 method)
julia> foo([1,2])
2-element Vector{Int64}:
1
2
In practice, they seem to be used to mutate input array arguments e.g. RuntimeGeneratedFunctions.jl README, and I can’t trigger the purity error if I mutate an input array or a global array. I need to write a nested function to trigger the error at compilation, and it doesn’t even need to involve any globals or mutation. Is this undefined behavior?
julia> @generated zerofirst1(A) = :(A[1] = zero(eltype(A)); A) # mutate input array
zerofirst1 (generic function with 1 method)
julia> zerofirst1([1,2])
2-element Vector{Int64}:
0
2
julia> B::Vector{Int} = [0]
1-element Vector{Int64}:
0
julia> @generated zerofirst2(A) = :(A[1] = B[1]; A) # also access global array
zerofirst2 (generic function with 1 method)
julia> zerofirst2([1,2])
2-element Vector{Int64}:
0
2
julia> @generated writeB(A) = :(B[1] = A[1]; B) # mutate global array
writeB (generic function with 1 method)
julia> writeB([1,2])
1-element Vector{Int64}:
1
julia> @generated getB(A) = :((x->B)(A)) # nested function accesses, not captures, global
getB (generic function with 1 method)
julia> getB([1,2])
ERROR: The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator.
Stacktrace:
[1] top-level scope
@ REPL[29]:1
julia> @generated donothing(A) = :((x->x)(A)) # nested function does not capture
donothing (generic function with 1 method)
julia> donothing([1,2])
ERROR: The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator.
Stacktrace:
[1] top-level scope
@ REPL[39]:1