For everyday use it is sufficient to understand the basics of scoping
rules in Elixir: that there’s the top level scope and function clause
scope, and that named functions have their own peculiar differences from
the more conventional anonymous functions.

But there are, in fact, quite a few rules you need to know to get a
complete picture of the way scopes work in Elixir. In this technical
article we will take a close look at all of the scoping rules and learn
in what ways they differ from Erlang.

Most of the time user code in Elixir is structured in the following way.
At the top level we define modules. Each module contains a number of
attributes and function clauses. Inside a function clause there can be
arbitrary number of expressions including control flow constructs like
case, if, or try:

Those are the two most commonly seen structures for code organisation in
Elixir.

In the general case, however, all scopes are arbitrarily nestable: we
could imagine a case expression inside a comprehension or a
top-level if expression defining different modules depending on some
condition. For example:

In order to understand how the example above works, you should be aware
of the fact the a module definition creates the module as its
side-effect, so the module itself will be available globally. Only the
name of the module is affected by the nesting of the defmodule call
as we’ll see later in this article.

This means that it is possible to determine the scope of every
identifier only by looking at the source code.

All variable bindings introduced in a scope are available until the end
of that scope. Elixir has a few special forms that treat scopes a little
differently (namely require, import, and alias). We will
examine them at the end of this article.

According to the rules of lexical scope, any variables defined in the
surrounding scope are accessible in all other scopes it contains.

In Figure 1 above, any variable defined in the top level scope will
be accessible in the module’s scope and any scope nested inside it, and
so on.

There is an exception to this rule which applies only to named
functions: any variable coming from the surrounding scope has to be
unquoted inside a function clause body.

Any variable in a nested scope whose name coincides with a variable from
the surrounding scope will shadow that outer variable. In other words,
the variable inside the nested scope temporarily hides the variable from
the surrounding scope, but does not affect it in any way.

The top level scope includes every variable and identifier defined
outside of any other scope.

x#=> undefined function: x/0x=1x#=> 1f=fn->xendf.()#=> 1

Named functions cannot be defined at the top level because a named
function always belongs within a module. However, named functions can be
imported into any lexical scope (including the top level scope) like
this:

importString,only:[reverse:1]reverse"Hello"#=> "olleH"

In fact, all functions and macros from the Kernel module are
autoimported in the top level scope by the compiler.

Each function clause defines a new lexical scope: any new variable bound
inside it will not be available outside of that clause:

defmoduleMdodeffoo(x),do:-x# this 'x' is completely independent from the one in 'foo/1'defbar(x),do:2*xx=1# shadowing in action: the 'x' in the argument list creates a variable# local to the function clause's body and has nothing to do with the# previously defined 'x'f=fn(x)->x=x+1endy=f.(x)IO.puts"The correct answer is #{y} == #{f.(x)}"# output: The correct answer is 2 == 2# in this case the argument 'y' shadows the named function 'y/0'defy(y),do:y*2# here the reference to 'y' inside the function# body is actually a recursive call to 'y/0'defy,do:y*2endM.foo3#=> -3M.bar4#=> 8M.y-2#=> -4M.y#=> infinite loop

Apart from named functions, a new function clause scope is created for
each module-like block, anonymous function, try block body, or
comprehension body (see below).

Module scope works just like function clause scope: any variables
defined between defmodule (or defprotocol, etc.) and its
corresponding end will not be accessible outside of the module, but
they will be available in the nested scopes of that module as per usual
(modulo the unquoting caveat of named functions mentioned above).

It is important to understand that a module’s scope exists as long as it
is being compiled. In other words, variables are not “compiled into” the
module. The Module.function syntax is only applicable to named
functions and that’s another thing that makes such functions special:

You may be wondering how local function calls work when named functions
don’t produce name bindings and don’t have direct access to the
surrounding scope. The answer to this lies in the following rule
followed by Elixir when trying to resolve an identifier to its value:

One more note about module naming and nested modules: modules are always
defined at the top level, no matter in what scope the actual call to
defmodule is located. This means that as long the VM can find the
.beam file with the module’s code at run time, it does not matter in
which scope you reference that module’s name.

What the scoping does affect is the name the module will get:

defmodulePdo# The actual module name will be P.Q, but it is implicitly aliased to Q# in P's scopedefmoduleQdodefq(false),do:"sorry"defq(true)do# The actual module name will be P.Q.MdefmoduleMdodefsay,do:"hi"endendend# Q is resolved to P.QdeffoodoQ.qfalseend# At run time, this has the same exact implementation as foodefbardoP.Q.qfalseendendP.foo#=> "sorry"P.bar#=> "sorry"P.Q.qfalse#=> "sorry"# the module hasn't been defined yetP.Q.M.say#=> undefined function: P.Q.M.say/0# after this call the P.Q.M module will become availableP.Q.qtrueP.Q.M.say#=> "hi"

any variable introduced in a clause pattern/condition will be
accessible only within that clause’s body

any variable introduced inside some (but not all) clause bodies will
become available in the surrounding scope (possibly with the default
nil value)

Here are some examples of those rules in action:

casexdo# both 'result' and 'a' are visible only within this clause's body{:ok,result}=a->IO.inspect(result);a# 'error' is actually bound in the surrounding scope; its value will be nil# if 'x' does not match :error:error->error=true# ordinary shadowing: this 'x' is visible only within the clause's body and# it doesn't affect the 'x' from the surrounding scope[x]->IO.inspect(x)endresult#=> undefined function: result/0a#=> undefined function: a/0error#=> true if x == :error, otherwise nil

Note: due to a bug in the 0.12.x series, cond‘s conditions actually
leak bindings to the surrounding scope. This should be fixed in 0.13.1.

conddoa0=false->a=a0b=1->bc=2->c=2true->d=3enda#=> false (bound to false inside the 1st condition's body)b#=> undefined function: b/0c#=> nil (the 2nd condition is truthy, so `c = 2` was not evaluated)d#=> nil (the body with `d = 3` was not evaluated,# so 'd' also leaks with the default value)

All of the rules described so far apply to variable bindings. When it
comes to one of these three special forms, their effect persists until
the end of the do block they are called in. Effectively, those forms
see a slightly different scope division in which control flow constructs
create a new lexical scope:

# top level scopedefmoduleMdo# new scopeimportString,only:[reverse:1]deffoodo# new scopeimportString,only:[strip:1]IO.putsreverse("abc")# ok: inherited from the surrounding scopeiftruedo# new scopeimportString,only:[downcase:1]else# new scopeimportString,only:[upcase:1]end" hello "|>strip# ok: made local in the current scope with 'import'|>downcase# error: no local function downcase/1|>upcase# dittoenddefbardo# new scopeIO.putsreverse("abc")# ok: inherited from the surrounding scopestrip(" hello ")# error: no local function strip/1endend

Most of the scoping rules described here have been inherited from
Erlang.

One notable difference is that modules simply contain forms and function
clauses, they don’t have scope nor allow arbitrary expressions like
modules in Elixir do.

There are two differences in the way case clause scope works in Erlang:

both bindings introduced in the pattern and in the body of a clause
modify the surrounding scope

those variables that are bound in some (but not all) of the clauses
will remain unbound in the surrounding scope (instead of getting the
nil value like they do in Elixir); they are also called unsafe
variables

There is an if construct in Erlang that looks similar to cond,
but works differently. It only allows guard expressions as conditions
and those do not let you introduce variable bindings. Variables bound in
clause bodies leak to the surrounding scope the same way they do in
case.