Modules in Julia help organize code into coherent units. They are delimited syntactically insidemodule NameOfModule ... end
, and have the following features:
Modules are separate namespaces, each introducing a new global scope. This is useful, because it allows the same name to be used for different functions or global variables without conflict, as long as they are in separate modules.
Modules have facilities for detailed namespace management: each defines a set of names itexport
s and marks aspublic
, and can import names from other modules withusing
andimport
(we explain these below).
Modules can be precompiled for faster loading, and may contain code for runtime initialization.
Typically, in larger Julia packages you will see module code organized into files, eg
module SomeModule# export, public, using, import statements are usually here; we discuss these belowinclude("file1.jl")include("file2.jl")end
Files and file names are mostly unrelated to modules; modules are associated only with module expressions. One can have multiple files per module, and multiple modules per file.include
behaves as if the contents of the source file were evaluated in the global scope of the including module. In this chapter, we use short and simplified examples, so we won't useinclude
.
The recommended style is not to indent the body of the module, since that would typically lead to whole files being indented. Also, it is common to useUpperCamelCase
for module names (just like types), and use the plural form if applicable, especially if the module contains a similarly named identifier, to avoid name clashes. For example,
module FastThingsstruct FastThing ...endend
Namespace management refers to the facilities the language offers for making names in a module available in other modules. We discuss the related concepts and functionality below in detail.
Names for functions, variables and types in the global scope likesin
,ARGS
, andUnitRange
always belong to a module, called theparent module, which can be found interactively withparentmodule
, for example
julia> parentmodule(UnitRange)Base
One can also refer to these names outside their parent module by prefixing them with their module, egBase.UnitRange
. This is called aqualified name. The parent module may be accessible using a chain of submodules likeBase.Math.sin
, whereBase.Math
is called themodule path. Due to syntactic ambiguities, qualifying a name that contains only symbols, such as an operator, requires inserting a colon, e.g.Base.:+
. A small number of operators additionally require parentheses, e.g.Base.:(==)
.
If a name is qualified, then it is alwaysaccessible, and in case of a function, it can also have methods added to it by using the qualified name as the function name.
Within a module, a variable name can be “reserved” without assigning to it by declaring it asglobal x
. This prevents name conflicts for globals initialized after load time. The syntaxM.x = y
does not work to assign a global in another module; global assignment is always module-local.
Names (referring to functions, types, global variables, and constants) can be added to theexport list of a module withexport
: these are the symbols that are imported whenusing
the module. Typically, they are at or near the top of the module definition so that readers of the source code can find them easily, as in
julia> module NiceStuff export nice, DOG struct Dog end # singleton type, not exported const DOG = Dog() # named instance, exported nice(x) = "nice $x" # function, exported end;
but this is just a style suggestion — a module can have multipleexport
statements in arbitrary locations.
It is common to export names which form part of the API (application programming interface). In the above code, the export list suggests that users should usenice
andDOG
. However, since qualified names always make identifiers accessible, this is just an option for organizing APIs: unlike other languages, Julia has no facilities for truly hiding module internals.
Also, some modules don't export names at all. This is usually done if they use common words, such asderivative
, in their API, which could easily clash with the export lists of other modules. We will see how to manage name clashes below.
To mark a name as public without exporting it into the namespace of folks who callusing NiceStuff
, one can usepublic
instead ofexport
. This marks the public name(s) as part of the public API, but does not have any namespace implications. Thepublic
keyword is only available in Julia 1.11 and above. To maintain compatibility with Julia 1.10 and below, use the@compat
macro from theCompat package.
using
andimport
For interactive use, the most common way of loading a module isusing ModuleName
. Thisloads the code associated withModuleName
, and brings
the module name
and the elements of the export list into the surrounding global namespace.
Technically, the statementusing ModuleName
means that a module calledModuleName
will be available for resolving names as needed. When a global variable is encountered that has no definition in the current module, the system will search for it among variables exported byModuleName
and use it if it is found there. This means that all uses of that global within the current module will resolve to the definition of that variable inModuleName
.
To load a module from a package, the statementusing ModuleName
can be used. To load a module from a locally defined module, a dot needs to be added before the module name likeusing .ModuleName
.
To continue with our example,
julia> using .NiceStuff
would load the above code, makingNiceStuff
(the module name),DOG
andnice
available.Dog
is not on the export list, but it can be accessed if the name is qualified with the module path (which here is just the module name) asNiceStuff.Dog
.
Importantly,using ModuleName
is the only form for which export lists matter at all.
In contrast,
julia> import .NiceStuff
bringsonly the module name into scope. Users would need to useNiceStuff.DOG
,NiceStuff.Dog
, andNiceStuff.nice
to access its contents. Usually,import ModuleName
is used in contexts when the user wants to keep the namespace clean. As we will see in the next sectionimport .NiceStuff
is equivalent tousing .NiceStuff: NiceStuff
.
You can combine multipleusing
andimport
statements of the same kind in a comma-separated expression, e.g.
julia> using LinearAlgebra, Random
using
andimport
with specific identifiers, and adding methodsWhenusing ModuleName:
orimport ModuleName:
is followed by a comma-separated list of names, the module is loaded, butonly those specific names are brought into the namespace by the statement. For example,
julia> using .NiceStuff: nice, DOG
will import the namesnice
andDOG
.
Importantly, the module nameNiceStuff
willnot be in the namespace. If you want to make it accessible, you have to list it explicitly, as
julia> using .NiceStuff: nice, DOG, NiceStuff
When two or more packages/modules export a name and that name does not refer to the same thing in each of the packages, and the packages are loaded viausing
without an explicit list of names, it is an error to reference that name without qualification. It is thus recommended that code intended to be forward-compatible with future versions of its dependencies and of Julia, e.g., code in released packages, list the names it uses from each loaded package, e.g.,using Foo: Foo, f
rather thanusing Foo
.
Julia has two forms for seemingly the same thing because onlyimport ModuleName: f
allows adding methods tof
without a module path. That is to say, the following example will give an error:
julia> using .NiceStuff: nicejulia> struct Cat endjulia> nice(::Cat) = "nice 😸"ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extendedStacktrace: [1] top-level scope @ none:0 [2] top-level scope @ none:1
This error prevents accidentally adding methods to functions in other modules that you only intended to use.
There are two ways to deal with this. You can always qualify function names with a module path:
julia> using .NiceStuffjulia> struct Cat endjulia> NiceStuff.nice(::Cat) = "nice 😸"
Alternatively, you canimport
the specific function name:
julia> import .NiceStuff: nicejulia> struct Cat endjulia> nice(::Cat) = "nice 😸"nice (generic function with 2 methods)
Which one you choose is a matter of style. The first form makes it clear that you are adding a method to a function in another module (remember, that the imports and the method definition may be in separate files), while the second one is shorter, which is especially convenient if you are defining multiple methods.
Once a variable is made visible viausing
orimport
, a module may not create its own variable with the same name. Imported variables are read-only; assigning to a global variable always affects a variable owned by the current module, or else raises an error.
as
An identifier brought into scope byimport
orusing
can be renamed with the keywordas
. This is useful for working around name conflicts as well as for shortening names. For example,Base
exports the function nameread
, but the CSV.jl package also providesCSV.read
. If we are going to invoke CSV reading many times, it would be convenient to drop theCSV.
qualifier. But then it is ambiguous whether we are referring toBase.read
orCSV.read
:
julia> read;julia> import CSV: readWARNING: ignoring conflicting import of CSV.read into Main
Renaming provides a solution:
julia> import CSV: read as rd
Imported packages themselves can also be renamed:
import BenchmarkTools as BT
as
works withusing
only when a single identifier is brought into scope. For exampleusing CSV: read as rd
works, butusing CSV as C
does not, since it operates on all of the exported names inCSV
.
using
andimport
statementsWhen multipleusing
orimport
statements of any of the forms above are used, their effect is combined in the order they appear. For example,
julia> using .NiceStuff # exported names and the module namejulia> import .NiceStuff: nice # allows adding methods to unqualified functions
would bring all the exported names ofNiceStuff
and the module name itself into scope, and also allow adding methods tonice
without prefixing it with a module name.
Consider the situation where two (or more) packages export the same name, as in
julia> module A export f f() = 1 endAjulia> module B export f f() = 2 endB
The statementusing .A, .B
works, but when you try to callf
, you get an error with a hint
julia> using .A, .Bjulia> fERROR: UndefVarError: `f` not defined in `Main`Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from.
Here, Julia cannot decide whichf
you are referring to, so you have to make a choice. The following solutions are commonly used:
Simply proceed with qualified names likeA.f
andB.f
. This makes the context clear to the reader of your code, especially iff
just happens to coincide but has different meaning in various packages. For example,degree
has various uses in mathematics, the natural sciences, and in everyday life, and these meanings should be kept separate.
Use theas
keyword above to rename one or both identifiers, eg
julia> using .A: f as fjulia> using .B: f as g
would makeB.f
available asg
. Here, we are assuming that you did not useusing A
before, which would have broughtf
into the namespace.
When the names in questiondo share a meaning, it is common for one module to import it from another, or have a lightweight “base” package with the sole function of defining an interface like this, which can be used by other packages. It is conventional to have such package names end in...Base
(which has nothing to do with Julia'sBase
module).
Modules automatically containusing Core
,using Base
, and definitions of theeval
andinclude
functions, which evaluate expressions/files within the global scope of that module.
If these default definitions are not wanted, modules can be defined using the keywordbaremodule
instead (note:Core
is still imported). In terms ofbaremodule
, a standardmodule
looks like this:
baremodule Modusing Baseeval(x) = Core.eval(Mod, x)include(p) = Base.include(Mod, p)...end
If evenCore
is not wanted, a module that imports nothing and defines no names at all can be defined withModule(:YourNameHere, false, false)
and code can be evaluated into it with@eval
orCore.eval
:
julia> arithmetic = Module(:arithmetic, false, false)Main.arithmeticjulia> @eval arithmetic add(x, y) = $(+)(x, y)add (generic function with 1 method)julia> arithmetic.add(12, 13)25
There are three important standard modules:
Core
contains all functionality "built into" the language.Base
contains basic functionality that is useful in almost all cases.Main
is the top-level module and the current module, when Julia is started.By default Julia ships with some standard library modules. These behave like regular Julia packages except that you don't need to install them explicitly. For example, if you wanted to perform some unit testing, you could load theTest
standard library as follows:
using Test
Modules can containsubmodules, nesting the same syntaxmodule ... end
. They can be used to introduce separate namespaces, which can be helpful for organizing complex codebases. Note that eachmodule
introduces its ownscope, so submodules do not automatically “inherit” names from their parent.
It is recommended that submodules refer to other modules within the enclosing parent module (including the latter) usingrelative module qualifiers inusing
andimport
statements. A relative module qualifier starts with a period (.
), which corresponds to the current module, and each successive.
leads to the parent of the current module. This should be followed by modules if necessary, and eventually the actual name to access, all separated by.
s.
Consider the following example, where the submoduleSubA
defines a function, which is then extended in its “sibling” module:
julia> module ParentModule module SubA export add_D # exported interface const D = 3 add_D(x) = x + D end using .SubA # brings `add_D` into the namespace export add_D # export it from ParentModule too module SubB import ..SubA: add_D # relative path for a “sibling” module struct Infinity end add_D(x::Infinity) = x end end;
You may see code in packages, which, in a similar situation, uses
julia> import .ParentModule.SubA: add_D
However, this operates throughcode loading, and thus only works ifParentModule
is in a package. It is better to use relative paths.
Note that the order of definitions also matters if you are evaluating values. Consider
module TestPackageexport x, yx = 0module Subusing ..TestPackagez = y # ERROR: UndefVarError: `y` not defined in `Main`endy = 1end
whereSub
is trying to useTestPackage.y
before it was defined, so it does not have a value.
For similar reasons, you cannot use a cyclic ordering:
module Amodule Busing ..C # ERROR: UndefVarError: `C` not defined in `Main.A`endmodule Cusing ..Bendend
Large modules can take several seconds to load because executing all of the statements in a module often involves compiling a large amount of code. Julia creates precompiled caches of the module to reduce this time.
Precompiled module files (sometimes called "cache files") are created and used automatically whenimport
orusing
loads a module. If the cache file(s) do not yet exist, the module will be compiled and saved for future reuse. You can also manually callBase.compilecache(Base.identify_package("modulename"))
to create these files without loading the module. The resulting cache files will be stored in thecompiled
subfolder ofDEPOT_PATH[1]
. If nothing about your system changes, such cache files will be used when you load the module withimport
orusing
.
Precompilation cache files store definitions of modules, types, methods, and constants. They may also store method specializations and the code generated for them, but this typically requires that the developer add explicitprecompile
directives or execute workloads that force compilation during the package build.
However, if you update the module's dependencies or change its source code, the module is automatically recompiled uponusing
orimport
. Dependencies are modules it imports, the Julia build, files it includes, or explicit dependencies declared byinclude_dependency(path)
in the module file(s).
For file dependencies loaded byinclude
, a change is determined by examining whether the file size (fsize
) or content (condensed into a hash) is unchanged. For file dependencies loaded byinclude_dependency
a change is determined by examining whether the modification time (mtime
) is unchanged, or equal to the modification time truncated to the nearest second (to accommodate systems that can't copy mtime with sub-second accuracy). It also takes into account whether the path to the file chosen by the search logic inrequire
matches the path that had created the precompile file. It also takes into account the set of dependencies already loaded into the current process and won't recompile those modules, even if their files change or disappear, in order to avoid creating incompatibilities between the running system and the precompile cache. Finally, it takes account of changes in anycompile-time preferences.
If you know that a module isnot safe to precompile (for example, for one of the reasons described below), you should put__precompile__(false)
in the module file (typically placed at the top). This will causeBase.compilecache
to throw an error, and will causeusing
/import
to load it directly into the current process and skip the precompile and caching. This also thereby prevents the module from being imported by any other precompiled module.
You may need to be aware of certain behaviors inherent in the creation of incremental shared libraries which may require care when writing your module. For example, external state is not preserved. To accommodate this, explicitly separate any initialization steps that must occur atruntime from steps that can occur atcompile time. For this purpose, Julia allows you to define an__init__()
function in your module that executes any initialization steps that must occur at runtime. This function will not be called during compilation (--output-*
). Effectively, you can assume it will be run exactly once in the lifetime of the code. You may, of course, call it manually if necessary, but the default is to assume this function deals with computing state for the local machine, which does not need to be – or even should not be – captured in the compiled image. It will be called after the module is loaded into a process, including if it is being loaded into an incremental compile (--output-incremental=yes
), but not if it is being loaded into a full-compilation process.
In particular, if you define afunction __init__()
in a module, then Julia will call__init__()
immediatelyafter the module is loaded (e.g., byimport
,using
, orrequire
) at runtime for thefirst time (i.e.,__init__
is only called once, and only after all statements in the module have been executed). Because it is called after the module is fully imported, any submodules or other imported modules have their__init__
functions calledbefore the__init__
of the enclosing module.
Two typical uses of__init__
are calling runtime initialization functions of external C libraries and initializing global constants that involve pointers returned by external libraries. For example, suppose that we are calling a C librarylibfoo
that requires us to call afoo_init()
initialization function at runtime. Suppose that we also want to define a global constantfoo_data_ptr
that holds the return value of avoid *foo_data()
function defined bylibfoo
– this constant must be initialized at runtime (not at compile time) because the pointer address will change from run to run. You could accomplish this by defining the following__init__
function in your module:
const foo_data_ptr = Ref{Ptr{Cvoid}}(0)function __init__() ccall((:foo_init, :libfoo), Cvoid, ()) foo_data_ptr[] = ccall((:foo_data, :libfoo), Ptr{Cvoid}, ()) nothingend
Notice that it is perfectly possible to define a global inside a function like__init__
; this is one of the advantages of using a dynamic language. But by making it a constant at global scope, we can ensure that the type is known to the compiler and allow it to generate better optimized code. Obviously, any other globals in your module that depends onfoo_data_ptr
would also have to be initialized in__init__
.
Constants involving most Julia objects that are not produced byccall
do not need to be placed in__init__
: their definitions can be precompiled and loaded from the cached module image. This includes complicated heap-allocated objects like arrays. However, any routine that returns a raw pointer value must be called at runtime for precompilation to work (Ptr
objects will turn into null pointers unless they are hidden inside anisbits
object). This includes the return values of the Julia functions@cfunction
andpointer
.
Dictionary and set types, or in general anything that depends on the output of ahash(key)
method, are a trickier case. In the common case where the keys are numbers, strings, symbols, ranges,Expr
, or compositions of these types (via arrays, tuples, sets, pairs, etc.) they are safe to precompile. However, for a few other key types, such asFunction
orDataType
and generic user-defined types where you haven't defined ahash
method, the fallbackhash
method depends on the memory address of the object (via itsobjectid
) and hence may change from run to run. If you have one of these key types, or if you aren't sure, to be safe you can initialize this dictionary from within your__init__
function. Alternatively, you can use theIdDict
dictionary type, which is specially handled by precompilation so that it is safe to initialize at compile-time.
When using precompilation, it is important to keep a clear sense of the distinction between the compilation phase and the execution phase. In this mode, it will often be much more clearly apparent that Julia is a compiler which allows execution of arbitrary Julia code, not a standalone interpreter that also generates compiled code.
Other known potential failure scenarios include:
Global counters (for example, for attempting to uniquely identify objects). Consider the following code snippet:
mutable struct UniquedById myid::Int let counter = 0 UniquedById() = new(counter += 1) endend
while the intent of this code was to give every instance a unique id, the counter value is recorded at the end of compilation. All subsequent usages of this incrementally compiled module will start from that same counter value.
Note thatobjectid
(which works by hashing the memory pointer) has similar issues (see notes onDict
usage below).
One alternative is to use a macro to capture@__MODULE__
and store it alone with the currentcounter
value, however, it may be better to redesign the code to not depend on this global state.
Associative collections (such asDict
andSet
) need to be re-hashed in__init__
. (In the future, a mechanism may be provided to register an initializer function.)
Depending on compile-time side-effects persisting through load-time. Example include: modifying arrays or other variables in other Julia modules; maintaining handles to open files or devices; storing pointers to other system resources (including memory);
Creating accidental "copies" of global state from another module, by referencing it directly instead of via its lookup path. For example, (in global scope):
#mystdout = Base.stdout #= will not work correctly, since this will copy Base.stdout into this module =## instead use accessor functions:getstdout() = Base.stdout #= best option =## or move the assignment into the runtime:__init__() = global mystdout = Base.stdout #= also works =#
Several additional restrictions are placed on the operations that can be done while precompiling code to help the user avoid other wrong-behavior situations:
eval
to cause a side-effect in another module. This will also cause a warning to be emitted when the incremental precompile flag is set.global const
statements from local scope after__init__()
has been started (see issue #12010 for plans to add an error for this)A few other points to be aware of:
Pkg.update
), and no cleanup is done afterPkg.rm
@__FILE__
/source_path()
to find resources at runtime, or the BinDeps@checked_lib
macro. Sometimes this is unavoidable. However, when possible, it can be good practice to copy resources into the module at compile-time so they won't need to be found at runtime.WeakRef
objects and finalizers are not currently handled properly by the serializer (this will be fixed in an upcoming release).Method
,MethodInstance
,MethodTable
,TypeMapLevel
,TypeMapEntry
and fields of those objects, as this can confuse the serializer and may not lead to the outcome you desire. It is not necessarily an error to do this, but you simply need to be prepared that the system will try to copy some of these and to create a single unique instance of others.It is sometimes helpful during module development to turn off incremental precompilation. The command line flag--compiled-modules={yes|no|existing}
enables you to toggle module precompilation on and off. When Julia is started with--compiled-modules=no
the serialized modules in the compile cache are ignored when loading modules and module dependencies. In some cases, you may want to load existing precompiled modules, but not create new ones. This can be done by starting Julia with--compiled-modules=existing
. More fine-grained control is available with--pkgimages={yes|no|existing}
, which only affects native-code storage during precompilation.Base.compilecache
can still be called manually. The state of this command line flag is passed toPkg.build
to disable automatic precompilation triggering when installing, updating, and explicitly building packages.
You can also debug some precompilation failures with environment variables. SettingJULIA_VERBOSE_LINKING=true
may help resolve failures in linking shared libraries of compiled native code. See theDeveloper Documentation part of the Julia manual, where you will find further details in the section documenting Julia's internals under "Package Images".
Settings
This document was generated withDocumenter.jl version 1.8.0 onWednesday 9 July 2025. Using Julia version 1.11.6.