Modules and compilation of modules
Last updated on 2026-02-21 | Edit this page
Estimated time: 20 minutes
Overview
Questions
- How can I collect parameters and sub-programs of my own in a module?
- Can I keep some parts of the module internal to it and hidden?
- Am I able to use scoping to introduce temporary variables in a larger program unit?
Objectives
- Learn how to write a non-intrinsic module of your own, and understand what is and isn’t appropriate to place within them..
- Understand how to use
publicandprivatewithin a module to control what components are visible externally. - Use the
blockconstruct to control the scope of names within larger program structures.
Module structure
We have already used one intrinsic module
(iso_fortran_env); we can also write our own, e.g.,
FORTRAN
module module1
implicit none
integer, parameter :: mykind = kind(1.d0)
contains
function pi_mykind() result(pi) ! Return the value of a well-known constant
real (kind = mykind) :: pi
pi = 4.0*atan(1.0_mykind)
end function pi_mykind
end module module1
We may now use the new module in other program
units (main program or other modules). For example:
FORTRAN
program example1
use module1
implicit none
real (kind = mykind) :: value
value = pi_mykind()
end program example1
Here both the parameter mykind and the function
pi_mykind() are said to be available by use
association. Note that any use statements must come
before the implicit statement.
Formally, the structure of a module is:
FORTRAN
module module-name
[specification-statements]
[ contains
module-subprograms ]
end [ module [ module-name ]]
The contains statement separates the specification
statements from module sub-programs. Sub-programs, or
procedures, consist of functions and/or subroutines which will
cover in the next episode
.
Digression: compilation of modules, and programs
One would typically expect modules and a main program to be in separate files, e.g.,:
It is often convenient to use the same name for the both module and
the corresponding file (with extension .f90). You can do
differently, but it can become confusing. Likewise for the main
program.
We can compile the module, e.g.,
where the -c option to the Fortran compiler
ftn requests compilation only (no link). This should give
us two new files:
The first is a compiler-specific module file (usually with a
.mod extension). This plays the role roughly analogous to a
header (.h) file in C, in that it contains the relevant
public information about what the module provides. The other file is the
object file (.o extension) which can be linked with the run
time to form an executable.
We can now compile both the main program and the module to give an executable.
Again, by analogy with C header files, we do not include the
.mod file in the compilation command; there is a search
path which the compiler uses to look for module files (which includes
the current working directory).
.mod files
As stated, the .mod files are compiler specific. That
means that you shouldn’t expect that a file created by one compiler will
be usable by a different compiler. This can be can cause compile time
errors if you try to use a .mod from one compiler with
another; perhaps the most common scenario for this is when testing
builds with different compilers without first cleaning up the
.mod files. Sometimes compilers also use different naming
schemes for the .mod, such as capitalising them. This
particular behaviour can usually be changed with compiler flags.
Exercise (2 minutes)
Compiling modules
If you haven’t already done so, compile the accompanying module1.f90 and program1.f90. Check the errors which occur if you: (1) try to compile the program without the module file via, e.g.,
and (2), if you try to compile and link the module file alone:
Compiling the program alone produces (with the Cray compiler) the following error:
OUTPUT
use module1
^
ftn-292 ftn: ERROR PROGRAM1, File = program1.f90, Line = 3, Column = 7
"MODULE1" is specified as the module name on a USE statement, but the compiler cannot find it.
real (kind = mykind) :: a
^
ftn-113 ftn: ERROR PROGRAM1, File = program1.f90, Line = 6, Column = 16
IMPLICIT NONE is specified in the local scope, therefore an explicit type must be specified for data object "MYKIND".
^
ftn-868 ftn: ERROR PROGRAM1, File = program1.f90, Line = 6, Column = 16
"MYKIND" is used in a constant expression, therefore it must be a constant.
Cray Fortran : Version 15.0.0 (20221026200610_324a8e7de6a18594c06a0ee5d8c0eda2109c6ac6)
Cray Fortran : Compile time: 0.0472 seconds
Cray Fortran : 13 source lines
Cray Fortran : 3 errors, 0 warnings, 0 other messages, 0 ansi
Cray Fortran : "explain ftn-message number" gives more information about each message.
This tells us precisely that the compiler doesn’t know what module
we’re talking about when we use module1, and then that
mykind (which we have been expecting to get from the
module) doesn’t exist either.
Compiling module1 alone gives the following output from
the Cray compiler:
OUTPUT
/opt/cray/pe/cce/15.0.0/binutils/x86_64/x86_64-pc-linux-gnu/bin/ld: /usr/lib64//crt1.o: in function `_start':
/home/abuild/rpmbuild/BUILD/glibc-2.31/csu/../sysdeps/x86_64/start.S:104: undefined reference to `main'
This is less immediately meaningful. The second line tells us that
there’s no function called main available. This is because
what we’ve compiled has no program ... end program. There’s
no program in there to start, were we able to invoke it on the command
line. In other words, a module alone doesn’t make a program we can
run.
Scope
Entities declared in a module are, by default, available by use
association, that is, they are visible in program units which
use the module. One can make this scope explicit via the
public and private statements.
FORTRAN
module module1
implicit none
public
integer, parameter :: mykind = kind(1.d0)
contains
function pi_mykind() result(pi)
real (kind = mykind) :: pi
...
end function pi_mykind()
end module module1
Note that the parameter mykind is available throughout
the module via host association (always).
An alternative would be to switch the default to
private, and explicitly add public
attributes:
FORTRAN
module module1
implicit none
private
integer, parameter, public :: mykind = kind(1.d0) ! visible via `use`
integer, parameter :: mypriv = 2 ! not visible via `use`
public :: pi_mykind
contains
function pi_mykind() result(pi) ! public
! ... may call my_private() ...
end function pi_mykind
subroutine my_private() ! private
...
end subroutine
end module module1
Note that scope of the implicit statement also covers
the whole module, including sub-programs.
Exercise (1 minute)
Private modules
Edit the accompanying module1.f90 to add a
private statement and check the error if you try to compile
program1.f90.
Making the module private hides the mykind
parameter it provide from the program. On compilation,
mykind can’t be found in the scope of the program, and the
compiler will complain that no such name exists.
Module data and other horrors
It is possible to establish non-parameter data in the specification section of a module. E.g.,
This course will argue that you should not do so. There are a number of reasons.
- Any such data takes on the character of a global mutable object. Global objects are generally frowned upon in modern software development.
- Operations in module procedures on such data run the risk of being neither thread safe nor re-entrant.
Even worse, variables declared with an initialisation in a module sub-program, e.g.,
implicitly take on the Fortran save attribute. This
means the variable is placed in heap memory and retains its value
between calls. (This is analogous to a static declaration
in C.) This is certainly neither thread-safe nor re-entrant.
Uninitialised variables appear on the stack (and disappear) as
expected.
For this reason it is the rule, rather than the exception, that variables are not initialised at the point of declaration in Fortran.
We will look at alternative ways of establishing and moving data as we go along.
Scope again: block
It is not possible in Fortran to intermingle declarations and executable statements. Specification statements must appear at the start of scope before any executable statements. This can lead to rather lengthy list of declarations at the start of large routines.
It is possible to introduce a local scope which follows executable
statements using the block construct. Schematically:
FORTRAN
... some computation ...
block
integer :: itmp ! in scope within the block only
... some more computation ...
end block
... some more computation ...
This can be useful for introducing temporary variables which are only
required for the duration of a short part of a longer procedure. In this
way, it acts like { .. } in C.
Exercise (5 minutes)
Return to Gauss-Legendre
Return to your code for the approximation to pi via the Gauss-Legendre iteration (or use the template exercise1.f90 from the earlier episode on arrays). Using the examples above as a template, write a module to contain a function which returns the value so computed. Check you can use the new function from a main program.
An example solution is provided in solution_program.f90 and solution_module.f90.
This is a circular dependency: a depends on
b depends on a. There is no solvable
dependency tree, and this is not allowed.
- Modules in Fortran provide the way to structure collections of related definitions and operations, and make them available elsewhere.