Local and global variables with the same name: ERROR: UndefVarError: x not defined

In REPL

julia> x = randn(3);

julia> function add_one()
           x = x .+ 1
           @show x
       end
add_one (generic function with 1 method)

julia> add_one()
ERROR: UndefVarError: x not defined
Stacktrace:
 [1] add_one() at .\REPL[2]:2
 [2] top-level scope at REPL[3]:1
  • How to explain the above error?
  • What should I do if I want to create a local variable with the same name as the global one (though it seems to be a bad practice)?

The issue is that when you write

x = x .+ 1

in your function, you are creating a new local variable with x =, so it ignores the “global” x = randn(3) you created outside, and then you are computing x .+ 1 which assumes that x already exists.

You could do

x .= x .+ 1

and this will modify your outside x, or, better yet, also pass x as an argument and avoid any confusion:

function add_one!(x) # note x is an argument, and the ! indicates that this will mutate x
    x .= x .+ 1 # note the . in front of the assignment symbol =
    @show x
end

You could do something like let x = x .+ 1 to assign a new x but I think that’d be confusing more than anything, since it really looks like you are trying to modify x.

3 Likes

I think no amount of clarification / keywords can make the following fact not confusing to the reader:

you have x on both left and right of = and one of them is local the other is global


I would imagine the least confusing syntax (doesn’t work in Julia right now) would be something like this:

x = global x .+ 1

or

local x = global x .+ 1

but it’s just, kinda annoying to look at with the length and information you need to process

2 Likes

Within a scope, an identifier may be resolved either as local or as global.
By default, if an identifier is encountered on the left-hand side of an assignment statement, its meaning is looked up in all enclosing scopes, excluding the global one (module scope), and a new binding is created if none is found. If an identifier only occurs in the right-hand sides of assignments, then it is searched for in all enclosing scopes, including the global one.

Here, you have the same identifier on both sides of an assignment. Then, the first rule takes precedence: x does not exist in any of the enclosing local scopes, so it is local, and we must evaluate the rhs of the statement and bind x to the result. But there is also x in the rhs, which is undefined, so there’s an error.

Now, what to do.
a) It is perfectly fine to name local variables the same as globals, not needing to bother about the existence of a local name somewhere else in the program is one of the biggest virtues of scopes.
b) If you want to create a new variable x and use the global x in the same function, there’s let to help:

function add_one()
    let x_glob = x, x = x_glob .+ 1
        @show x
    end
end

The assignments in let are in nested scopes, so x_glob = x is evaluated in one scope, x = x_glob .+ 1 in another, and there’s no clash.
c) If you want to bind the global to another value, mark x as global anywhere in the function body:

function add_one()
    x = x .+ 1
    global x
    @show x
end

d) If you want to mutate a global, just use some mutating function on x as argument (like x .= x .+ 1, as suggested by @briochemc , because it is a syntactic sugar to broadcast!).

2 Likes