Appendix A: Modern Fortran Syntax Reference
A dense, scannable map of Modern Fortran (2018), free-form — the page to keep open in a second window
while you write code. Everything here is the modern style the book teaches: implicit none everywhere,
lowercase keywords, kind-parameterized reals (real(dp)), intent on every argument, arrays and modules
as first-class tools. Each section names the chapter that teaches it in depth; this appendix only
reminds, it does not explain.
Legacy forms (fixed-form source, COMMON, implicit typing, GOTO, assumed-size a(*), the
double precision keyword, FORALL) are deliberately absent from this reference — they belong to
Appendix E and Chapters 17–19. If you meet one in old
code, translate it there.
Conventions below: a trailing & continues a line; ! starts a comment; => is pointer assignment.
Program units and implicit none
Every executable is a program; everything reusable lives in a module. implicit none is the
non-negotiable first line of every unit (introduced in
Chapter 2).
program name
implicit none ! always, first thing — turns off implicit typing
! specification part: use statements, then declarations
! execution part: statements
end program name ! name must match
module name
use kinds, only: dp ! what this module needs
implicit none ! once; governs the whole module
private ! hide by default
public :: api ! expose only the interface
contains ! separates specification from procedures
! module procedures here (they get an explicit interface for free)
end module name
| Unit / keyword | Role | Home |
|---|---|---|
program … end program |
the entry point; exactly one per executable | Ch. 2 |
module … end module |
container of shared types, data, procedures | Ch. 8 |
submodule (parent) child … end submodule |
holds the bodies of a module's separate procedures | Ch. 8 |
contains |
begins the internal/module procedures of a unit | Ch. 6, 8 |
use m, only: a, b => c |
import public entities of m (optionally renamed) |
Ch. 8 |
implicit none |
require every name to be declared | Ch. 2 |
stop / error stop "msg" |
normal / error termination (nonzero exit) | Ch. 13 |
submodule (heat_solver) heat_solver_impl ! interface in the module, body here
contains
module procedure step ! inherits the interface declared in the parent
! ... body ...
end procedure step
end submodule heat_solver_impl
Declarations: types, kinds, attributes
General form — type, then attributes, then ::, then the entities:
<type>[(kind)] [, attribute]... :: name[(dims)] [= initial-value]
The five intrinsic types
| Type | Holds | Declaration | Notes |
|---|---|---|---|
integer |
exact whole numbers | integer :: n = 0 |
default ~32-bit, range ±2.1×10⁹ |
real |
approximate floating-point | real(dp) :: x |
plain real is single (~7 digits) — avoid in numerics |
complex |
a pair (a, b) = a + b·i |
complex(dp) :: z |
real(z), aimag(z), conjg(z), abs(z) |
logical |
.true. / .false. |
logical :: ok = .true. |
prints as T / F |
character |
text | character(len=20) :: s |
deferred-length character(:), allocatable → Ch. 12 |
Types, kinds, and arithmetic are owned by Chapter 3.
Kinds — portable precision
integer, parameter :: dp = selected_real_kind(15, 307) ! ≥15 digits, exponent ≥10^307
real(dp) :: x
x = 0.5_dp ! every real literal takes the _dp suffix
complex(dp) :: z = (1.0_dp, -2.0_dp)
- Shortcut kinds via the intrinsic module:
use, intrinsic :: iso_fortran_env, only: dp => real64(alsoreal32,real128,int32,int64). selected_int_kind(r)→ an integer kind holding all values up tordecimal digits.- Never hard-code a kind number (
real(8)); the number is compiler-specific. Request the requirement.
Attributes
| Attribute | Meaning | Example |
|---|---|---|
parameter |
named compile-time constant (cannot be reassigned) | real(dp), parameter :: pi = 3.14159265358979_dp |
dimension(d) |
give the shape as an attribute (or attach to the name) | real(dp), dimension(3,4) :: a ≡ real(dp) :: a(3,4) |
allocatable |
run-time-sized; deferred shape (:); auto-freed |
real(dp), allocatable :: g(:,:) |
pointer |
an alias / dynamic reference (Ch. 11) | real(dp), pointer :: p => null() |
target |
may be pointed at | real(dp), target :: a |
intent(in/out/inout) |
dummy-argument direction (Ch. 6) | real(dp), intent(in) :: x |
optional |
argument the caller may omit (guard with present) |
real(dp), intent(in), optional :: tol |
save |
persist a local's value across calls | integer, save :: calls = 0 |
public / private |
module-level visibility | public :: step |
Also common: protected (module value, read-only outside), contiguous (Ch. 11), value and bind(c)
(C interop, Ch. 14). A module variable is implicitly save; keep it private.
Operators and precedence
| Class | Operators |
|---|---|
| Arithmetic | ** (power) * / + - |
| Character | // (concatenation) |
| Relational | == /= < <= > >= |
| Logical | .not. .and. .or. .eqv. .neqv. |
Precedence, highest to lowest (parentheses always win):
| Level | Operators | Note |
|---|---|---|
| 1 | ** |
right-associative: 2**3**2 = 2**(3**2) = 512 |
| 2 | * / |
left-associative |
| 3 | unary + - |
below ** — so -2**2 = -(2**2) = -4 |
| 4 | binary + - |
|
| 5 | // |
character concatenation |
| 6 | == /= < <= > >= |
result is logical |
| 7 | .not. |
|
| 8 | .and. |
|
| 9 | .or. |
|
| 10 | .eqv. .neqv. |
logical equivalence |
So a == 0 .and. b /= 0 .or. c == 0 parses as (a==0 .and. b/=0) .or. c==0. Mixed-mode arithmetic
promotes the lower type per operation (integer → real → complex): 2 * 3.0_dp is 6.0_dp, but
1/2 is 0 (see gotchas). The .dots. are part of the logical operators' spelling.
Control constructs
Modern Fortran is block-structured; no goto (control flow is
Chapter 4).
if (cond) then
! ...
else if (cond2) then
! ...
else
! ...
end if
if (cond) x = 0.0_dp ! logical if: one guarded statement, no end if
select case (n) ! one discrete value (integer/character/logical, not real)
case (1)
! ...
case (2, 4, 6) ! a list
case (10:20) ! a range; (:0) and (100:) are open
case default
! ...
end select ! exactly one block runs — no fall-through
do i = 1, n ! counted; add a stride: do i = 1, n, 2
! ...
end do
do while (cond) ! test-at-top loop
! ...
end do
do concurrent (i = 1:n, mod(i,2)==0) ! assert iterations are independent (perf: Ch. 29)
a(i) = b(i)
end do
| Construct | Purpose |
|---|---|
exit / exit name |
leave the innermost / named loop |
cycle / cycle name |
skip to the next iteration of the innermost / named loop |
name: do … end do name |
named construct — the target for exit name / cycle name |
where (mask) … elsewhere … end where |
masked whole-array assignment (Ch. 5) |
associate (t => expr) … end associate |
a readable alias for a sub-expression, no copy |
outer: do i = 1, n
do j = 1, m
if (found) exit outer ! break out of the *outer* loop by name
end do
end do outer
where (a > 0.0_dp)
b = sqrt(a)
elsewhere
b = 0.0_dp
end where
associate (u => field%temp(i,j))
u = u + dt * lap ! u aliases the component for the block's duration
end associate
do concurrent may carry locality specifiers (local, local_init, shared, default(none)).
The old forall is obsolescent in Fortran 2018 — use where or do concurrent
(Appendix E).
Arrays
Fortran's superpower: 1-based, column-major (first index varies fastest), whole-array semantics (Chapter 5).
real(dp) :: v(5) ! rank 1, indices 1..5
real(dp) :: a(3, 4) ! rank 2, 3 rows × 4 columns
real(dp) :: b(-1:1) ! custom bounds
real(dp), allocatable :: g(:,:) ! deferred shape — sized at run time
| Feature | Syntax | Meaning |
|---|---|---|
| Whole-array op | c = a + b, y = sqrt(x), a = 2.0_dp * a |
elementwise; shapes must conform |
| Section | a(2, :) · a(:, 3) · a(1:2, 2:3) · v(1:10:2) |
a first-class sub-array (read, write, pass) |
| Constructor | [1.0_dp, 2.0_dp, 3.0_dp] |
an inline rank-1 array value |
| Implied-do | [(i*i, i = 1, n)] |
a loop inside a constructor |
| Reshape | reshape([1,2,3,4], [2,2]) |
reflow a flat list into higher rank |
allocate(g(nx, ny), stat=ierr) ! sized now; check ierr == 0
allocate(g(nx, ny), source=0.0_dp) ! allocate and initialize
deallocate(g) ! or let it auto-free at scope exit
if (allocated(g)) ... ! query allocation status
Core array intrinsics (elementwise or reducing): size, shape, rank, sum, product, maxval,
minval, maxloc, minloc, count, any, all, pack, merge, matmul, dot_product,
transpose, spread. Note a * b is elementwise — the matrix product is matmul(a, b).
Procedures
A function returns one value in an expression; a subroutine does work via arguments and a call.
Every dummy argument gets an intent (owned by
Chapter 6).
function mean(x) result(m) ! result clause names the return variable
real(dp), intent(in) :: x(:) ! assumed-shape: shape travels with the array
real(dp) :: m
m = sum(x) / real(size(x), dp)
end function mean
subroutine describe(x, avg, sd) ! several outputs → a subroutine
real(dp), intent(in) :: x(:)
real(dp), intent(out) :: avg, sd
! ...
end subroutine describe
! call describe(data, a, s)
| Element | Syntax | Meaning |
|---|---|---|
intent(in) |
real(dp), intent(in) :: x |
read-only; may not be written (compiler-enforced) |
intent(out) |
real(dp), intent(out) :: y |
output; undefined on entry, must be set |
intent(inout) |
real(dp), intent(inout) :: z |
updated in place |
optional + present |
intent(in), optional :: tol |
omissible; guard with if (present(tol)) |
| Keyword call | call relax(c, goal=0.0_dp, factor=0.1_dp) |
name arguments; any order after the first keyword |
pure |
pure function f(x) result(r) |
no side effects → compiler may reorder/parallelize |
elemental |
elemental function g(x) result(r) |
scalar body, applies to any-shape arrays; implies pure |
recursive |
recursive function fac(n) result(r) |
self-calling; requires a result clause |
| Assumed-shape | a(:), a(:,:) |
the default array dummy; needs an explicit interface |
An explicit interface (automatic for module and internal procedures) is required for optional, keyword, and assumed-shape arguments. For an external procedure, write one:
interface
subroutine ext(x)
real(dp), intent(inout) :: x
end subroutine ext
end interface
interface swap ! generic: overload by argument type
module procedure swap_int, swap_real
end interface
Derived types and object orientation
Build your own types (Chapter 9); extend and dispatch them (Chapter 10).
type :: point
real(dp) :: x = 0.0_dp, y = 0.0_dp ! components, with default initialization
end type point
type(point) :: p
p = point(1.0_dp, 2.0_dp) ! structure constructor (or point(x=..., y=...))
p%x = 3.0_dp ! % accesses a component
Type-bound procedures
type :: circle
real(dp) :: r
contains
procedure :: area => circle_area ! binding-name => module procedure
end type circle
pure function circle_area(self) result(a)
class(circle), intent(in) :: self ! passed object is class(...), NEVER type(...)
real(dp) :: a
a = 3.14159265358979_dp * self%r**2
end function circle_area
! call it: x = c%area()
| Construct | Syntax | Meaning |
|---|---|---|
| Binding | procedure :: m => proc |
object passed as first arg (default pass) |
nopass |
procedure, nopass :: m => proc |
object not passed (class-level method) |
| Extension | type, extends(base_t) :: child_t |
inherit all of base_t, add/override |
| Polymorphic var | class(base_t), allocatable :: x |
holds base_t or any extension; dynamic dispatch |
| Abstract type | type, abstract :: t |
cannot be instantiated, only extended |
| Deferred binding | procedure(iface), deferred :: m |
contract every concrete extension must fulfill |
select type |
select type (o => x) / type is / class is / class default |
run a block per dynamic type |
| Finalizer | final :: cleanup |
runs just before an object is destroyed |
select type (o => shape)
type is (circle) ! exact dynamic type
print *, o%r
class is (base_t) ! this type or any extension
! ...
class default
! ...
end select
type(t) is monomorphic (resolved at compile time, inlinable — the default). class(t) is
polymorphic (dispatched at run time; must be allocatable, pointer, or a dummy). Keep polymorphism at
the coarse grain, never in a hot inner loop.
Pointers and targets
Prefer allocatable; reach for a pointer only to alias, link, or interoperate
(Chapter 11).
real(dp), target :: a
real(dp), pointer :: p => null() ! ALWAYS initialize a pointer
p => a ! pointer assignment: p becomes an ALIAS for a
p = a ! value assignment: writes a's value THROUGH p
| Name | Meaning |
|---|---|
=> |
pointer assignment (relocate the alias) — distinct from = (write the value) |
associated(p) |
is p associated with any target? (illegal on an undefined pointer) |
associated(p, t) |
is p associated with this specific target t? |
nullify(p) / => null() |
set p to the disassociated state |
contiguous |
promise an array occupies one unbroken block (licenses vectorization) |
is_contiguous(a) |
test whether a is actually contiguous |
move_alloc(from, to) |
O(1) transfer of an allocation; leaves from deallocated |
A pointer may only target an object declared target (or another pointer). Confusing => and =
corrupts data silently; after deallocate, nullify every remaining alias.
Input/output (quick reference)
Brief on purpose — the full edit-descriptor and file-control reference is Appendix F; I/O is taught in Chapter 7.
print '(a, f8.2)', 'x = ', x ! formatted; no leading blank
print *, 'quick debug', x ! list-directed; adds a leading blank
write(u, '(i0)') n ! write to unit u with a format
read(u, *) y ! read one value, list-directed
open(newunit=u, file='out.dat', status='replace', action='write', &
iostat=ios, iomsg=msg) ! newunit picks a free unit
close(u)
inquire(file='out.dat', exist=ok) ! ask before you open
| Item | Quick meaning |
|---|---|
print fmt, list |
write to standard output |
write(unit, fmt) list |
write to a unit (a file, or * = stdout) |
read(unit, fmt) list |
read from a unit (* = stdin) |
| Common descriptors | i0 iw.m · fw.d · esw.d · a · nx · / (see Appendix F) |
status= |
'replace' · 'old' · 'new' · 'scratch' |
action= |
'read' · 'write' · 'readwrite' |
iostat=ios |
0 ok · negative = end (iostat_end) · positive = error |
namelist /grp/ a, b |
key/value config read with read(u, nml=grp) |
Common gotchas
| Trap | What bites | The rule |
|---|---|---|
| Integer division | 1/2 is 0; 7/2 is 3; -7/2 is -3 (truncates toward zero) |
make one operand real: real(i, dp)/n, 1.0_dp/2.0_dp |
| 1-based indexing | first element is a(1); no a(0) by default |
do i = 1, n — both bounds inclusive (unlike Python range) |
| Column-major order | the first index varies fastest in memory | inner loop over the first index: do j; do i; a(i,j) |
implicit none |
omit it and a typo silently becomes a new variable | put implicit none in every program unit |
Unary - vs ** |
-2**2 is -4, not 4 |
** binds tighter than unary minus; parenthesize when unsure |
| Real equality | 0.1_dp + 0.2_dp /= 0.3_dp |
compare with a tolerance (Ch. 20), never == on reals |
intent(out) |
wipes the argument's incoming value on entry | use intent(inout) to preserve it |
Implicit save |
a local initialized in its declaration (integer :: c = 0) is saved, initialized once |
assign in an executable statement if you need a fresh value each call |
| Assumed-shape bounds | dummy a(:) renumbers from 1 regardless of the caller |
pass bounds explicitly, or declare a(0:) when the math needs it |
a * b on matrices |
that is elementwise, not the matrix product | use matmul(a, b) / dot_product(x, y) |
For compiler flags that catch several of these at run time (-fcheck=all, -ffpe-trap), see
Appendix C.