Fortran — Reference

Source: https://fortran-lang.org/learn/

Fortran

  • Created: 1957 by John Backus at IBM (first compiler delivered April 1957 for the IBM 704). Originally “FORmula TRANslator.” Standardized by ISO/IEC JTC1/SC22/WG5 with the US technical body J3 (INCITS/Fortran).
  • Latest stable: Fortran 2023 = ISO/IEC 1539:2023, published November 2023. Predecessors: Fortran 2018 (ISO/IEC 1539:2018), 2008, 2003, 95, 90, 77, 66.
  • Paradigms: Imperative, procedural, array-oriented, object-oriented (since 2003), parallel (since 2008 via coarrays + 2018 teams).
  • Typing: Strong, static, with explicit numeric kinds (integer(kind=8), real(real64)). implicit none is the modern default — turn it on at the top of every scope.
  • Memory: No GC. Stack + heap with allocatable (preferred, RAII-like — auto-deallocated at scope exit) and pointer (use sparingly). No null-deref UB if you stick to allocatable.
  • Compilation: Ahead-of-time native code. Major compilers: gfortran (GCC), Intel ifx/ifort (LLVM-based), NVIDIA nvfortran (HPC SDK, GPU offload), LFortran (modern, interactive, MLIR-based, alpha), LLVM Flang (production-ready as of LLVM 19+), Cray, AOCC.
  • Primary domains: HPC, climate/weather, CFD, computational chemistry, astrophysics, finite-element, numerical libraries (BLAS, LAPACK), legacy scientific code.
  • Official docs: https://fortran-lang.org/learn/ (community), https://j3-fortran.org/ (standards body), https://wg5-fortran.org/ (ISO WG5).

At a glance

Fortran has been continuously redesigned since 1957 and is now a modern array+OO+parallel language, not the 1970s caricature. Free-form source is standard since Fortran 90; .f/.for is fixed-form (column 7 onward), .f90/.f95/.f03/.f08 is free-form. Use .f90 regardless of standard year unless you need fixed-form. Where it dominates: anything where dense-array performance and the BLAS/LAPACK ABI matter — Fortran’s array slicing, intrinsic reductions, and do concurrent map directly to vectorized + parallel codegen, and most of the numerical-library world (BLAS, LAPACK, ScaLAPACK, FFTW interfaces, MPI Fortran bindings) speaks Fortran natively.

Getting started

Install:

  • Linux: sudo apt install gfortran (Debian/Ubuntu), sudo dnf install gcc-gfortran (Fedora), sudo pacman -S gcc-fortran (Arch).
  • macOS: brew install gcc (gfortran ships with it), or brew install lfortran.
  • Windows: MSYS2 (pacman -S mingw-w64-x86_64-gcc-fortran), Intel oneAPI HPC Toolkit (free), or WSL.
  • HPC clusters: module load intel-oneapi-compilers or module load nvhpc.

Hello world (hello.f90):

program hello
  implicit none
  print *, "Hello, world!"
end program hello

Compile + run: gfortran -O2 -Wall -std=f2018 hello.f90 -o hello && ./hello.

Project layout with fpm (Fortran Package Manager — the modern, Cargo-like tool):

myproj/
  fpm.toml
  src/myproj.f90
  app/main.f90
  test/check.f90

fpm new myproj && cd myproj && fpm run and fpm test. Dependencies declared in fpm.toml like [dependencies] stdlib = "*".

Version manager: No first-class one. On HPC, use Lmod/Environment Modules. Locally, pick a package manager (apt, brew, conda install -c conda-forge gfortran) or use Spack for reproducible HPC stacks.

REPL: Fortran has historically had no REPL. LFortran changes that: lfortran opens an interactive prompt with Jupyter kernel support. Useful for exploration; not yet a full standards-conforming compiler.

Basics

Types & literals. integer (default kind 4 bytes), real (default kind 4), double precision (legacy alias for real(kind=real64)), complex, logical, character(len=N). Use the iso_fortran_env module’s named kinds: integer(int32), real(real64), real(real128). Literal suffixes: 1.0_real64, 1_int64. Arrays declared with shape: real(real64) :: a(100, 100). Strings are fixed-length unless declared character(len=:), allocatable.

Variables/scoping. Lexical scope per program/module/subprogram. Always start every scope with implicit none — the default i-n integer rule from 1957 is the source of countless bugs. Module variables visible via use mymod (with optional only: allow-list).

Control flow. if/else if/else/end if, select case, do i = 1, n (default step 1) / do while / do (infinite, exit with exit/cycle). Modern: do concurrent (i = 1:n) declares the iterations are independent — compiler is free to vectorize/parallelize/offload. associate (x => long%nested%expr) ... end associate for local aliases. Conditional expressions (Fortran 2023): result = (x > 0 ? x : -x) via the new merge-style if-expression syntax.

Procedures. subroutine (no return value, called with call) and function (returns a value). Argument intent must be declared: intent(in), intent(out), intent(inout). pure and elemental markers enable optimization and parallelism (elemental lifts a scalar function over array args automatically). Recursion requires recursive keyword.

Strings. character(len=20) :: name = "Ada". Concatenate with //. Slicing: name(1:3). Allocatable strings (character(len=:), allocatable) auto-resize on assignment. No native Unicode — selected_char_kind('ISO_10646') exists but support is uneven.

Collections. Arrays are first-class. Whole-array ops: c = a + b, dot_product(a, b), matmul(A, B), sum(a, dim=1), maxval, minloc, pack, reshape. Array sections: a(1:10:2, :) (stride 2, all columns). Allocatable arrays: real, allocatable :: a(:) then allocate(a(n)). Derived types replace structs: type :: point ; real :: x, y ; end type.

Intermediate

Type system depth. Derived types with type-bound procedures (since 2003), inheritance via extends, abstract types, polymorphic pointers (class(parent_t), pointer :: p). Parameterized derived types (PDTs) since 2003: type :: matrix(rows, cols, kind) ; integer, len :: rows, cols ; integer, kind :: kind ; real(kind) :: data(rows, cols) ; end type — kind parameters at compile time, len parameters at runtime. (Compiler support varies; gfortran has historically been weak here, ifx is solid.)

Modules. module foo ... contains ... end module is the unit of encapsulation. use foo, only: bar controls imports. Submodules (since 2008) split interface from implementation, dramatically reducing recompilation cascades for large projects — declare a procedure interface in a parent module, implement in a submodule, and changing the implementation doesn’t recompile users of the parent.

Error handling. No exceptions. Conventions: an integer, intent(out) :: stat argument (mirrors allocate(..., stat=ierr)) plus optional errmsg. error stop "msg" aborts with optional message and stop-code. Modern stdlib (stdlib_error) provides check/error_stop helpers.

Concurrency primitives. Three layers: (1) do concurrent — single-image data parallelism, compiler-driven. (2) OpenMP/OpenACC pragmas (!$omp parallel do) — threads + GPU offload. (3) Coarrays (Fortran 2008+) — Partitioned Global Address Space (PGAS) model, multiple “images” with one-sided communication: a[image] = b. Coarray teams (Fortran 2018) split images into subgroups.

I/O. print *, x (list-directed to stdout), write(unit, fmt) x. Format strings: '(I5, 2X, F10.4)'. Open files with open(unit=10, file="data.txt", action="read", iostat=ios). NAMELIST I/O for self-describing config blocks. Stream access (Fortran 2003): access="stream" for binary byte streams without record markers.

Stdlib highlights. Intrinsics cover most numerical needs: sin/cos/exp/log (now elemental), bessel_j0, gamma, erf, dot_product, matmul, transpose, cshift, random numbers via random_number(x) + random_seed. The community Fortran stdlib (https://stdlib.fortran-lang.org/) adds string handling, sorting, statistics, linear algebra wrappers, hash maps, optional types — install via fpm.

Advanced

Memory model. No GC. allocatable is the right default — automatic deallocation at end of scope, no aliasing surprises (assignment makes a copy by default; use move_alloc to transfer ownership cheaply). pointer enables aliasing and linked structures but disables many optimizations and re-introduces dangling-reference bugs. target attribute is required on anything pointable. Compilers assume non-aliasing on dummy arguments by default (Fortran’s killer perf advantage over C); violating that with target + pointer aliasing breaks vectorization.

Concurrency deep dive. Coarrays: declare with real :: a(100)[*], the [*] codimension makes it visible to all images. num_images() and this_image() query the team. Sync with sync all, sync images([2,3]), atomic ops via atomic_define/atomic_ref, locks, events. Implementations: gfortran uses OpenCoarrays + MPI under the hood; Intel ifx has native shared-memory coarrays; Cray’s compiler is the original PGAS reference. OpenMP support is mature in gfortran/ifx (5.0+). OpenACC for GPUs is best in nvfortran. do concurrent is increasingly compiler-parallelized (nvfortran offloads it to GPU under -stdpar=gpu).

FFI. iso_c_binding module is the standardized C interop (Fortran 2003). Declare bind(C):

interface
  function c_strlen(s) bind(C, name="strlen") result(n)
    use iso_c_binding
    character(kind=c_char), intent(in) :: s(*)
    integer(c_size_t) :: n
  end function
end interface

c_ptr, c_loc, c_f_pointer, c_funloc round-trip pointers. For BLAS/LAPACK, use the de-facto F77 ABI: column-major arrays, all args by reference, character strings with hidden trailing length argument (compiler-specific — gfortran/ifort/ifx all agree on the standard convention).

Reflection. Essentially none. storage_size(x) returns bits, kind(x) returns the type kind. No runtime type introspection except via polymorphic class(*) + select type. Generic dispatch is compile-time only.

Performance tools. Compiler flags: -O3 -march=native -funroll-loops (gfortran), -O3 -xHost -ipo (Intel), -fast (nvfortran). Diagnostics: -fopt-info-vec/-fopt-info-missed (gfortran), -qopt-report (Intel). Profilers: gprof, Linaro MAP, Intel VTune, NVIDIA Nsight Systems/Compute (for GPU offload), TAU, Score-P + Vampir (HPC tracing). Sanitizers: gfortran supports -fsanitize=address,undefined; bounds checking with -fcheck=all is essential during development (slow but catches whole classes of bugs).

God mode

Coarrays + teams (Fortran 2018). PGAS distributed-memory programming without dropping to MPI. form team(2, my_team) splits images; change team (my_team) ... end team scopes coarray access. Atomic intrinsics (atomic_add, atomic_cas) implement lock-free algorithms across images. The OpenCoarrays runtime layers this on MPI for clusters; on a single node, Intel implements it via shared memory.

do concurrent for offload. Modern code increasingly uses do concurrent with locality clauses (Fortran 2018: local, local_init, shared, default(none)) instead of OpenMP. nvfortran’s -stdpar=gpu lifts these loops to the GPU automatically; this is the path Fortran is staking on for portable parallelism.

Submodules + interface stability. Large projects (climate models, CFD codes) put public interfaces in a thin parent module and implementations in submodules. Changing a submodule body recompiles only that submodule; the parent’s .mod file stays binary-compatible. Without submodules, changing any line in a module recompiles every dependent.

Parameterized derived types (PDTs). type :: tensor(k, rank) ; integer, kind :: k ; integer, len :: rank ; real(k) :: data(rank) ; end type — encode shape and precision in the type. Compiler support: ifx/Cray solid, gfortran improving (still some bugs as of GCC 14). When they work, they’re the cleanest way to write generic numerical containers.

iso_c_binding + c_ptr round-tripping. Pass a Fortran array to C, store the c_loc(arr), and recover it later with c_f_pointer(cptr, fptr, [n]). This is how you bind to GSL, FFTW, HDF5, MPI’s MPI_Aint, CUDA host pointers, etc. Watch out: c_loc requires target or pointer attribute; passing a plain array variable is non-conforming.

Assumed-rank arrays (Fortran 2018). real, intent(in) :: a(..) — a procedure can accept any-rank arrays. Use select rank inside to dispatch. Replaces the old hack of overloading on rank with a generic interface.

Preprocessing. Most compilers run fpp (Intel) or cpp (gfortran with -cpp or files named .F90/.fpp). Standard #ifdef/#define work. Use sparingly — modern Fortran prefers compile-time selection via kind parameters, generics (in standard 202y), and submodules.

Compiler internals worth knowing. gfortran emits GIMPLE → RTL like other GCC frontends; LFortran uses Abstract Semantic Representation (ASR) then lowers to LLVM, with the deliberate goal of being usable as a library (for IDEs, JIT, kernel JIT). LLVM Flang uses MLIR’s FIR dialect for high-level optimization (loop transforms, OpenMP lowering) before LLVM IR.

Idioms & style

  • implicit none everywhere. Always. Configure your editor and CI to fail without it. Use implicit none (type, external) (Fortran 2018) to also force explicit external declarations.
  • Naming: lower_snake_case for everything in modern code. Old code is often SCREAMING_CASE from fixed-form days. Fortran is case-insensitive, but pick one and be consistent.
  • only: on use statements. Always: use iso_fortran_env, only: real64, int32. Otherwise you import the module’s entire surface and pollute the namespace; refactoring a module breaks distant callers.
  • intent(in) / intent(out) / intent(inout) on every dummy. No exceptions. Compilers can elide copies and detect bugs.
  • allocatable over pointer by default. Pointers exist for linked structures and aliasing; they disable optimizations and add bug surface.
  • Formatter/linter: fprettify (auto-formatter — opinionated about indentation, operator spacing). fortls (LSP for editors). stdlib + fpm fmt is emerging. No single dominant linter — gfortran’s -Wall -Wextra -Wno-unused -pedantic + ifx’s -warn all cover most lint.
  • Expert review focus: (1) implicit none present? (2) intent on every dummy? (3) only: on every use? (4) Mixed-precision arithmetic — accidental promotion to wrong kind? (5) Array temporaries — contiguous descriptor mismatches force compiler-generated copies; check with -Warray-temporaries. (6) do concurrent loops with hidden dependencies (assignments to shared scalars). (7) Pointer aliasing of target-attributed arrays.

Ecosystem

  • Numerical libraries: BLAS + LAPACK (the foundation), ScaLAPACK (distributed), FFTW, PETSc (PDEs), SUNDIALS (ODEs), Trilinos, HDF5, NetCDF (climate/geo data formats). All have first-class Fortran bindings.
  • Parallel runtimes: OpenMPI / MPICH (MPI Fortran 2008 bindings via use mpi_f08), OpenCoarrays (coarray runtime), OpenMP (built into compilers), OpenACC (best in nvfortran).
  • HPC/GPU: NVIDIA HPC SDK (nvfortran + cuFortran + cuTensor), AMD ROCm (flang-aocc + ROCmFortran), Intel oneAPI (ifx with SYCL interop).
  • Climate/CFD: WRF, CESM, MPAS, ECMWF IFS, OpenFOAM (mostly C++ but Fortran-adjacent), MOM6, NEMO — all million-line Fortran codes.
  • Modern tooling: fpm (package manager), stdlib (https://stdlib.fortran-lang.org/), fortran-lang.org community, Fortran Discourse, fortls (LSP), fprettify (formatter), test-drive (xUnit-style testing).
  • Notable users: NOAA, NASA, ECMWF, CERN (lattice QCD), most national weather services, every major aerospace + automotive sim shop, every academic computational physics/chemistry/astronomy group on Earth.

Gotchas

  • Default implicit i-n. Without implicit none, undeclared variables starting with i-n are integer, others are real. A typo silently creates a new variable with default kind. Always implicit none.
  • Column rules in fixed-form. .f/.for files: code starts at column 7, columns 1-5 are labels, column 6 is continuation, columns 73-80 ignored. If you see weird “syntax error” on what looks like valid code, you’re probably in fixed form. Use .f90 / free-form unless maintaining legacy.
  • Array slicing copies. Passing a(1:n:2) to a procedure expecting a contiguous array forces a copy in/out. Declare dummies as assumed-shape (real :: a(:)) or use contiguous attribute (Fortran 2008): real, intent(in), contiguous :: a(:).
  • Character length in C interop. Hidden trailing length argument when passing strings to old-style F77 routines. iso_c_binding and bind(C) avoid this — use them for any new interop.
  • Allocatable LHS in F2003. s = "longer string" re-allocates s to fit if it’s character(len=:), allocatable. Compilers default-on this since gfortran 5+ / ifort 17+, but old code written for F95 expects truncation. Compile with -fallocatable-lhs (it’s the default now).
  • Coarray performance. Single-image performance is unaffected, but inter-image communication latency on OpenCoarrays-over-MPI is ~MPI-message tier; if you’re chasing nanoseconds, native shared-memory implementations (Intel) or hand-written MPI-3 RMA win.
  • Mixed compiler .mod files. .mod files are not portable across compilers (gfortran’s differ from ifx’s). Build everything with one compiler in a project, or use bind(C) interfaces for cross-compiler boundaries.
  • integer default kind. Default integer is 32-bit in nearly all compilers; do not assume — query kind(0) or use integer(int64) from iso_fortran_env. Some compilers expose -fdefault-integer-8 which silently breaks every library binding.
  • real default kind matches integer by default but -fdefault-real-8 exists too — never use it; pollutes the ABI. Specify real(real64) explicitly when you mean double precision.

Citations