Scope Question (Maybe Bug?)

I have an example that has me confused about scope. I define a variable current_header which is inside a for loop. My understanding is this variable should be in the scope of the for loop. Furthermore this example works when running in the Debugger, but not in plain Julia. Here is the code:

scope_question.jl

using Debugger

struct FilePart
    header::String
    key::String
    value::String
end

function processfile(lines::Vector{String})
    file = FilePart[]

    for line in lines
        if startswith(line, "[") & endswith(line, "]")
            current_header = line

            println("Header: $current_header")
        else
            parts = split(line, "=")
            key = strip(parts[1])
            value = strip(parts[2])

            println("key: $key | value: $value")

            push!(file, FilePart(current_header, key, value))
        end
    end

    print("finished with $(size(file,1)) values")
    return file
end


lines = [
    "[Test]"
    "key1=value1"
    ]

println("Running in Debugger...")
@run processfile(lines)
println("*")

println()
println("Running Outside Debugger...")
processfile(lines)
println("*")

And the output I get is:

julia> include(“scope_question.jl”)
Running in Debugger…
Header: [Test]
key: key1 | value: value1
finished with 1 values*

Running Outside Debugger…
Header: [Test]
key: key1 | value: value1
ERROR: LoadError: UndefVarError: current_header not defined

As can be seen I get an UndefVarError for current_header. What am I missing about scope here?

(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)

But why? As I understand a for loop has a scope and current_header is defined in that scope. So when I attempt to use it in the same for loop shouldn’t it be defined?

It doesn’t have to be global (in fact, it shouldn’t be!), but it has to be defined outside the loop. The reason is that each iteration of a for loop is wrapped in a let block. The variables defined inside it exist only until the end of the iteration.

4 Likes

From https://docs.julialang.org/en/v1/manual/variables-and-scoping/#Loops-and-Comprehensions:

In loops and comprehensions, new variables introduced in their body scopes are freshly allocated for each loop iteration, as if the loop body were surrounded by a let block.

This will not work for instance

function foo()
    for i in 1:5
        if i == 1
            x = 42
        else
            @show x
        end
    end
end
3 Likes

Ah, OK, thank you @tomerarnon!

Do you know why it works in Debugger.jl? Just curious.

Thanks @FedericoStra, I missed that part of the documentation.

No, I don’t honestly. Debugger works differently than normal execution (using an interpreter in many places) and maybe defines it’s own scope with its own variables. It’s worth checking if that behavior has been reported there already, and if not filing an issue.

The most obvious approach is to simply declare x in the scope of the function:

function foo()
    local x
    for i in 1:5
        if i == 1
            x = 42
        else
            @show x
        end
    end
end
Edit: here is the previous version of my answer, which contains a wrong statement caused by some kind of hallucinations I must have had while staring at the REPL for too long.

What I find less than desirable is that to make my example work one needs to write something like

function foo()
    x = 0
    for i in 1:5
        if i == 1
            x = 42
        else
            @show x
        end
    end
end

The most obvious approach of simply declaring x in the scope of the function

function foo()
    local x
    for i in 1:5
        if i == 1
            x = 42
        else
            @show x
        end
    end
end

is not sufficient and doesn’t work unfortunately.

2 Likes

Your second foo works fine.

1 Like

Uhm… I don’t know what kind of hallucinations I must have had… :confused: I was probably working in a dirty workspace and used the wrong definition. You are perfectly right of course.

2 Likes