Scope of variables inside functions, for loops, and if statments

I don’t understand variable scope in Julia. I wrote a function to read some data files. The program reads nifti (a format for storing medical images) files inside a for loop. The data read is then returned to the calling program.
Unfortunately, the data are of local scope inside the for loop (or the if statement), so I get an UndefVarError. I put global statements everywhere, and I still get the error.
Note: I recently upgraded from Julia 1.3.1-1 to 1.4.1-1. The program worked before the upgrade.
Here’s the code:
make_decmap.jl

using Images
using CSV
using NIfTI
using Glob
function read_amdt(dir)
wd=pwd();
cd(dir)
nii_files=glob("*nii",dir)
global dt
global am
for file in nii_files
    println(file)
    global dt
    global am
    if occursin(r"/[Dd][Tt].*nii",file)
        println("reading $file")
        global dt
        dt=niread(file)
        println(typeof(dt))
    end
    if occursin(r"/[Aa][Mm].*nii",file)
        println("reading $file")
        global am
        am=niread(file)
        println(typeof(am))
    end
    println(typeof(dt))
    println(typeof(am))
end
cd(wd)
return am,dt
end

Note: nifti image files end .nii
Here are the commands I use in the REPL window, and julia’s output:

julia> dir="/mnt/data/PVP/philips/new_calib_test_data_temp_control_100518/asbUncor"
"/mnt/data/PVP/philips/new_calib_test_data_temp_control_100518/asbUncor"

julia> run(`ls $dir`)
AMu.nii  DTu.nii  EGu.nii  EVu.nii  FAu.nii  test  TRu.nii
Process(`ls /mnt/data/PVP/philips/new_calib_test_data_temp_control_100518/asbUncor`, ProcessExited(0))

julia> include("/mnt/data/julia/medical_imaging/make_decmap.jl")
make_decmap (generic function with 1 method)

julia> am,dt=read_amdt(dir)
/mnt/data/PVP/philips/new_calib_test_data_temp_control_100518/asbUncor/AMu.nii
reading /mnt/data/PVP/philips/new_calib_test_data_temp_control_100518/asbUncor/AMu.nii
NIVolume{Float32,3,Array{Int16,3}}
ERROR: UndefVarError: dt not defined
Stacktrace:
 [1] read_amdt(::String) at /mnt/data/julia/medical_imaging/make_decmap.jl:27
 [2] top-level scope at none:0

please format your code;

then, the global keyword is for accessing a global variable inside a function, for example:

a = 1
function main()
    global a
    a = 3
    return nothing
end

you can’t use global to create a global binding that didn’t exist before

1 Like

Does your code even need am and dt to be defined in the outer scope?
This should work fine:

function read_amdt(dir)
    wd = pwd()
    let am, dt
        try
            cd(dir)
            nii_files = glob("*nii",dir)
            for file in nii_files
                println(file)
                if occursin(r"/[Dd][Tt].*nii",file)
                    println("reading $file")
                    dt = niread(file)
                    println(typeof(dt))
                end
                if occursin(r"/[Aa][Mm].*nii",file)
                    println("reading $file")
                    am = niread(file)
                    println(typeof(am))
                end
                println(typeof(dt))
                println(typeof(am))
            end
        finally
            cd(wd)
        end
        return am, dt
    end
end
1 Like

I got the program to work. I find the way julia handles the scope of variables quite unintuitive.

In every other language I’ve written code in, all variables defined in functions are local to the function unless declared as global (or included in a common block). I therefore expected the following code to work: (version 1)
function read_amdt(dir)
wd=pwd();
cd(dir)
nii_files=glob("*nii",dir)
cd(wd)
for file in nii_files
if occursin(r"/[Dd][Tt].*nii",file)
dt=niread(file)
end
if occursin(r"/[Aa][Mm].*nii",file)
am=niread(file)
end
end
return am,dt
end

But this code throws the error
ERROR: UndefVarError: dt not defined
at the line
return am,dt.

Variables defined in “for” loops are local to the “for” loop.
To fix this, I added global statements inside the “for” loop.
I interpreted the julia documentation on scope to mean that declaring a variable as “global” in a loop extends its scope to the context in which the “for” loop is embedded, so in this case it would be local to the function. So my next attempt included global statements inside the “for” loops: (version 2)

function read_amdt(dir)
wd=pwd();
cd(dir)
nii_files=glob("*nii",dir)
cd(wd)
for file in nii_files
if occursin(r"/[Dd][Tt].*nii",file)
global dt
dt=niread(file)
end
if occursin(r"/[Aa][Mm].*nii",file)
global am
am=niread(file)
end
end
return am,dt
end

This version doesn’t throw an error and does return the desired images, but the scope of dt and am is not restricted to the function, but is really global. So the function has the side effect of defining variables dt and am in the context in which the function is called. Executing
out1,out2=read_amdt(dir)
not only sets variables out1 and out2 to the desired images, but also sets variables am and dt.
Again, not the behavior I wanted or expected.

For my next attempt I added “local” statements after the function call: (version 3)
function read_amdt(dir)
wd=pwd();
cd(dir)
nii_files=glob("*nii",dir)
cd(wd)
local am
local dt
for file in nii_files
if occursin(r"/[Dd][Tt].*nii",file)
global dt
dt=niread(file)
end
if occursin(r"/[Aa][Mm].*nii",file)
global am
am=niread(file)
end
end
return am,dt
end

This version failed to compile, throwing the error
ERROR: LoadError: syntax: global dt: dt is a local variable in its enclosing scope.

I finally got a version to do what I expected by removing the global statements and keeping the local statements:(version 4)
function read_amdt(dir)
wd=pwd();
cd(dir)
nii_files=glob("*nii",dir)
cd(wd)
local am
local dt
for file in nii_files
if occursin(r"/[Dd][Tt].*nii",file)
dt=niread(file)
end
if occursin(r"/[Aa][Mm].*nii",file)
am=niread(file)
end
end
return am,dt
end

I hope this posting will be helpful to someone else; I’m sure I’m not the only person who finds the way variable scope is handled in julia very confusing.

Declaring something global makes it global, not local to the function. You can either use a let like @Vasily_Pisarev did or put local am, dt at the top of the function body to declare those variables in the outermost scope of the function instead of the loop body where they are assigned.

Please quote your code: Please read: make it easier to help you - #11

2 Likes

Thanks for your code sample. I didn’t know about let, try, and finally.
I’m still not quite sure what the difference between try and local is.

try ... catch ... finally is a way of dealing with operations that may throw an error.
In your case, the function changes the directory, processes some files and then goes back to the original directory. try ... finally cd(wd) end ensures that it goes back to the initial directory even if it failed to read the files.
More on that in the Manual chapter on exception handlng.
Scope has its own Manual chapter as well.