Modules and organising a Julia project
Split code into modules, use and import them deliberately, and understand what Project.toml and Manifest.toml each record.
Modules
module Geometry
export area, perimeter # names users get with "using"
struct Circle
r::Float64
end
area(c::Circle) = pi * c.r^2
perimeter(c::Circle) = 2 * pi * c.r
function _helper(x) # not exported: internal
x * 2
end
end # module| Form | Effect |
|---|---|
using Geometry | Brings exported names into scope; the module itself is also accessible |
import Geometry | Only the module name; call Geometry.area(...) |
using Geometry: area | Only the named bindings |
import Geometry: area | Named binding, and lets you add methods to it |
export | Marks names for using; does not make them public API |
include("x.jl") | Evaluates a file inside the current module |
To add a method to a function owned by another module you must import it, not using it. That rule prevents a stray definition from silently extending a function you did not intend to touch.
Projects and environments
mkdir MyPkg && cd MyPkg
julia --project=. -e 'using Pkg; Pkg.generate("MyPkg")'
# resulting layout
# MyPkg/
# Project.toml name, uuid, version, [deps], [compat]
# Manifest.toml exact resolved versions and hashes
# src/MyPkg.jl module MyPkg ... end
# test/runtests.jl @testset for the packageusing Pkg
Pkg.activate(".")
Pkg.add(["DataFrames", "CSV"]) # writes into Project.toml and Manifest.toml
Pkg.status()
Pkg.update()
Pkg.instantiate() # install exactly what Manifest.toml records
Pkg.test() # runs test/runtests.jl in a clean environmentProject.tomldeclares direct dependencies and version compatibility; it is written by hand or edited byPkg.add.Manifest.tomlrecords the full resolved dependency graph. Commit it for applications; it is optional for libraries.Pkg.test()runs in a separate sandbox with the test dependencies added, so a passing test proves the package works outside your session.
Layout and testing
# src/MyPkg.jl
module MyPkg
include("types.jl")
include("operations.jl")
using .Types # a submodule declared inside include files
using .Operations
export transform, Point
end# test/runtests.jl
using Test
using MyPkg
@testset "MyPkg" begin
@testset "transform" begin
@test transform(Point(1.0, 2.0)) == Point(2.0, 4.0)
@test_throws ArgumentError transform(Point(NaN, 0.0))
end
end💡
A package is only reusable if nothing depends on the current working directory. Use
@__DIR__ or pkgdir(MyPkg) to build paths to data files, never a bare relative string, which breaks the moment someone calls your code from another directory.FAQ
What is the difference between using and import?
using makes exported names available unqualified; import requires the module prefix and is needed when you want to add methods to another module's function.Should I commit Manifest.toml for a library?
For an application or a script that must reproduce exactly, commit it. For a library that others will install, publishing the manifest can conflict with the resolver in the consumer's environment, so most libraries commit only the project file.
Related
Installing Julia and the REPL workflow Debugging, testing and benchmarking Julia code
Last refreshed 2026-09-18.