Error Handling and Program Termination

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-18. Every example was compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and the examples were also built and run with LLVM Flang 23; the output shown is real output. Where the two compilers print different text or return different numbers, both are shown, because most of what an error produces (status values, message texts, the form of the stop output) is processor dependent. To keep the listings readable the output was captured with the stderr and stdout streams merged and unbuffered (GFORTRAN_UNBUFFERED_ALL=1 for gfortran), and gfortran’s address-only backtrace frames were switched off with -fno-backtrace. The rules are in these places in the standard: termination of execution is 5.3.7, the stop and error stop statements are 11.4, the stat= and errmsg= specifiers of allocate and deallocate are 9.7.4 and 9.7.5, input/output conditions are 12.11, the IEEE exceptions are 17.3 and halting is 17.6. Annex C.1 lists what Fortran 2018 changed.

Fortran has no try/catch, no throw and no stack unwinding. An error is handled in one of three ways: the statement that detected it reports it through a status variable that the programmer supplied, the program decides that it cannot go on and terminates, or nothing detects it at all and the program carries on with wrong data. This page covers the first two, in the order a program meets them, and then says what the third looks like and how to turn it into one of the first two. It uses the heatflow running example: the configuration reader that returns stat and msg, the solver factory that stops on an unknown solver, and the application that turns a bad configuration into a nonzero exit status. The same ideas for I/O are developed in Input/Output: Units and Files, for allocation in Allocatable Arrays and Dynamic Memory, for floating-point exceptions in Floating-Point and IEEE Arithmetic, and for debugging tools in Testing, Debugging and Profiling.

Fortran has no exceptions

The mechanisms Fortran offers are all local: the statement or procedure call that can fail takes an argument in which the outcome is stored, and the caller looks at it, or does not, in which case a default happens. The defaults differ, and that is the first thing to know.

Mechanism Where If you ask for nothing Clause

stat= and errmsg=

allocate, deallocate; image control statements (sync all, lock, form team, and so on); move_alloc

error termination of the image

9.7.1.2, 9.7.4, 9.7.5, 11.7.11

iostat= and iomsg=, err=, end=, eor=

open, close, read, write, inquire, rewind, backspace, endfile, flush, wait

error termination for an error, an end-of-file or an end-of-record condition

12.11

status, stat, exitstat, cmdstat arguments

get_command_argument, get_environment_variable, execute_command_line, move_alloc, collective and atomic subroutines

depends on the procedure: a truncated value is silent, a failed execute_command_line without cmdstat terminates

16.9

IEEE exception flags

every floating-point operation

nothing happens, the flag stays set and the result is Infinity or NaN unless halting is enabled

17.3, 17.6

stop, error stop

anywhere (not stop in pure procedures)

not applicable: these are the termination

11.4

Runtime checks

compiler options such as -fcheck=all

undetected: wrong results or a crash

processor dependent

Two ideas run through the table. A condition that is detected and has no handler ends the program (error termination) for the I/O and allocation statements, which is the safe default. A condition that the processor does not detect, or detects only on request, is simply not an error as far as the program can tell. The next figure is the decision a procedure writer faces.

flowchart TD A["Something is wrong inside a procedure"] --> B{"Can the caller reasonably recover?"} B -- "yes: bad file, bad input, no memory" --> C["Report it: stat= iostat= status argument or error object"] B -- "no: a violated precondition, a bug" --> D["error stop with a message and a nonzero code"] C --> E{"Did the caller ask to be told?"} E -- "yes: argument present" --> F["Fill the status, return normally"] E -- "no: argument absent" --> G["Stop: the statement's own default or an error stop"] H["Finished early on purpose"] --> I["stop, with code 0 or no code"]

stat= and errmsg= on allocation

An allocate or deallocate statement that fails and has no stat= specifier initiates error termination (9.7.1.2 and 9.7.3). With stat= it does not terminate: the integer stat-variable becomes zero on success and a positive, processor-dependent value on failure, and with errmsg= a message is stored in the character variable as if by intrinsic assignment (9.7.4, 9.7.5). errmsg= is left unchanged when nothing goes wrong, so it must be cleared by the caller if it is going to be examined.

The program tries five things: an allocation of 250 eight-byte elements (8 PiB) that no machine can satisfy, a normal allocation, an allocation of an array that is already allocated, a normal deallocation and a deallocation of an array that is not allocated.

program alloc_stat
  use, intrinsic :: iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: dp = real64
  real(dp), allocatable :: u(:, :)
  integer :: stat
  character(len=200) :: msg
  integer(int64) :: n

  n = 2_int64**50                      ! 2**50 elements of 8 bytes: 8 PiB, cannot succeed
  msg = ''
  allocate (u(n, 1), stat=stat, errmsg=msg)
  print '(a, i0, a, l1)', 'huge allocate: stat = ', stat, ', allocated = ', allocated(u)
  print '(a, a)', 'errmsg: ', trim(msg)

  allocate (u(21, 21), stat=stat, errmsg=msg)
  print '(a, i0, a, l1)', 'normal allocate: stat = ', stat, ', allocated = ', allocated(u)

  allocate (u(5, 5), stat=stat, errmsg=msg)   ! already allocated: an error condition
  print '(a, i0)', 'allocate of an allocated array: stat = ', stat
  print '(a, a)', 'errmsg: ', trim(msg)

  deallocate (u, stat=stat, errmsg=msg)
  print '(a, i0)', 'deallocate: stat = ', stat
  deallocate (u, stat=stat, errmsg=msg)       ! already unallocated: an error condition
  print '(a, i0)', 'deallocate of an unallocated array: stat = ', stat
  print '(a, a)', 'errmsg: ', trim(msg)
end program alloc_stat

Output with gfortran 16.2:

huge allocate: stat = 5020, allocated = F
errmsg: Insufficient virtual memory
normal allocate: stat = 0, allocated = T
allocate of an allocated array: stat = 5014
errmsg: Attempt to allocate an allocated object
deallocate: stat = 0
deallocate of an unallocated array: stat = 1
errmsg: Attempt to deallocate an unallocated object

Output with LLVM Flang 23:

huge allocate: stat = 19, allocated = F
errmsg: Memory allocation failed
normal allocate: stat = 0, allocated = T
allocate of an allocated array: stat = 12
errmsg: Base address is not null
deallocate: stat = 0
deallocate of an unallocated array: stat = 11
errmsg: Base address is null

The rules this shows:

  • Only zero versus nonzero is portable. The standard fixes the sign (positive) and, for coarray and image failures, two values; every other number and every message text belongs to the compiler, as the two columns above show (5020 against 19 for the same failure). Test stat /= 0, never a particular value, and print errmsg rather than parsing it.

  • A failed allocate leaves the object as it was. After the first statement allocated(u) is still false. With several allocate-object entries in one statement the state of each is processor dependent after a failure, and the standard recommends asking allocated or associated for each (9.7.4).

  • Give stat an integer with a decimal exponent range of at least four. A default integer is fine; the standard says a smaller range might be unable to hold the processor’s code (9.7.4).

  • The variable cannot be part of the statement. The stat-variable and errmsg-variable shall not be an allocate-object, nor depend on one, and errmsg-variable shall not depend on stat-variable (9.7.4, 9.7.5).

  • deallocate of an unallocated allocatable is an error condition, not a no-op, as the last statement shows. Guard it with if (allocated(u)) or accept the status.

  • Coarrays add two values. For an allocate or deallocate of a coarray, stat is stat_stopped_image if the current team contains a stopped image and stat_failed_image if it contains a failed image (both in iso_fortran_env, 16.10.2.31 and 16.10.2.28), see Termination in coarray programs.

  • -fcheck=mem is about compiler-generated allocations. In gfortran it adds run-time checks for memory allocation, and explicit allocate statements are always checked whatever the option (GNU Fortran manual, Code Gen Options).

Without stat=, the same failure is fatal. The next program allocates the same impossible array, with no stat=:

program alloc_nostat
  use, intrinsic :: iso_fortran_env, only: real64, int64
  implicit none
  real(real64), allocatable :: u(:)
  allocate (u(2_int64**50))                  ! no stat=: failure means error termination
  u(1) = 1.0_real64
  print '(a, f4.1)', 'not reached: ', u(1)
end program alloc_nostat

gfortran 16.2 terminates with exit status 1 and one line of output:

In file '12_alloc_nostat.f90', around line 5: Error allocating 9007199254740992 bytes: Cannot allocate memory
exit status: 1

Flang 23, on the same program, did not error-terminate: the failed allocation went unnoticed and the first use of u crashed with Segmentation fault (exit status 139). The standard requires error termination here (9.7.1.2), so this is a compiler shortcoming on the version tested, and a reason to put stat= on every allocation whose size depends on input, which is portable and testable.

Input/output conditions: iostat=, iomsg=, err=, end= and eor=

Input/output statements report three kinds of condition (12.11):

  • an error condition, whose set is processor dependent (a file that cannot be opened, a conversion that fails, a write to a full disk);

  • an end-of-file condition, for sequential and stream input that runs past the last record, and for a read beyond the end of an internal file;

  • an end-of-record condition, for non-advancing input that tries to read past the end of the current record.

With iostat= the integer becomes zero for success, a positive processor-dependent value for an error, the negative constant iostat_end for end of file and the negative constant iostat_eor for end of record (12.11.5; the constants are in iso_fortran_env, 16.10.2.16 and 16.10.2.17). iomsg= receives an explanatory message for any of the three conditions and is otherwise left alone (12.11.6). The intrinsic functions is_iostat_end(i) and is_iostat_eor(i) (16.9.116 and 16.9.117) test a value without importing the constants.

If a condition occurs and the statement contains neither the matching specifier nor iostat=, the statement initiates error termination (12.11.2 to 12.11.4). The program below triggers each condition in turn: an open of a file in a directory that does not exist, a read of a number from text that is not a number, a loop that reads until end of file, a non-advancing read of a record that is shorter than the field, and finally a read with no specifier at all.

program io_status
  use, intrinsic :: iso_fortran_env, only: iostat_end, iostat_eor
  implicit none
  integer :: unit, stat, k
  real :: x
  character(len=200) :: msg
  character(len=*), parameter :: file = 'no_such_dir/heatflow.nml'
  character(len=6) :: text = '3.5abc'

  ! 1. a file that cannot be opened
  open (newunit=unit, file=file, status='old', action='read', iostat=stat, iomsg=msg)
  print '(a, i0, a, a)', 'open:        iostat = ', stat, '  iomsg = ', trim(msg)

  ! 2. a record that cannot be converted
  read (text, *, iostat=stat, iomsg=msg) x
  print '(a, i0, a, a)', 'bad real:    iostat = ', stat, '  iomsg = ', trim(msg)

  ! 3. end of file: negative, and equal to iostat_end
  open (newunit=unit, status='scratch', action='readwrite')
  write (unit, '(a)') '1.5'
  write (unit, '(a)') '2.5'
  rewind (unit)
  k = 0
  do
    read (unit, *, iostat=stat, iomsg=msg) x
    if (stat == iostat_end) exit
    if (stat /= 0) then
      print '(a, i0, a, a)', 'read error: ', stat, ' ', trim(msg)
      exit
    end if
    k = k + 1
  end do
  print '(a, i0, a, i0, a, l1)', 'read ', k, ' values; last iostat = ', stat, ', is iostat_end: ', stat == iostat_end
  close (unit)

  ! 4. end of record in non-advancing input: negative, equal to iostat_eor
  open (newunit=unit, status='scratch', action='readwrite')
  write (unit, '(a)') 'abc'
  rewind (unit)
  read (unit, '(a5)', advance='no', iostat=stat, size=k) msg
  close (unit)
  print '(a, i0, a, l1)', 'short record: iostat = ', stat, ', is iostat_eor: ', stat == iostat_eor

  ! 5. no iostat= or err=: the error terminates the program
  print '(a)', 'now reading without iostat='
  read (text, *) x
  print '(a)', 'not reached'
end program io_status

Output with gfortran 16.2:

open:        iostat = 2  iomsg = Cannot open file 'no_such_dir/heatflow.nml': No such file or directory
bad real:    iostat = 5010  iomsg = Bad real number in item 1 of list input
read 2 values; last iostat = -1, is iostat_end: T
short record: iostat = -2, is iostat_eor: T
now reading without iostat=
At line 46 of file 02_iostat.f90
Fortran runtime error: Bad real number in item 1 of list input

Error termination. Backtrace:
exit status: 2

Output with LLVM Flang 23:

open:        iostat = 2  iomsg = No such file or directory
bad real:    iostat = 1046  iomsg = invalid character (0x62) after list-directed input value, at column 5 in record 1
read 2 values; last iostat = -1, is iostat_end: T
short record: iostat = -2, is iostat_eor: T
now reading without iostat=

fatal Fortran runtime error(/work/02_iostat.f90:46): invalid character (0x62) after list-directed input value, at column 5 in record 1
Aborted
exit status: 134

What to take from the output:

  • The positive codes are as compiler specific as the allocation codes (2 and 5010 against 2 and 1046), and so are the messages. The values of iostat_end (-1) and iostat_eor (-2) happen to be the same on both compilers but are documented as processor dependent: always compare with the constants or is_iostat_end.

  • gfortran’s runtime error reports the file and the line of the failing statement and ends with the exit status 2. Flang aborts with a different message and exit status 134 (SIGABRT). Neither status is specified by the standard.

  • The pattern in step 3 is the idiomatic read loop: test for end of file first, then for any other error, and only then use the value. Testing stat /= 0 alone would treat the normal end of the data as an error.

  • After an error condition the file position is indeterminate and the read list items become undefined (12.11.2): do not use x after step 2.

The heatflow configuration reader follows the same pattern from the inside, passing the status of the two I/O statements straight back to the caller:

open (newunit=unit, file=file, status='old', action='read', iostat=stat, iomsg=msg)
if (stat /= 0) return
read (unit, nml=heatflow, iostat=stat, iomsg=msg)
close (unit)
if (stat /= 0) return

read_config has stat and msg dummy arguments and never stops by itself; the application decides what a configuration error means (Exit statuses and the calling script).

err=, end= and eor= with labels

The specifiers err=, end= and eor= name a statement label to branch to. They are not obsolescent in the standard and they behave like iostat= for the branching part; they also suppress error termination. They carry no number and no message unless iostat= and iomsg= are given too, and a branch to a label is awkward in structured code, so iostat= is the style to prefer. A book that calls err= obsolescent is passing on its authors' recommendation, not the standard’s list in Annex B.

program err_labels
  implicit none
  integer :: unit, n
  real :: x

  open (newunit=unit, status='scratch', action='readwrite')
  write (unit, '(a)') '1.5'
  write (unit, '(a)') 'oops'
  rewind (unit)
  n = 0
  do
    read (unit, *, err=90, end=80) x       ! labels instead of iostat=
    n = n + 1
  end do
80 print '(a, i0, a)', 'end of file after ', n, ' values'
  stop
90 print '(a, i0, a)', 'read error after ', n, ' values'
end program err_labels
read error after 1 values

gfortran and Flang print the same line.

Further rules of 12.11 worth knowing: an inquire whose unit is an internal file gives the positive constant iostat_inquire_internal_unit; an iostat= in a flush statement can return a processor-dependent negative value (not iostat_end or iostat_eor) when the unit does not support flushing; and the iostat=, iomsg= and size= variables must not be associated with anything in the input/output list (12.12). Asynchronous, namelist and derived-type I/O add their own conditions, covered in Internal Files, Namelist and Non-Advancing I/O and Derived-Type and Asynchronous I/O.

Status arguments of intrinsic procedures

A handful of intrinsic procedures that talk to the operating system or the command line report the outcome through optional status arguments instead of failing: get_command_argument (16.9.93), get_environment_variable (16.9.94) and execute_command_line (16.9.83). The collective subroutines (co_sum and friends) and move_alloc take stat and errmsg in the same way for coarray work. The calls below show the main values.

program intrinsic_status
  implicit none
  character(len=4) :: short
  character(len=64) :: msg
  integer :: stat, exitstat, cmdstat, len_needed

  ! get_command_argument: status 0, -1 (value too short) or a positive value (cannot retrieve)
  call get_command_argument(1, short, length=len_needed, status=stat)
  print '(a, i0, a, i0, a, a)', 'argument 1: status = ', stat, ', length = ', len_needed, ', value = ', short

  ! get_environment_variable: status 1 when the variable does not exist
  call get_environment_variable('HEATFLOW_NO_SUCH_VARIABLE', short, status=stat)
  print '(a, i0)', 'environment variable: status = ', stat

  ! execute_command_line: exitstat is the command's own status, cmdstat the status of running it
  call execute_command_line('exit 3', exitstat=exitstat, cmdstat=cmdstat, cmdmsg=msg)
  print '(a, i0, a, i0)', 'exit 3:         exitstat = ', exitstat, ', cmdstat = ', cmdstat
  msg = ''
  call execute_command_line('/no/such/command', exitstat=exitstat, cmdstat=cmdstat, cmdmsg=msg)
  print '(a, i0, a, i0, a, a)', 'missing command: exitstat = ', exitstat, ', cmdstat = ', cmdstat, ', cmdmsg = ', trim(msg)
end program intrinsic_status

Run with the single argument longer-argument, gfortran prints:

argument 1: status = -1, length = 15, value = long
environment variable: status = 1
exit 3:         exitstat = 3, cmdstat = 0
sh: 1: /no/such/command: not found
missing command: exitstat = 127, cmdstat = 3, cmdmsg = Invalid command line

Flang prints the same first three lines and the same shell message, and ends with missing command: exitstat = 127, cmdstat = 5, cmdmsg = Command not found with exit code: 127.. Source: J3/23-007r1: 16.9.83 EXECUTE_COMMAND_LINE, 16.9.93 GET_COMMAND_ARGUMENT, 16.9.94 GET_ENVIRONMENT_VARIABLE

A status of -1 from get_command_argument means the value was truncated; length gives the size needed, which is how a program sizes an allocatable character(len=:) buffer. exitstat and cmdstat answer two different questions: what did the command return, and could it be run at all. A command-not-found shows cmdstat positive (3 or 5 here, processor dependent) together with the shell’s own 127. When cmdstat is omitted and the call fails, the program error-terminates (16.9.83).

Designing error reporting

The mechanisms above are for the statements the language gives you. Code you write has to decide how it reports errors to its callers. Fortran offers no standard exception type, so the design is a convention, and the choice matters more than in languages where the runtime enforces one.

Design Strengths Weaknesses

Integer status argument (stat) with intent(out)

matches iostat= and stat=; works in pure and elemental procedures; trivial to pass up

callers can ignore it silently; carries no message; codes must be documented

Status plus message (stat, msg)

what read_config does; the message is usable in logs

two arguments everywhere; the message length is fixed by the caller

Optional status: report if present, error stop if absent

zero cost for callers that want to handle errors, safe default for those that do not; the convention used by stdlib

present on every path; a missing status can hide a missing check in code review

Error derived type (status_t with code and allocatable message)

one argument; room for a location, a code and a text; ok() and failed() type-bound predicates

not usable from pure code if the type allocates; the type must be shared by all modules

Logical or integer function result

reads naturally in an if

a function result cannot also be the value, so the value goes in an intent(out) argument

Named exit codes (parameter integers or an enum, bind(c))

scripts and tests can recognize the failure; one place defines them

must be kept in step with the documentation of the program

Three conventions make any of them work well:

  • Libraries report, programs stop. A procedure in a module that other programs call should never call stop. It returns a status, or stops only when the caller supplied no way to receive one. The main program, the one place that knows what failure means for the user, decides on the message, the cleanup and the exit status.

  • A programming error is not a status. A violated precondition (a negative array size, an unknown solver name) is a bug in the caller; error stop with a message names it and gives a nonzero status. Do not invent a status code for a condition the caller cannot sensibly handle.

  • Pure code returns its status. A pure procedure cannot print or stop. It can return a status or, for a true bug, use error stop (error stop in pure procedures).

The next example is a complete, small version of the optional-status convention for heatflow: a status type, a helper that either fills the status or stops, and a validator that uses it. Calling validate_grid with a status handles the error locally; calling it without one terminates with the grid-specific exit code.

module heatflow_status
  use, intrinsic :: iso_fortran_env, only: error_unit
  implicit none
  private
  public :: status_t, status_ok, status_fail, report
  public :: exit_config, exit_grid, exit_io

  integer, parameter :: exit_config = 2, exit_grid = 3, exit_io = 4   ! process exit codes

  type :: status_t
    integer :: code = 0                           ! 0 means success
    character(len=:), allocatable :: message
  contains
    procedure :: failed => status_failed
  end type status_t

contains

  pure function status_ok() result(s)
    type(status_t) :: s
    s%code = 0
    s%message = ''
  end function status_ok

  pure function status_fail(code, message) result(s)
    integer, intent(in) :: code
    character(len=*), intent(in) :: message
    type(status_t) :: s
    s%code = code
    s%message = message
  end function status_fail

  elemental logical function status_failed(self)
    class(status_t), intent(in) :: self
    status_failed = self%code /= 0
  end function status_failed

  !> Optional-status convention: report to the caller if it asked, otherwise stop.
  subroutine report(s, status)
    type(status_t), intent(in) :: s
    type(status_t), intent(out), optional :: status
    if (present(status)) then
      status = s
    else if (s%failed()) then
      write (error_unit, '(a)') 'fatal: '//s%message
      error stop s%code
    end if
  end subroutine report
end module heatflow_status

module heatflow_checks
  use heatflow_status
  implicit none
  private
  public :: validate_grid
contains
  subroutine validate_grid(nx, ny, status)
    integer, intent(in) :: nx, ny
    type(status_t), intent(out), optional :: status
    character(len=80) :: text
    if (nx < 3 .or. ny < 3) then
      write (text, '(a, i0, a, i0, a)') 'grid ', nx, ' x ', ny, ' is smaller than 3 x 3'
      call report(status_fail(exit_grid, trim(text)), status)
    else
      call report(status_ok(), status)
    end if
  end subroutine validate_grid
end module heatflow_checks

program status_design
  use heatflow_status
  use heatflow_checks
  implicit none
  type(status_t) :: st

  call validate_grid(21, 21, st)
  print '(a, l1)', '21 x 21 failed: ', st%failed()

  call validate_grid(2, 40, st)                  ! the caller handles the failure
  print '(a, l1, a, i0, a, a)', ' 2 x 40 failed: ', st%failed(), '  code = ', st%code, '  message = ', st%message

  print '(a)', 'now without a status argument'
  call validate_grid(2, 40)                      ! nobody is listening: error stop
  print '(a)', 'not reached'
end program status_design

Output with gfortran 16.2 (the program’s exit status is 3):

21 x 21 failed: F
 2 x 40 failed: T  code = 3  message = grid 2 x 40 is smaller than 3 x 3
now without a status argument
fatal: grid 2 x 40 is smaller than 3 x 3
ERROR STOP 3
exit status: 3

Flang 23 produces the same program output and exit status, with its own stop text, Fortran ERROR STOP: code 3.

Notes on the design: status is intent(out), so it is reset on every call, and present(status) is the whole decision. Because error stop accepts a nonconstant integer (F2018) the exit code comes straight from the status. report writes to error_unit first so that the message is the last thing the user reads before the process ends. The type has an allocatable component, so status_fail and status_ok are pure but they allocate: if you need a status inside a pure procedure that is called from a do concurrent loop, use plain integer codes instead.

stdlib: stdlib_error, check and state_type

The Fortran standard library stdlib (community project, not part of ISO Fortran; see Fortran stdlib) ships the same convention as the stdlib_error module. The version checked here is stdlib 0.8.1, where every entity in the module is marked experimental. It provides:

  • check(condition, msg, code, warn): if condition is false, print msg (default Check failed.) to stderr and stop with exit code code (default 1), or only print when warn is true;

  • error_stop(msg, code): a subroutine that performs an error stop with the message and the code (a procedure, so it can be passed around and wrapped);

  • state_type: a derived type with an integer flag, a fixed-size message and a location string, with the type-bound procedures ok(), error(), print(), print_msg() and handle(); a generic constructor of the same name builds a state from a location, a flag and a list of integer, real, complex or character values to put in the message;

  • the flags STDLIB_SUCCESS (0), STDLIB_VALUE_ERROR, STDLIB_LINALG_ERROR, STDLIB_INTERNAL_ERROR, STDLIB_IO_ERROR and STDLIB_FS_ERROR (all negative), and comparison operators between a state and an integer flag.

The stdlib documentation describes this type on its "state_type" page (the spec file in the 0.8.1 repository is stdlib_error_state_type.md, and the module is stdlib_error). The handle procedure is the optional-status convention in one call: if an output state was passed it receives the state, otherwise an error state stops the program.

module heatflow_load
  use stdlib_error, only: state_type, STDLIB_VALUE_ERROR, STDLIB_IO_ERROR
  implicit none
  private
  public :: parse_nx
contains
  !> Read the grid size from text. With a state argument the routine reports; without one it stops.
  integer function parse_nx(text, err0) result(nx)
    character(len=*), intent(in) :: text
    type(state_type), intent(out), optional :: err0
    type(state_type) :: err
    integer :: stat

    read (text, *, iostat=stat) nx
    if (stat /= 0) then
      nx = -1
      err = state_type('parse_nx', STDLIB_IO_ERROR, 'cannot read an integer from "'//text//'"')
    else if (nx < 3) then
      err = state_type('parse_nx', STDLIB_VALUE_ERROR, 'nx must be at least 3, got', nx)
    end if
    call err%handle(err0)                        ! copies to err0 if present, otherwise error stop
  end function parse_nx
end module heatflow_load

program stdlib_error_demo
  use stdlib_error, only: check, state_type
  use heatflow_load, only: parse_nx
  implicit none
  type(state_type) :: err
  integer :: nx

  nx = parse_nx('21', err)
  print '(a, i0, a, l1)', 'nx = ', nx, ', ok = ', err%ok()

  nx = parse_nx('two', err)
  print '(a, l1)', 'ok = ', err%ok()
  print '(a)', err%print()

  nx = parse_nx('2', err)
  print '(a)', err%print()

  call check(nx == 2, msg='nx should be 2 here')              ! passes: silent
  call check(nx > 100, msg='nx > 100 failed', warn=.true.)    ! prints and carries on
  print '(a)', 'now without a state argument'
  nx = parse_nx('1')                                          ! nobody listens: the program stops
  print '(a)', 'not reached'
end program stdlib_error_demo

This was built with gfortran 16.2 (-std=f2023 and the usual warning options, no warnings) against stdlib 0.8.1, which was built from the v0.8.1 tag with the project’s own preprocessing script and fpm. Output, with stderr and stdout merged:

nx = 21, ok = T
ok = F
[parse_nx] returned I/O Error: cannot read an integer from "two"
[parse_nx] returned Value Error: nx must be at least 3, got 2
 nx > 100 failed
now without a state argument
ERROR STOP [parse_nx] returned Value Error: nx must be at least 3, got 1
exit status: 1

state_type(location, flag, ...) joins the trailing values into the message, so nx is formatted without a format. The warn=.true. call prints the message (through list-directed output, hence the leading blank) and goes on. The error stop text is the printed state, and its exit status is 1, because the stop code is a character string. This example was not built with Flang: the module files of a library are specific to the compiler that built it, and stdlib was built only with gfortran here. One discrepancy in the documentation: the specification text lists the default of warn as .true., but the source and a test run (call check(a == 5, msg='message')) stop the program with exit status 1, so the default is false.

stop and error stop

stop and error stop are the only statements that end a program on purpose. Their syntax is (R1162 to R1164):

stop [stop-code] [, quiet=scalar-logical-expr]
error stop [stop-code] [, quiet=scalar-logical-expr]

where stop-code is a scalar default character expression or a scalar integer expression of default kind (C1176). Note the comma: with no stop code the quiet= specifier is written stop , quiet=.true., and stop quiet=.true. is a syntax error on both compilers.

Aspect Rule Clause

Kind of termination

stop initiates normal termination, error stop initiates error termination. In a program with several images, normal termination happens when all images have initiated it; error termination on any one image terminates all of them

5.3.7, 11.4

Stop code

made available in a processor-dependent way. Recommended: an integer code is the process exit status; with a character code or no code, stop gives status 0 and error stop gives a nonzero status

11.4

Output

unless quiet= is true, if any IEEE exception is signaling the processor shall warn on error_unit, and a stop code should be written, formatted, to the same unit

11.4

quiet=

if present and true, no exception summary and no stop code are written (F2018)

11.4, C.1

Nonconstant code

any scalar default character or default integer expression, not only a constant (F2018)

C.1

Range

the integer code might be reduced by the processor, for example to its low 8 bits

11.4 Note 2

Finalization

entities existing at termination are not finalized

7.5.6.4

Files

on normal termination all connected units are closed (scratch files deleted); the standard does not say this for error stop, but both compilers closed them in the test below

12.5.7.1

end program

executing the end of the main program is also normal termination, with status 0 and no output

5.3.7

error stop was introduced in Fortran 2008 (the GNU Fortran Fortran 2008 status page lists it as supported since 4.6), and quiet=, the nonconstant stop code and error stop in pure procedures are Fortran 2018 (Annex C.1). The old pause statement is gone: Annex B.1 lists it among the features deleted from Fortran 90, that is, in Fortran 95.

The cases below exercise every form once. The program takes the name of a case on the command line:

program stop_variants
  implicit none
  character(len=12) :: arg
  integer :: code

  call get_command_argument(1, arg)
  code = 7
  select case (trim(arg))
  case ('end');       continue                         ! falls off end program: normal termination
  case ('stop');      stop
  case ('stop-int');  stop 3
  case ('stop-char'); stop 'finished early'
  case ('stop-var');  stop code + 1                    ! nonconstant stop code (F2018)
  case ('stop-quiet'); stop 3, quiet=.true.
  case ('err');       error stop
  case ('err-int');   error stop 4
  case ('err-char');  error stop 'solver diverged'
  case ('err-var');   error stop code                  ! nonconstant stop code (F2018)
  case ('err-quiet'); error stop 4, quiet=.true.
  case ('big');       stop 300                         ! only the low 8 bits reach the shell
  case default;       print '(a)', 'usage: stop_variants <case>'
  end select
end program stop_variants

The table shows what each case wrote to standard error and the exit status seen by the shell. The first output column is gfortran 16.2, the second Flang 23. Flang writes a blank line after the lines that give a code.

Case gfortran output Exit Flang output Exit

end program

nothing

0

nothing

0

stop

nothing

0

Fortran STOP

0

stop 3

STOP 3

3

Fortran STOP: code 3

3

stop 'finished early'

STOP finished early

0

Fortran STOP: finished early

0

stop code + 1 (value 8)

STOP 8

8

Fortran STOP: code 8

8

stop 3, quiet=.true.

nothing

3

nothing

3

error stop

ERROR STOP

1

Fortran ERROR STOP: code 1

1

error stop 4

ERROR STOP 4

4

Fortran ERROR STOP: code 4

4

error stop 'solver diverged'

ERROR STOP solver diverged

1

Fortran ERROR STOP: solver diverged

1

error stop code (value 7)

ERROR STOP 7

7

Fortran ERROR STOP: code 7

7

error stop 4, quiet=.true.

nothing

4

nothing

4

stop 300

STOP 300

44

Fortran STOP: code 300

44

Reading the table:

  • Character codes do not set the status. stop 'finished early' exits with 0 and error stop 'solver diverged' with 1, exactly as the standard recommends. If a script has to tell failures apart, use integer codes.

  • The code is reduced to 8 bits by the shell. 300 became 44 (300 mod 256) on both compilers; stay within 1 to 125 so that the value cannot be mistaken for a shell or signal status (126, 127 and 128+n have special meanings).

  • quiet= removes the output, not the status. It is for programs that print their own message, and it is the way to keep a clean standard error when the exception summary would be noise.

  • The text on stderr is processor dependent. gfortran prints STOP code, Flang prints Fortran STOP: code n. Do not parse it; use the exit status.

  • A bare stop does not mean success to the reader. Prefer falling off end program for success and an explicit error stop for failure, so that the intent is visible.

Exception flags at termination

When quiet= is absent or false, a stop or error stop reports the IEEE exceptions that are signaling at that moment (11.4). The program below divides by zero, prints the Infinity, and then stops with or without quiet=:

program ieee_at_stop
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: zero, ratio
  character(len=8) :: arg

  call get_command_argument(1, arg)
  zero = 0.0_dp
  ratio = 1.0_dp / zero                      ! divide-by-zero flag is raised (no trap by default)
  print '(a, es9.2)', 'ratio = ', ratio
  if (arg == 'quiet') then
    stop , quiet=.true.
  else
    stop
  end if
end program ieee_at_stop

gfortran 16.2, run with the argument plain:

ratio =  Infinity
Note: The following floating-point exceptions are signalling: IEEE_DIVIDE_BY_ZERO
exit status: 0

Flang 23, same argument:

ratio =  Infinity
Fortran STOP
IEEE arithmetic exceptions signaled: DIVBYZERO
exit status: 0

With the argument quiet both compilers print only the first line. The exit status is 0 in every case: the summary is information, not an error. Two compiler-specific details. gfortran lists every flag except inexact by default and -ffpe-summary=list changes the list (none, all or a set of invalid, zero, overflow, underflow, inexact, denormal); the manual says the summary is printed when stop or error stop is invoked. Flang 23 also includes the inexact flag in its summary, as the pure-procedure example below shows. Source: GNU Fortran manual: Options for debugging your program (-ffpe-trap, -ffpe-summary)

error stop in pure procedures

A pure procedure cannot contain a stop statement: stop is an image control statement (11.7.1) and C15109 forbids those in pure subprograms, and a pure procedure may have no external input/output either (C15107, C15108). Fortran 2018 added the exception: error stop is not an image control statement and is permitted in a pure subprogram (Annex C.1; the GNU Fortran Fortran 2018 status page lists it too). That makes it the standard way to report a precondition violation from the pure kernels and elemental functions that do concurrent requires.

module heatflow_guard
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: stable_dt
  integer, parameter :: dp = real64
contains
  !> Largest stable FTCS time step; a nonpositive spacing is a programming error.
  pure function stable_dt(alpha, dx, dy) result(dt)
    real(dp), intent(in) :: alpha, dx, dy
    real(dp) :: dt
    if (alpha <= 0.0_dp .or. dx <= 0.0_dp .or. dy <= 0.0_dp) then
      error stop 'stable_dt: alpha, dx and dy must be positive'
    end if
    dt = 0.25_dp * min(dx, dy)**2 / alpha
  end function stable_dt
end module heatflow_guard

program pure_error_stop
  use, intrinsic :: iso_fortran_env, only: real64
  use heatflow_guard, only: stable_dt
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: dt(3)
  integer :: i

  dt = [(stable_dt(1.0e-2_dp, 0.05_dp * i, 0.5_dp), i=1, 3)]
  print '(a, 3es11.3)', 'stable dt: ', dt
  dt = [(stable_dt(1.0e-2_dp, 0.05_dp * (i - 2), 0.5_dp), i=1, 3)]
  print '(a)', 'not reached'
end program pure_error_stop
stable dt:   6.250E-02  2.500E-01  5.625E-01
ERROR STOP stable_dt: alpha, dx and dy must be positive
exit status: 1

Flang 23 prints the same first line and Fortran ERROR STOP: stable_dt: alpha, dx and dy must be positive, and also IEEE arithmetic exceptions signaled: INEXACT (it reports the inexact flag, which the arithmetic of the first line raised), with exit status 1. In the third element i - 2 gives zero spacing, so the second statement stops.

A stop in the same position is rejected at compile time. This module is deliberately wrong:

module m
  implicit none
contains
  pure subroutine fail(msg)
    character(len=*), intent(in) :: msg
    if (len(msg) > 0) stop 1
  end subroutine fail
end module m

gfortran 16.2:

06b_pure_stop_rejected.f90:6:28:

    6 |     if (len(msg) > 0) stop 1
      |                            1
Error: STOP statement not allowed in PURE procedure at (1)

Flang 23:

06b_pure_stop_rejected.f90:6:5: error: An image control statement may not appear in a pure subprogram
      if (len(msg) > 0) stop 1
      ^^^^^^^^^^^^^^^^^^^^^^^^

Notice that the two diagnostics name different reasons for the same rule: gfortran names the statement and Flang the class of statements it belongs to. The practical advice: use error stop for bugs in pure code and keep recoverable conditions as returned status values, which pure code can do too.

Data left behind: files, buffers and finalization

On normal termination the standard closes every connected unit with status='keep' (status='delete' for scratch files) and says nothing equivalent for error termination. Finalizers are not run in either case (7.5.6.4), so a final subroutine cannot be relied on to write a restart file. The next program writes a line to a file and then ends with stop 1 (argument stop), with error stop 2 (argument error), or with error stop 2 after an explicit flush (argument flush); the shell then prints the file:

program unit_flush
  implicit none
  character(len=12) :: arg
  integer :: unit

  call get_command_argument(1, arg)
  open (newunit=unit, file='log.txt', status='replace', action='write')
  write (unit, '(a)') 'step 1 written'
  if (trim(arg) == 'flush') flush (unit)
  select case (trim(arg))
  case ('stop');  stop 1
  case default;   error stop 2
  end select
end program unit_flush

All three variants, with both compilers, left log.txt containing step 1 written and returned the exit status 1 or 2, so both runtimes close open units on error stop. That is a courtesy of these runtimes, not a promise of the standard. If a restart file matters, flush it (or close it) before the stop; and never rely on a final procedure or on another image’s data being intact.

Exit statuses and the calling script

The exit status is what a build script, a batch scheduler or a test runner sees. The standard only recommends how a stop code maps to it (11.4), and process statuses are limited to 0 to 255 by the operating system. A workable scheme for an application:

Exit status Meaning in heatflow

0

normal termination: end program or stop without a code

1

an error stop with a message and no code, or an error termination caused by the runtime (failed allocation, bad I/O)

2

configuration cannot be read (exit_config in the example above)

3

invalid grid (exit_grid)

4

output cannot be written (exit_io)

The heatflow application turns a failed read_config into the second row. This is its real behavior, with a file that does not exist (gfortran 16.2 built through fpm; the backtrace frames that follow the message are omitted):

call read_config(trim(file), cfg, stat, msg)
if (stat /= 0) then
  write (*, '(a)') 'cannot read configuration: '//trim(msg)
  error stop 2
end if
$ heatflow missing.nml
cannot read configuration: Cannot open file 'missing.nml': No such file or directory
ERROR STOP 2

Error termination. Backtrace:
$ echo $?
2

A calling script then tests the status:

if ! heatflow case-17.nml; then
    status=$?
    case $status in
        2) echo "configuration problem in case-17.nml" ;;
        3) echo "grid too small" ;;
        *) echo "heatflow failed with status $status" ;;
    esac
fi

Under fpm run the same failure is reported as Execution for object " heatflow " returned exit code 2, so the status survives the package manager. Two more points. First, a status is for machines: also write a human-readable message to error_unit (not to output_unit, which may be redirected into the results file). Second, in a program with several images or MPI ranks the status of the whole job is decided by the launcher, not only by the code of one image.

The vendor extensions exit and abort

Compilers provide call exit(status) and call abort() as extensions. They are not part of any Fortran standard. They exist in gfortran and in Flang, and legacy code calls them. With -std=f2023 gfortran treats them as ordinary external procedures, and the link fails:

13_exit_abort.f90:(.text+0xf0): undefined reference to `exit_'
13_exit_abort.f90:(.text+0x14c): undefined reference to `abort_'

Without -std=f2023 (here -std=gnu) the program below runs, and gfortran gives status 5 for exit(5) and 134 (SIGABRT) for abort; Flang gives the same two statuses and prints a stack trace for abort.

program exit_abort
  implicit none
  character(len=8) :: arg

  call get_command_argument(1, arg)
  print '(a)', 'before'
  if (trim(arg) == 'exit') call exit(5)        ! vendor extension, not in the standard
  if (trim(arg) == 'abort') call abort()       ! vendor extension, not in the standard
  print '(a)', 'after'
end program exit_abort

Prefer stop with an integer code and error stop: they are standard, they work in every compiler and they take part in the termination rules for several images. Use abort only when you want a core file for a debugger. The GNU manual pages for the two intrinsics are EXIT and ABORT.

IEEE flags as an error channel

Floating-point arithmetic reports problems through the five IEEE exception flags rather than through status arguments. By default a flag only records that something happened (the result is Infinity or NaN) and execution continues. The module ieee_exceptions (also visible through ieee_arithmetic) lets a program read and clear the flags, and ask for halting (17.3, 17.6). The program below provokes the three serious exceptions, reads the flags and clears them. The full catalogue of rounding modes, ieee_value and ieee_class is on Floating-Point and IEEE Arithmetic.

program ieee_flags
  use, intrinsic :: ieee_arithmetic
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  type(ieee_flag_type), parameter :: watched(3) = [ieee_invalid, ieee_divide_by_zero, ieee_overflow]
  character(len=14), parameter :: names(3) = [character(len=14) :: 'invalid', 'divide_by_zero', 'overflow']
  logical :: raised(3)
  real(dp) :: zero, ten, b, c, d
  integer :: i

  call ieee_set_flag(watched, .false.)             ! start from a clean slate
  zero = 0.0_dp
  ten = 10.0_dp
  b = 1.0_dp / zero                                ! divide by zero  (b = +Infinity)
  c = zero / zero                                  ! invalid         (c = NaN)
  d = huge(1.0_dp) * ten                           ! overflow        (d = +Infinity)

  call ieee_get_flag(watched, raised)
  do i = 1, size(watched)
    print '(a, a, l1)', names(i), ' signaling: ', raised(i)
  end do
  print '(a, 3l2)', 'finite results (b, c, d): ', ieee_is_finite(b), ieee_is_finite(c), ieee_is_finite(d)
  print '(a, l2)', 'c is NaN: ', ieee_is_nan(c)
  print '(a, l2)', 'halting supported for divide_by_zero: ', ieee_support_halting(ieee_divide_by_zero)

  call ieee_set_flag(ieee_all, .false.)            ! clear every flag, so that stop reports none
  stop 'flags cleared: no exception summary at termination'
end program ieee_flags
invalid        signaling: T
divide_by_zero signaling: T
overflow       signaling: T
finite results (b, c, d):  F F F
c is NaN:  T
halting supported for divide_by_zero:  T
STOP flags cleared: no exception summary at termination

Flang 23 prints the same lines, with Fortran STOP: flags cleared: no exception summary at termination as the last one. Source: J3/23-007r1: 17.3 The exceptions, 17.6 Halting, 17.11.5 IEEE_GET_FLAG, 17.11.39 IEEE_SET_FLAG

Where the program should react to a flag, the pattern is: clear the flags of interest, run the computation, read the flags once, and decide. Reading a flag after every operation defeats vectorization; reading it per sweep of the solver costs nothing. ieee_support_halting(flag) says whether halting can be switched on for the exception; with ieee_set_halting_mode(flag, .true.) an exception then stops execution, but the standard says halting is "not precise" and can occur at any time after the exception.

For the whole program, compilers offer the equivalent option. gfortran’s -ffpe-trap=invalid,zero,overflow enables the traps for the three serious exceptions and turns the first occurrence into a SIGFPE. On the divide-by-zero program of the previous section the result is (the message line is the shell’s, printed because the process died of a signal):

Floating point exception
exit status: 136

(136 is 128 + 8, the signal number of SIGFPE; with -fbacktrace, the default, gfortran prints a backtrace first.) The GNU manual advises enabling at least those three traps unless the program provides for the exceptions and notes that by default no trap is enabled; once enabled a trap can only be cleared with -ffpe-trap=none. Flang 23’s --help listing has no equivalent option on the version tested: use ieee_set_halting_mode in the program.

Termination in coarray programs

A program with several images (Coarrays, Images and Synchronization) adds three rules to the picture (5.3.7, 11.4, 11.5):

  • Normal termination needs everyone. An image that executes stop or reaches end program is stopped, but the program only terminates normally when all images have initiated normal termination. In three steps, initiation, synchronization and completion: the images synchronize at the second step so no image starts the completion step (which closes units) before all have finished initiation. The data of a stopped image stay available to the others.

  • Error termination spreads. When one image initiates error termination (error stop, or an error condition that the standard says causes it), the processor should terminate all the others as quickly as possible.

  • An image can fail without terminating. The fail image statement makes the executing image cease to participate without initiating termination, to test recovery code; on hardware that can detect a dead image the same state happens for real. failed_images(), stopped_images() and image_status() (16.9.87, 16.9.199, 16.9.108) list them.

The stat= specifier of image control statements reports these states. It is the zero for success, stat_stopped_image if the current team contains a stopped image, stat_failed_image if it contains a failed image and no other error occurred, and a different positive value for other errors (11.7.11). Without stat= an error condition on an image control statement initiates error termination. A program that wants to survive a stopped neighbour therefore writes sync all(stat=s) and tests s.

The program below has image 2 stop or error-stop after a first sync all:

program coarray_termination
  implicit none
  character(len=12) :: arg
  integer :: me, stat
  character(len=64) :: msg

  call get_command_argument(1, arg)
  me = this_image()
  print '(a, i0, a, i0)', 'image ', me, ' of ', num_images()
  sync all
  select case (trim(arg))
  case ('error')                        ! image 2 error-terminates: every image must terminate
    if (me == 2) error stop 'image 2 failed'
    sync all
  case ('stop')                         ! image 2 stops normally: the others may continue
    if (me == 2) stop 'image 2 is done'
    sync all (stat=stat, errmsg=msg)
    print '(a, i0, a, i0)', 'image ', me, ': sync all stat = ', stat
  end select
  print '(a, i0, a)', 'image ', me, ' reached the end'
end program coarray_termination

This was built with OpenCoarrays 2.10.3 (caf and cafrun -n 4, which uses MPI) and with GCC 16.2’s native shared-memory library (-fcoarray=lib with -lcaf_shmem). Flang 23 cannot build it: its multi-image support is experimental and the link failed on missing runtime symbols of the PRIF interface, so Flang results are not given. The error case under OpenCoarrays:

image 1 of 4
image 2 of 4
image 4 of 4
image 3 of 4
ERROR STOP image 2 failed
Abort(1) on node 1 (rank 1 in comm 0): application called MPI_Abort(MPI_COMM_WORLD, 1) - process 1
Error: Command:
   `/usr/bin/mpiexec -n 4 /tmp/co2.out error`
failed to run.
exit status: 1

No image reached its last print, and the job’s exit status is 1: the whole job was terminated, as the standard requires for error termination. The stop case under OpenCoarrays also ended the job with Abort(0) on node 1 ... MPI_Abort and status 0, without the other images reaching the end: the standard allows the other images to carry on and the runtime tested did not do that. With the shared-memory library the behavior was different again: in both the stop and the error case the program printed the stop message and then did not end. The other three images were still waiting for the next sync all when a 10 second timeout killed the run (status 124), in every one of three repeated stop runs and two error runs, and the library printed ERROR: Image 2 ... failed with signal 0, exitstatus 1. in the error case. The sync all (stat=stat) therefore never reported stat_stopped_image. Treat termination and failure detection in coarray programs as runtime-specific: test your chosen runtime, set a timeout in the launcher, and prefer designs where no image waits for one that may have stopped.

Runtime checks and what happens without them

A program that violates a rule of the language is not an error in the sense of this page unless something detects it. Most violations are not detected by default: the standard places the burden on the programmer, and a conforming processor is not required to diagnose them. The common ones and their behavior with and without checking, for the program below, run once per case:

program checks_off
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: u(10)
  real(dp), allocatable :: v(:)
  character(len=8) :: arg
  integer :: i

  call get_command_argument(1, arg)
  u = 1.0_dp
  i = 11
  select case (trim(arg))
  case ('bounds')
    print '(a, f4.1)', 'u(11) = ', u(i)          ! outside 1:10
  case ('alloc')
    print '(a, f4.1)', 'v(1) = ', v(1)           ! v is not allocated
  case ('dealloc')
    deallocate (v)                               ! nothing to deallocate
  end select
end program checks_off
Case gfortran with -fcheck=all gfortran without checks Flang 23

bounds: u(11) of a 10-element array

runtime error, exit 2: Index '11' of dimension 1 of array 'u' above upper bound of 10

prints u(11) = 0.0 (whatever is in memory), exit 0

prints u(11) = 0.0, exit 0

alloc: v(1) of an unallocated array

runtime error, exit 2: Index '1' of dimension 1 of array 'v' above upper bound of 0

Segmentation fault, exit 139

Segmentation fault, exit 139

dealloc: deallocate of an unallocated array with no stat=

runtime error, exit 2: Attempt to DEALLOCATE unallocated 'v'

the same runtime error, exit 2

silent, exit 0

gfortran’s runtime errors carry the file and line (At line 15 of file 07_checks_off.f90) and the text Fortran runtime error. The first row shows the worst outcome: a bounds violation without checking gives a plausible number and no signal. The second shows the second worst: a crash far from the cause. The third shows a difference between compilers on a rule the standard states plainly (a deallocate of an unallocated allocatable is an error condition and error-terminates when there is no stat=): gfortran reports it with or without -fcheck, Flang 23 does not.

What to do about it, in order of cost:

  • Develop with the checks on. gfortran -fcheck=all -g -fbacktrace enables the bounds, array-temps, bits, do (modification of a loop variable), mem, pointer and recursion checks (options documented in the Code Gen Options page). The checks slow the program down, so release builds omit them. Flang 23 has no bounds-checking option in its --help listing on the version tested, so run the test suite with gfortran, or with another compiler that has run-time checks, as well.

  • Add stat= where the program can recover, as above, so that a failure is a value rather than a death.

  • Use the other tools that look for what the compiler does not: valgrind for invalid reads and leaks, -fsanitize=address,undefined for out-of-range accesses at run time and -ffpe-trap for floating-point exceptions. They are covered with profilers and unit-test frameworks on Testing, Debugging and Profiling.

  • Write assertions for invariants. if (.not. condition) error stop 'message' costs one branch; a small helper, like the stdlib check, keeps the call site short. A precondition checked by an assertion in a hot loop can be disabled by a preprocessor flag in release builds (see Program Structure and Source Form for .F90 and cpp).

Practical guidelines

  • Put stat= (and errmsg=) on every allocate whose size comes from input or from a computation that can be large. Test only stat /= 0.

  • Put iostat= and iomsg= on every open, read, write and close that touches a user’s file. Test for iostat_end first, then for any other error. Never test for a particular positive code.

  • Give routines that can fail a status argument; make it optional with an error stop fallback if the routine is also used in simple programs. Do not call stop from library code.

  • Use error stop with a message for bugs, stop or end program for success and finished-early, and integer codes between 1 and 125 for failures a script must recognize. Character codes never change the exit status.

  • Write failure messages to error_unit. Flush or close files whose contents matter before an error stop.

  • In pure procedures use error stop, never stop; do not allocate a status in code that may be called from a do concurrent loop.

  • Clear the IEEE flags before a computation whose exceptions you intend to inspect, and clear them again before a stop if you do not want the summary; use quiet=.true. when the summary would be noise.

  • Run the unit tests with -fcheck=all (and -ffpe-trap=invalid,zero,overflow) on at least one compiler.

  • Do not use call exit or call abort in code that has to build with -std=f2023.

Common pitfalls

  • Ignoring stat. A stat= that is never tested is worse than none: the program neither stops nor reports, and uses the object that was not allocated.

  • Comparing iostat with a number. if (stat == -1) works on today’s compilers; if (stat == iostat_end) is the portable form. stat < 0 also catches iostat_eor.

  • Treating end of file as an error, or the reverse. The read loop needs the iostat_end test before the generic check.

  • A stale errmsg. It keeps its previous contents when no error occurs; clear it before use if you print it.

  • Using a variable after an error. After a failed read the items are undefined and the file position is indeterminate (12.11.2); after a failed allocate the object keeps its earlier status.

  • deallocate of an unallocated array. An error condition: guard with allocated.

  • stop with a character code and the expectation of a nonzero status. Only integer codes set the status.

  • stop 256. The shell sees 0 (both compilers returned status 0 on the Linux container used). Keep the codes small.

  • Expecting final procedures or buffered output to survive a stop. Neither is guaranteed.

  • A stop inside a library routine. It kills a host program that could have recovered, and in a multi-image program it takes the other images with it or leaves them waiting.

  • Writing the message to output_unit and the data to the same unit. A failure message mixed into a redirected result file is easy to miss.

  • Suppressing the summary with quiet= and then forgetting that flags are still set. The flags stay signaling for later code that reads them.

  • Relying on a coarray runtime’s reaction to a stopped image. It differs between OpenCoarrays, the native GCC 16 library and other compilers (Termination in coarray programs).

  • Using -fcheck=all for timing. The checks change the performance by large factors; time the release build.

Where older references differ

  • err=, end= and eor= are not obsolescent in Fortran 2023. A recommendation to avoid them is the book author’s style, not the standard’s. iostat= is preferred because it carries a value and does not need a label.

  • stop with a numeric code. Older texts describe stop as taking only a constant string of digits or a character constant; since Fortran 2018 any scalar integer or default character expression is allowed (C.1), and error stop (Fortran 2008) takes the same.

  • quiet= is new in Fortran 2018 and absent from older books and from compilers that implement only Fortran 2008.

  • error stop in pure procedures is Fortran 2018. Older code works around the ban by returning a status from everywhere.

  • pause is a deleted feature (Fortran 95, Annex B.1); a program that pauses for the operator should read from input instead.

  • Image failure and stat_failed_image are Fortran 2018 (they came from the coarray technical specification); a program written for Fortran 2008 coarrays has no way to survive a failed image.

  • The vendor extensions exit and abort appear in many tutorials as if they were standard; they are not (see above).

  • Exception handling by IEEE flags reads differently from IEEE 754-2008 references: the standard now follows IEEE 754-2019 (ISO/IEC 60559:2020), see Floating-Point and IEEE Arithmetic.

See also Best Practices and Style and Common Gotchas and Pitfalls, and, for the I/O and allocation statements themselves, Input/Output: Units and Files and Allocatable Arrays and Dynamic Memory.