Fortran Modules
use statement replaces entire heaps of hand-written interfaces. This lesson covers the module lifecycle, visibility control, and the compile-order discipline that makes multi-file projects reliable.
Why modules
Procedure arguments have types, and those types must be known at both call sites. Modules make the compiler know them: when a program uses a module, the compiler reads the module's interface file and checks every call for argument count, type, kind and intent — the same safety net other languages get from headers or signatures, but generated automatically and guaranteed in sync.
Fig. 1 — Consumers compile after providers; each USE edge is verified by the compiler.
The module skeleton
A module has three zones in order: use statements importing its dependencies, declarations of public data (parameters, types), and a contains block of procedures. Everything not explicitly private is public by default — which is a leak; the recommended pattern declares the module private and re-exports only the intended API with public :::
module geometry
implicit none
private ! nothing leaks by default
public :: circle_area, g
Compile order and .mod files
Compiling a module produces the object file plus a module file (*.mod on most compilers, e.g. geometry.mod) containing the interface. Every unit that uses it must find that file at compile time — hence the classic two-pass recipe and its flags:
gfortran -c -J build/ geometry.f90 # first: produce .mod into build/
gfortran -c -I build/ app.f90 # second: consume the .mod
gfortran -o app build/geometry.o app.o # link everything
-J names the directory where this compilation writes module files; -I lists directories where it looks for imported ones. fpm and CMake generate this ordering automatically from the dependency graph — one of the strongest arguments for adopting fpm early (the ecosystem lesson installs it properly).
Renaming and ONLY
use module, only: local = imported renames on import, which resolves the two classic collisions: two modules exporting the same name, and a module name clashing with a local variable. only: also documents intent — every reader of the use line sees exactly which symbols the unit needs. Prefer it over bare use module, which drags the entire public surface into scope.
module constants
real, parameter :: pi = 3.14159265358979
real, parameter :: e = 2.71828182845905
end module constants
module math_utils
use constants, only: pi ! math_utils depends on constants
implicit none
contains
pure real function torus_volume(R, r)
real, intent(in) :: R, r
torus_volume = 2.0 * pi**2 * R * r * r
end function torus_volume
end module math_utils
program ring
use math_utils, only: torus_volume
use constants, only: pi => pi_val ! renamed to dodge the name 'pi'
implicit none
print '(a,f8.3)', trim('torus: '), torus_volume(3.0, 1.0)
print '(a,f8.5)', 'pi as pi_val = ', pi_val
end program ring
Generic interfaces and operators
A generic interface lets one call name dispatch to several specific procedures by argument type — the foundation of Fortran's compile-time polymorphism. Combined with interface operator(+) it also teaches the compiler how to add your derived types:
module vector2d
implicit none
private
public :: vector, magnitude, operator(+)
type :: vector
real :: x, y
end type vector
interface magnitude
module procedure magnitude_v2 ! real vectors
module procedure magnitude_v3 ! integer vectors
end interface magnitude
contains
pure real function magnitude_v2(v)
type(vector), intent(in) :: v
magnitude_v2 = sqrt(v%x**2 + v%y**2)
end function magnitude_v2
pure real function magnitude_v3(v)
integer, intent(in) :: v(3)
magnitude_v3 = sqrt(real(v(1)**2 + v(2)**2 + v(3)**2))
end function magnitude_v3
type(vector) function operator_plus(a, b)
type(vector), intent(in) :: a, b
operator_plus = vector(a%x + b%x, a%y + b%y)
end function operator_plus
end module vector2d
program generic_call
use vector2d
implicit none
type(vector) :: p, q
p = vector(1.0, 2.0); q = vector(3.0, 4.0)
print *, magnitude(p) ! dispatches to magnitude_v2
print *, magnitude([1, 2, 3]) ! dispatches to magnitude_v3
print *, p + q ! our own operator(+)
end program generic_call
Submodules: compile-time decoupling
Large modules recompile fully when any detail changes. Submodules split declaration from implementation: the parent module declares an interface, the submodule provides the body. Consumers depend only on the parent, so editing a submodule body never recompiles the whole project. Every serious codebase above a few tens of procedures should keep this tool in mind, though fpm's incremental builds already soften the pain.
Module rules for this track: one module per file, file named after the module; private by default with explicit public ::; only on every use; contains for every procedure — and let fpm own the build order.
real, parameter :: g = 9.80665 ! private constant, invisible outside
contains
pure real function circle_area(r)
real, intent(in) :: r
circle_area = 3.14159265358979 * r * r
end function circle_area
end module geometry
program use_geometry
use geometry, only: circle_area ! import exactly one symbol
implicit none
print '(a,f8.3)', "area = ", circle_area(2.0)
end program use_geometry