StackOverflowError with locally defined function

function main()

    function sub()
        fun(f) = f
        return x -> fun(x)
    end

    fun = sub()
    fun(1)
end

A call to main() results in a stack overflow. If sub() returned fun instead of x -> fun(x) or the assignment in main() was fun2 = sub() or calling sub()(1) all would be OK.

Is this construct against Julia’s grammar?

No, it is parsed fine and I think it is executed correctly.

What happens is that sub takes fun from the scope of main, which you happen to overwrite, so it ends up being recursive.

Interesting that this happens even if fun is first assigned/defined after the local function assignment/definition. I would assume that any binding I assign a value inside a local function which does not exist yet in the outer scope will end up being local to the local function, not that it “retroactively” makes the binding in the local function to refer to the binding in the outer scope.

This just happen if fun is assigned in the outer scope? It works as I expect if I just try to print the binding in the outer scope.

function main()

    function sub()
        fun(f) = f 
        return x -> fun(x)
    end 

    show(fun)
    sub()
end

main()

In other words, this says fun is not defined. Counter-intuitive for me that defining a binding at any point of the scope makes it available before it was defined in some cases (i.e., to be captured by a local function) but not change the answer of @isdefined.

function main()

    function sub()
        fun(f) = f
        return x -> fun(x)
    end

    println(@isdefined(fun))
    fun = sub()
    fun(1)
end

This outputs false before the stack overflow.

1 Like

This is the documented behavior: right in the beginning of the relevant chapter, you will find that

The scope of a variable cannot be an arbitrary set of source lines; instead, it will always line up with one of these blocks.

This is later elaborated with examples.

This is actually a very clean way of thinking about scope: relying on the order of things happening can get chaotic pretty quickly (think of branches, etc).

I think it is best to avoid writing code like this, it can become very hard to reason about.

As pointed out by @Tamas_Papp, the docs are explicit about nested functions and their parent’s scope:

Note that nested functions can modify their parent scope’s local variables:

1 Like