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
programend program the entry point; exactly one per executable Ch. 2
moduleend module container of shared types, data, procedures Ch. 8
submodule (parent) childend 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 (also real32, real128, int32, int64).
  • selected_int_kind(r) → an integer kind holding all values up to r decimal 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) :: areal(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 (integerrealcomplex): 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.