Enumerations

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-19. Every example marked as run was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and also with LLVM Flang 23.1.4; the output shown is real output. The F2023 enumeration type and the typed form of enum are the exception: neither compiler runs them, so those examples are labelled as checked against the standard text, and, where it was possible, against the semantic analysis of Flang, not as run. Enumerations are defined in clause 7.6 of the standard ("Other nonintrinsic types"): 7.6.1 for interoperable enumerations and enum types, 7.6.2 for enumeration types.

An enumeration is a closed set of named values. Fortran has had one since Fortran 2003, but only as a companion of the C interoperability feature: a block of enumerator statements that declares named integer constants with the kind that a C compiler would pick for the matching enum. Fortran 2023 keeps that form, renames it interoperable enumeration and lets it carry a type name (an enum type), and adds a second, purely Fortran feature, the enumeration type, whose values are not integers at all. The two are easy to confuse, so keep this table in mind for the rest of the page.

Interoperable enumeration (enum, bind(c)) Enumeration type (enumeration type)

Statement

enum, bind(c) …​ enumerator …​ end enum

enumeration type :: name …​ enumerator …​ end enumeration type

Clause

7.6.1

7.6.2

What the enumerators are

named integer constants of a C-compatible kind

named constants of a new nonintrinsic type

Interoperable with C

yes, with the matching C enum

no

Type name

optional (enum, bind(c) :: name, new in Fortran 2023)

required

Mixing with integers

free (an enumerator is an integer)

not allowed, except through the constructor and int

Arithmetic

all integer operations

none, only comparison

Provenance

Fortran 2003; typed form and renaming in Fortran 2023

Fortran 2023

GNU Fortran 16.2

yes, untyped form only

no

LLVM Flang 23.1.4

yes, untyped form only

parsed and checked with -fenumeration-type, but not compiled

Readers coming from C will find the corresponding construct in Constants, Enumerations and Initialization; the important difference is that Fortran’s interoperable enumeration is in practice only a way to declare a group of integer constants, while the F2023 enumeration type behaves like the enums of Ada or Rust. Where to place the declarations is covered in Modules and Submodules, and the integer kinds that enumerators receive in Intrinsic Types and Kinds.

Interoperable enumerations

An interoperable enumeration (7.6.1) is an enum, bind(c) statement, one or more enumerator statements and an end enum. The enumerators it declares are named integer constants: each behaves exactly as if it had been declared with the parameter attribute, so it can appear in constant expressions, array bounds, do bounds, case values and initializers, and it cannot be assigned to. All the enumerators of one enum block have the same kind, and the standard requires that kind to be the one that is interoperable with the C enum that would be declared with the same values in the same order (7.6.1 and 18.3.1).

program enum_basic
  use, intrinsic :: iso_c_binding, only: c_int
  implicit none

  enum, bind(c)
    enumerator :: red = 4, blue = 9
    enumerator :: yellow
  end enum

  integer(kind(red)) :: colour
  integer :: k

  colour = blue
  print '(a, i0)', 'kind of the enumerators: ', kind(red)
  print '(a, i0)', 'c_int:                   ', c_int
  print '(a, 3(i0, 1x))', 'values: ', red, blue, yellow
  print '(a, l1)', 'colour == blue: ', colour == blue
  k = int(colour) + 1
  print '(a, i0)', 'blue + 1 = ', k
  select case (colour)
  case (red)
    print '(a)', 'red'
  case (blue, yellow)
    print '(a)', 'blue or yellow'
  end select
end program enum_basic

Output (identical with GNU Fortran 16.2 and LLVM Flang 23.1.4):

kind of the enumerators: 4
c_int:                   4
values: 4 9 10
colour == blue: T
blue + 1 = 10
blue or yellow

There is no Fortran type called "enum" in this form. A variable that holds an enumerator is declared with the kind of the enumerators, integer(kind(red)), as in the standard’s own note on interoperable enumerations. kind accepts any one of the enumerators as its argument, so the declaration follows the enum if its kind ever changes. Declaring the variable as plain integer works only while the processor’s choice happens to be the default integer kind, which it is for both compilers used here.

How the values are assigned

The rule of 7.6.1 is the same as in C. An enumerator with an explicit = constant-expression has that value. Otherwise the first enumerator of the block has the value zero and every later one has the value of its predecessor plus one. Several enumerator statements in one block are the same as one long statement: only their order matters. Values may be negative and may repeat, because nothing in 7.6.1 requires them to be distinct, which has consequences for select case, shown below.

program enum_values
  implicit none

  ! No value given: the first enumerator is 0, each next one is previous + 1
  enum, bind(c)
    enumerator :: first, second, third
  end enum

  ! Explicit values may restart the counting, be negative, or repeat
  enum, bind(c)
    enumerator :: low = -2, mid, high = 10, higher
    enumerator :: alias_of_high = 10
  end enum

  print '(a, 3(i0, 1x))', 'first, second, third: ', first, second, third
  print '(a, 4(i0, 1x))', 'low, mid, high, higher: ', low, mid, high, higher
  print '(a, i0)', 'alias_of_high: ', alias_of_high
  print '(a, l1)', 'high == alias_of_high: ', high == alias_of_high
  print '(a, 2(i0, 1x))', 'kinds: ', kind(first), kind(low)
end program enum_values

Output (identical with GNU Fortran 16.2 and LLVM Flang 23.1.4):

first, second, third: 0 1 2
low, mid, high, higher: -2 -1 10 11
alias_of_high: 10
high == alias_of_high: T
kinds: 4 4

Two syntax details come from the constraint on enumerator (C7111): the :: separator is optional in enumerator yellow but mandatory as soon as an = appears in the list. An enum block has no name unless you use the typed form described below, and the kind of each block is chosen separately, so two blocks in one program may in principle have different kinds.

Enumerators are named constants

Because an enumerator is a named constant, it is usable wherever a constant integer expression is, and it is not a variable. The first listing uses enumerators as the bounds of an array and of a do loop and as an index; the second tries to assign to one.

program enum_constants
  implicit none
  enum, bind(c)
    enumerator :: red = 1, green, blue
  end enum
  integer(kind(red)) :: colour
  character(len=5), parameter :: names(red:blue) = [character(len=5) :: 'red', 'green', 'blue']
  integer :: c

  ! Enumerators are integer named constants: array bounds, do bounds, case values
  do c = red, blue
    print '(i0, 1x, a)', c, trim(names(c))
  end do
  colour = green
  print '(2a)', 'colour is ', trim(names(colour))
end program enum_constants

Output (identical with both compilers):

1 red
2 green
3 blue
colour is green

This is the idiom for mapping an enumerator to a name: a constant array indexed by the enumerators. It works while the enumerators are consecutive and start at a known value, which is why the examples start the block at = 1; with gaps or negative values, use a select case function instead (see the heatflow module below).

Deliberate error, assigning to an enumerator:

program enum_assign
  implicit none
  enum, bind(c)
    enumerator :: red = 1, green, blue
  end enum
  red = 5
  print '(i0)', red
end program enum_assign

GNU Fortran 16.2:

03b_enum_assign.f90:6:3:

    6 |   red = 5
      |   1~~~
Error: Named constant 'red' in variable definition context (assignment) at (1)

LLVM Flang 23.1.4:

error: Semantic errors in 03b_enum_assign.f90
03b_enum_assign.f90:6:3: error: Assignment to constant 'red' is not allowed
    red = 5
    ^^^

Bit flags

Enumerators with distinct powers of two are the classic way to name bits. Since an enumerator is an integer, the bit intrinsics ior, iand, ieor and not apply directly (see Numeric Model, Bit Manipulation and Conversion).

module file_modes
  implicit none
  private
  public :: mode_read, mode_write, mode_exec, describe

  enum, bind(c)
    enumerator :: mode_read = 1, mode_write = 2, mode_exec = 4
  end enum

contains

  function describe(flags) result(text)
    integer(kind(mode_read)), intent(in) :: flags
    character(len=3) :: text
    text = '---'
    if (iand(flags, mode_read) /= 0)  text(1:1) = 'r'
    if (iand(flags, mode_write) /= 0) text(2:2) = 'w'
    if (iand(flags, mode_exec) /= 0)  text(3:3) = 'x'
  end function describe
end module file_modes

program enum_flags
  use file_modes
  implicit none
  integer(kind(mode_read)) :: perms

  perms = ior(mode_read, mode_write)
  print '(a)', describe(perms)
  perms = ior(perms, mode_exec)
  print '(a)', describe(perms)
  perms = iand(perms, not(mode_write))
  print '(a)', describe(perms)
end program enum_flags

Output (identical with both compilers):

rw-
rwx
r-x

The enumerators are made public by name in the module’s access list; private in the module header keeps the module’s other names hidden. Accessibility of the enumerators of an interoperable enumeration is therefore the ordinary accessibility of named constants (8.5.2); Fortran 2028 will allow it to be set per enumerator (see below).

select case and overlapping values

select case accepts an enumerator wherever it accepts a constant integer, including ranges such as case (mode_read:mode_exec) (see Control Flow). Because enumerators may repeat a value, the rule that no value may match two case selectors (C1154) can be violated by two different names, and the compiler reports it.

program case_overlap
  implicit none
  enum, bind(c)
    enumerator :: fast = 1, slow = 2
    enumerator :: default_speed = 1
  end enum
  integer(kind(fast)) :: speed

  speed = default_speed
  select case (speed)
  case (fast)
    print '(a)', 'fast'
  case (default_speed)
    print '(a)', 'default'
  case (slow)
    print '(a)', 'slow'
  end select
end program case_overlap

GNU Fortran 16.2:

13_case_overlap.f90:11:8:

   11 |   case (fast)
      |        1
   12 |     print '(a)', 'fast'
   13 |   case (default_speed)
      |        2
Error: CASE label at (1) overlaps with CASE label at (2)

LLVM Flang 23.1.4:

error: Semantic errors in 13_case_overlap.f90
13_case_overlap.f90:13:3: error: CASE (1_4) conflicts with previous cases
    case (default_speed)
    ^^^^^^^^^^^^^^^^^^^^
13_case_overlap.f90:11:3: Conflicting CASE (1_4)
    case (fast)
    ^^^^^^^^^^^

Aliases such as default_speed are legitimate in the enumeration but must be left out of the select case. Neither compiler checks that a select case over an enumeration handles every enumerator, and neither warns about a missing one: put a case default in every such construct.

The kind of the enumerators and -fshort-enums

The standard fixes only that the kind is interoperable with the corresponding C enum, so it is processor dependent. GNU Fortran uses a C-compatible kind that follows its companion C compiler: by default the size of a C int. The GNU Fortran option -fshort-enums mirrors the GCC option of the same name: it makes the compiler choose the smallest integer kind that holds all the values of a block, so that an enum matches C code compiled with -fshort-enums.

program enum_short
  implicit none
  enum, bind(c)
    enumerator :: a = 1, b = 2, c = 100
  end enum
  print '(a, 3(i0, 1x))', 'values: ', a, b, c
  print '(a, i0)', 'kind(a) = ', kind(a)
  print '(a, i0)', 'huge of that kind: ', huge(a)
end program enum_short

Built with gfortran -std=f2023 -fshort-enums (GNU Fortran 16.2):

values: 1 2 100
kind(a) = 1
huge of that kind: 127

Built without the option (GNU Fortran 16.2, and also LLVM Flang 23.1.4, which does not accept -fshort-enums):

values: 1 2 100
kind(a) = 4
huge of that kind: 2147483647

Because the option changes the kind of every enumerator in the program, it has to be applied to every Fortran file that shares enums with the C code, and never to only some of them. Do not use it unless the C side was built with it.

Calling C with an enumerated argument

The point of an interoperable enumeration is to share it with C. A C enum is passed as an int-compatible value, so the Fortran interface passes integer(c_int), value. The C side below is the library part of the heatflow project: it takes a boundary condition and returns the number of ghost cells the stencil needs.

#ifndef BOUNDARY_H
#define BOUNDARY_H

enum heatflow_bc { HEATFLOW_DIRICHLET = 1, HEATFLOW_NEUMANN = 2, HEATFLOW_PERIODIC = 3 };

/* Returns the number of ghost cells needed for the given boundary condition. */
int heatflow_ghost_cells(enum heatflow_bc bc);

#endif
#include "boundary.h"

int heatflow_ghost_cells(enum heatflow_bc bc)
{
    switch (bc) {
    case HEATFLOW_DIRICHLET: return 0;
    case HEATFLOW_NEUMANN:   return 1;
    case HEATFLOW_PERIODIC:  return 1;
    }
    return -1;
}
module heatflow_boundary_c
  use, intrinsic :: iso_c_binding, only: c_int
  implicit none
  private
  public :: dirichlet, neumann, periodic, bc_kind, ghost_cells

  enum, bind(c)
    enumerator :: dirichlet = 1, neumann = 2, periodic = 3
  end enum
  integer, parameter :: bc_kind = kind(dirichlet)

  interface
    function ghost_cells(bc) bind(c, name='heatflow_ghost_cells') result(n)
      import :: c_int
      integer(c_int), value :: bc
      integer(c_int) :: n
    end function ghost_cells
  end interface
end module heatflow_boundary_c

program enum_c
  use heatflow_boundary_c
  implicit none
  print '(a, i0)', 'dirichlet: ', ghost_cells(dirichlet)
  print '(a, i0)', 'neumann:   ', ghost_cells(neumann)
  print '(a, i0)', 'periodic:  ', ghost_cells(periodic)
  print '(a, i0)', 'bogus 99:  ', ghost_cells(99)
end program enum_c

Built with gcc -std=c17 -Wall -Wextra -pedantic -c boundary.c and, for Fortran, the strict gfortran command line above plus boundary.o; also built and run with flang-23 and the same object file. Output (identical with both compilers):

dirichlet: 0
neumann:   1
periodic:  1
bogus 99:  -1

The interface uses integer(c_int) rather than integer(kind(dirichlet)) on purpose: GNU Fortran 16.2 accepts the latter but warns that the dummy argument "may not be C interoperable" (-Wc-binding-type), because it does not recognise the kind of an enumerator as a bind(c) kind. On the usual platforms the two are the same kind, and bc_kind remains available for ordinary Fortran declarations. The last call shows what the interface does not give you: nothing stops 99, which is not an enumerator, from reaching C, and the C function has to handle it. See Interoperability with C for the rest of the bind(c) rules, including how to pass the same enum through a c_ptr array or a derived type.

The heatflow boundary conditions

The heatflow project describes its three boundary conditions with an interoperable enumeration, because Fortran 2023’s enumeration type is not accepted by the compilers it targets (see below). The module also shows the parts that a plain enum does not give you: a name lookup, a range check for values that come from outside, and a one-line conversion from an untrusted integer.

module heatflow_boundary
  implicit none
  private
  public :: bc_kind, dirichlet, neumann, periodic
  public :: boundary_name, is_valid_bc, bc_from_integer

  enum, bind(c)
    enumerator :: dirichlet = 1
    enumerator :: neumann = 2
    enumerator :: periodic = 3
  end enum
  integer, parameter :: bc_kind = kind(dirichlet)

contains

  pure function boundary_name(bc) result(name)
    integer(bc_kind), intent(in) :: bc
    character(len=:), allocatable :: name
    select case (bc)
    case (dirichlet)
      name = 'dirichlet'
    case (neumann)
      name = 'neumann'
    case (periodic)
      name = 'periodic'
    case default
      name = 'unknown'
    end select
  end function boundary_name

  ! The enumerators are contiguous, so a range test is enough.
  pure logical function is_valid_bc(bc)
    integer(bc_kind), intent(in) :: bc
    is_valid_bc = bc >= dirichlet .and. bc <= periodic
  end function is_valid_bc

  ! Convert an untrusted integer (for example from a namelist) and report failure.
  pure subroutine bc_from_integer(raw, bc, ok)
    integer, intent(in) :: raw
    integer(bc_kind), intent(out) :: bc
    logical, intent(out) :: ok
    ok = raw >= dirichlet .and. raw <= periodic
    bc = dirichlet
    if (ok) bc = int(raw, bc_kind)
  end subroutine bc_from_integer
end module heatflow_boundary

program boundary_demo
  use heatflow_boundary
  implicit none
  integer(bc_kind) :: bc
  integer :: bc_raw, unit, stat, i
  logical :: ok
  character(len=80) :: msg
  namelist /heatflow/ bc_raw

  ! Write and read back a one-variable namelist: an enumerator is just an integer.
  bc_raw = periodic
  open (newunit=unit, status='scratch', action='readwrite')
  write (unit, nml=heatflow)
  rewind (unit)
  bc_raw = -1
  read (unit, nml=heatflow, iostat=stat, iomsg=msg)
  close (unit)
  print '(a, i0, a, i0)', 'read back bc = ', bc_raw, ', iostat = ', stat

  call bc_from_integer(bc_raw, bc, ok)
  print '(3a, l1)', 'converted: ', boundary_name(bc), ', ok = ', ok

  do i = dirichlet, periodic
    print '(i0, 1x, a)', i, boundary_name(int(i, bc_kind))
  end do

  ! Nothing stops an out-of-range value: an enum variable is an integer.
  bc = 42_bc_kind
  print '(3a, l1)', 'forced 42: ', boundary_name(bc), ', valid = ', is_valid_bc(bc)
  call bc_from_integer(7, bc, ok)
  print '(3a, l1)', 'from 7: ', boundary_name(bc), ', ok = ', ok
end program boundary_demo

Output (identical with both compilers):

read back bc = 3, iostat = 0
converted: periodic, ok = T
1 dirichlet
2 neumann
3 periodic
forced 42: unknown, valid = F
from 7: dirichlet, ok = F

In the real heatflow code the run configuration reads the boundary condition from the namelist as a plain integer, the conversion routine is called once, and the grid code receives an integer(bc_kind) and a select case over the enumerators. The page on Internal Files, Namelist and Non-Advancing I/O shows the namelist in detail. The conversion matters because an interoperable enumeration offers no type safety at all: any integer of the right kind is a valid value, so bc = 42_bc_kind compiles, runs and is only caught by the case default.

Enum types: the typed interoperable enumeration

Fortran 2023 allows an interoperable enumeration to have a name: enum, bind(c) :: colour_t. The name defines an enum type (7.6.1), an interoperable nonintrinsic type that is not a derived type and has no type parameters. It still has the representation of the integer kind of its enumerators, so its values correspond one to one to the integers of that kind, and it interoperates with the matching C enum and with the C integer type of the enumerators (18.3.3). What the type adds is a declaration form, type(colour_t) :: x, and a set of rules that make the enumerators and the integers cooperate in a controlled way:

  • Declaration. type(colour_t) :: x = red; the type is in scope where its name is accessible.

  • Constructor. colour_t(expr) converts a scalar integer expression (or a BOZ constant) to the type (R765, C7113). The value must be representable in the integer kind, but it does not have to be one of the enumerators: colour_t(12345) is valid.

  • Assignment. The right-hand side may be of the same enum type, or an integer expression whose primary is an enumerator of that type; the integer is converted as if by the constructor (10.2.1.2, Table 10.8).

  • Comparison. The relational operators accept two operands of the enum type, or one operand of the type and an integer expression involving an enumerator of that type; they compare the integer values (10.1.5.5).

  • Type compatibility. An entity of the enum type is type compatible with an integer expression that has an enumerator of that type as a primary (7.3.3), which is what lets type(colour_t), value :: a accept red as an actual argument.

  • Intrinsics. int(x) returns the integer value of an enum-type x (16.9.110). Array constructors with a type specification accept the type as long as the elements are in type conformance with it (C7121).

  • I/O. Values are transferred with the I, B, O, Z and G edit descriptors as the corresponding integer (13.7.2.1 and 13.7.2.2).

  • Association. A nonpointer enum object may be storage-associated (in a common block) only with an object of the same type (8.10.2.4).

Not allowed: assigning a plain integer that is not an enumerator (type(myenum) :: z = 12345), passing a non-enumerator integer (call sub(999)) without the constructor, and using an enumerator of another enum type (call sub(f1) when f1 belongs to a different enum). These are the examples of the standard’s own note on the subject.

module enum_mod
  implicit none
  private
  public :: myenum, flags, one, two, three, f1, f2, f3, show

  enum, bind(c) :: myenum
    enumerator :: one = 1, two, three
  end enum

  enum, bind(c) :: flags
    enumerator :: f1 = 1, f2 = 2, f3 = 4
  end enum

contains

  subroutine show(a) bind(c)
    type(myenum), value :: a
    print '(a, i0)', 'value: ', int(a)
  end subroutine show
end module enum_mod

program enum_type_demo
  use enum_mod
  implicit none
  type(myenum) :: x = one
  type(myenum) :: y = myenum(12345)
  type(myenum) :: x2 = myenum(two)

  call show(x)
  call show(y)
  call show(x2)
  call show(three)
  call show(myenum(-huge(one)))
  print '(a, l1)', 'x == one: ', x == one
  print '(a, l1)', 'x < y:    ', x < y
end program enum_type_demo

This listing follows the rules of 7.6.1 and was not run: both compilers reject the typed enum. GNU Fortran 16.2 stops at the first statement (the first diagnostics are shown; the others are consequences of it):

02_enum_type.f90:6:3:

    6 |   enum, bind(c) :: myenum
      |   1
Error: Unclassifiable statement at (1)
02_enum_type.f90:7:19:

    7 |     enumerator :: one = 1, two, three
      |                   1
Error: ENUM definition statement expected before (1)

LLVM Flang 23.1.4 reports (first diagnostics only):

error: Could not parse 02_enum_type.f90
02_enum_type.f90:6:17: error: expected end of statement
    enum, bind(c) :: myenum
                  ^
02_enum_type.f90:6:3: in the context: enum definition
    enum, bind(c) :: myenum
    ^

The values the program would print, from the rules above, are 1, 12345, 2, 3 and the negated largest integer of the kind, in that order, followed by T and T: x is 1, y is 12345, and x < y compares the integers. This is a prediction from the standard, not a compiler result. Until a compiler you target accepts the typed form, keep enum, bind(c) unnamed and declare the variables with integer(kind(enumerator)) as above. Intel ifx and the other compilers listed on History, Standards and Compilers were not available for these checks; consult their release notes before relying on it.

The enumeration type

The enumeration type (7.6.2) is a type whose values are exactly a list of names, with no arithmetic and no accidental mixing with integers. It is defined in the specification part of a program unit, usually a module, with an enumeration type statement, enumerator statements and end enumeration type.

The rules of 7.6.2 in brief:

  • Syntax. enumeration type [[, access-spec] ::] name, then one or more enumerator [::] name-list statements, then end enumeration type [name] (R766 to R769). An access-spec may appear only in a module (C7114); it gives the accessibility of the type name and the default accessibility of its enumerators.

  • Enumerators. Each is a scalar named constant of the type. Their order defines their ordinal position, counting from 1. They have no integer value of their own.

  • Not a derived type. It has no type parameters, and WG5 N2212 describes it as behaving like a derived type with no components. It is not interoperable with C. Variables may be scalars, arrays, allocatable, pointers, components of derived types and coarrays; they may not be polymorphic (N2212).

  • Same type. Two entities have the same type if they are declared with reference to the same enumeration type definition.

  • Constructor. name(i) produces the enumerator whose ordinal position is i; i must be positive and not greater than the number of enumerators (R771).

  • Assignment. The right-hand side must be of the same enumeration type (Table 10.8). a = 1 and a = other_type_enumerator are errors.

  • Comparison. ==, /=, <, <=, > and >= between two values of the same type are available, and compare ordinal positions (10.1.5.5.1). No other intrinsic operation exists, and none can be applied to one enumeration-type operand and an integer.

  • select case. The case expression and the case values may be of an enumeration type; ranges such as case (w2:w4) follow the ordinal order (C1150, C1152). A constant array of the type may be written with an array constructor with a type specification, [bc_t :: dirichlet, neumann] (C7123).

  • Intrinsics. int(x) returns the ordinal position (16.9.110); huge(x) returns the last enumerator (16.9.96); the new elemental functions next(a [, stat]) and previous(a [, stat]) return the following or preceding enumerator, and stay on the last or first one respectively (16.9.151 and 16.9.164). When stat is present it receives zero, or a positive processor-dependent value at the end of the range; when it is absent, running off the end initiates error termination.

  • I/O. An enumeration-type item must not appear in a list-directed transfer (12.6.3). In a formatted transfer it corresponds to an I, B, O or Z edit descriptor, which write and read the ordinal position; an input value must be between 1 and the number of enumerators (13.7.2.2). It is not allowed in a namelist group (C8109, 8.9), in an equivalence (8.10.1) or in a common block (C8123, 8.10.2). Unformatted I/O is not restricted by the standard.

A boundary-condition type

The heatflow boundary condition kinds are the natural application. The module below is how the project would declare them with the enumeration type: the type name replaces bc_kind, the edge_t component has a default value, and the lookup function needs no integer conversion.

module heatflow_boundary
  implicit none
  private
  public :: bc_t, dirichlet, neumann, periodic, boundary_name, edge_t

  enumeration type :: bc_t
    enumerator :: dirichlet, neumann, periodic
  end enumeration type bc_t

  type :: edge_t
    type(bc_t) :: bc = periodic
    real :: value = 0.0
  end type edge_t

contains

  pure function boundary_name(bc) result(name)
    type(bc_t), intent(in) :: bc
    character(len=:), allocatable :: name
    select case (bc)
    case (dirichlet)
      name = 'dirichlet'
    case (neumann:periodic)
      name = 'neumann or periodic'
    case default
      name = 'unknown'
    end select
  end function boundary_name
end module heatflow_boundary

program enumeration_demo
  use heatflow_boundary
  implicit none
  type(bc_t) :: a, b
  type(bc_t), parameter :: all_bc(*) = [bc_t :: dirichlet, neumann, periodic]
  type(edge_t) :: edge
  integer :: i
  logical :: same, ordered

  a = bc_t(2)
  b = neumann
  same = a == b
  ordered = dirichlet < periodic
  do i = 1, size(all_bc)
    if (all_bc(i) == edge%bc) print '(2a)', 'default edge: ', boundary_name(all_bc(i))
  end do
  print '(i2)', a
  if (same .and. ordered) stop 0
end program enumeration_demo

Not run. GNU Fortran 16.2 rejects the enumeration type statement, with these first diagnostics:

04_enumeration_valid.f90:6:3:

    6 |   enumeration type :: bc_t
      |   1
Error: Unclassifiable statement at (1)
04_enumeration_valid.f90:7:19:

    7 |     enumerator :: dirichlet, neumann, periodic
      |                   1
Error: ENUM definition statement expected before (1)

LLVM Flang 23.1.4 rejects it by default with an explicit message (the further errors are consequences of it):

04_enumeration_valid.f90:6:3: error: F2023 ENUMERATION TYPEs are not yet implemented
    enumeration type :: bc_t
    ^^^^^^^^^^^^^^^^^^^^^^^^

With the experimental option -fenumeration-type, Flang accepts the program above in its semantic analysis. The check was run with flang-23 -fenumeration-type -fsyntax-only on the file shown, and Flang printed only its warning (which says that support is incomplete and should be enabled only for testing) and exit code 0:

04_enumeration_valid.f90:6:3: warning: ENUMERATION TYPE support is incomplete and should be enabled only for testing
    enumeration type :: bc_t
    ^^^^^^^^^^^^^^^^^^^^^^^^

Under the program, the standard semantics are: a is the second enumerator, so same is true; ordered is true; only the element periodic equals the default edge%bc, so the loop prints default edge: neumann or periodic; and print '(i2)', a writes 2, the ordinal position, with the I2 edit descriptor. These are the values the rules above give, not compiler output.

What the compilers do with an enumeration type

Compile-time support is much further along than run-time support, so the page records exactly what was observed, with LLVM Flang 23.1.4 and the option -fenumeration-type on an aarch64 Linux container.

Feature Result

enumeration type, enumerator, end enumeration type in a module

accepted by semantic analysis

Constructor bc_t(2), assignment, ==, <, default component initialisation

accepted by semantic analysis

Array constructor with type specification, select case with ranges

accepted by semantic analysis

int(a), huge(a), next(a), previous(a)

rejected, with the messages Actual argument for 'a=' has bad type, Actual argument for 'x=' has bad type and No explicit type declared for 'next' (likewise previous)

Generating code for a select case over the type

internal compiler error (a crash with a stack dump) in the program tested

Generating code for int, or for a comparison that involves the type

"not yet implemented: intrinsic: INT", then the compiler aborts

A program unit that uses a module defining the type, in a separate file

fatal internal error while reading the .mod file; a module and program in one file is accepted

List-directed print *, a of a value of the type

not diagnosed (the standard forbids it, 12.6.3)

In short, with Flang 23.1.4 you can use -fenumeration-type -fsyntax-only to check that your declarations and expressions are well formed, but you cannot produce an executable. Flang’s own documentation says the same: the per-feature table of Fortran 2023 on the Flang standards-support page lists "Enumerations" as not supported. GNU Fortran 16.2 has no support at all. For Intel ifx, the conformance table of release 2025.3 lists enumeration types among the unsupported Fortran 2023 features, and the compilers whose pages were read for History, Standards and Compilers do not state support. Re-check before relying on it: the situation changes with every release.

The invalid usages from the standard (7.6.2) were also checked with Flang’s semantic analysis, with a pair of enumeration types in one module:

module enumeration_mod
  implicit none
  enumeration type :: v_value
    enumerator :: v_one, v_two, v_three
  end enumeration type
  enumeration type :: w_value
    enumerator :: w1, w2, w3
  end enumeration type
end module enumeration_mod

program invalid
  use enumeration_mod
  implicit none
  type(v_value) :: a, b
  a = 1
  b = w1
  print *, a
end program invalid

LLVM Flang 23.1.4 with -fenumeration-type -fsyntax-only reports the two assignments (and, as listed above, not the list-directed print):

05_enumeration_invalid.f90:15:3: error: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(v_value) and INTEGER(4)
    a = 1
    ^^^^^
05_enumeration_invalid.f90:16:3: error: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(v_value) and TYPE(w_value)
    b = w1
    ^^^^^^

The last statement, print *, a, is invalid under 12.6.3 even though this Flang version does not report it. Write print '(i0)', a (which prints the ordinal position) instead.

The intrinsics are part of the standard but could not be compiled by anything available, so this fragment is checked against 16.9.96, 16.9.110, 16.9.151 and 16.9.164 only:

module bc_enum
  implicit none
  private
  public :: bc_t, dirichlet, neumann, periodic

  enumeration type :: bc_t
    enumerator :: dirichlet, neumann, periodic
  end enumeration type bc_t
end module bc_enum

program enumeration_intrinsics
  use bc_enum
  implicit none
  type(bc_t) :: a, c
  integer :: stat

  a = bc_t(2)
  print '(a, i0, a, i0)', 'ordinal of a: ', int(a), ', last: ', int(huge(a))
  c = next(periodic, stat)
  print '(a, i0, a, i0)', 'next(periodic) = ', int(c), ', stat = ', stat
  c = previous(a)
  print '(a, i0)', 'previous(a) = ', int(c)
  print '(a, i2)', 'formatted with I2: ', a
end program enumeration_intrinsics

Not run. Flang 23.1.4 with -fenumeration-type -fsyntax-only rejects the intrinsics, as listed in the table above:

11_enumeration_intrinsics.f90:18:49: error: Actual argument for 'a=' has bad type 'bc_t'
    print '(a, i0, a, i0)', 'ordinal of a: ', int(a), ', last: ', int(huge(a))
                                                  ^
11_enumeration_intrinsics.f90:18:74: error: Actual argument for 'x=' has bad type 'bc_t'
    print '(a, i0, a, i0)', 'ordinal of a: ', int(a), ', last: ', int(huge(a))
                                                                           ^
11_enumeration_intrinsics.f90:19:7: error: No explicit type declared for 'next'
    c = next(periodic, stat)
        ^^^^

By the standard the program prints ordinal of a: 2, last: 3; then next(periodic) = 3, stat = ` followed by a positive, processor-dependent number, because `periodic is the last enumerator and next returns its argument unchanged and sets stat to a positive value; previous(a) = 1; and 2 for the I2 edit. Without stat, the next call would end the program with error termination.

Fallbacks while compilers catch up

Three techniques cover the work of an enumeration type with what every compiler accepts; the table adds the real enumeration type for comparison. They differ in how much the compiler checks.

Technique What the compiler checks Cost Example in this page

Named integer constants (integer, parameter)

nothing: any integer is accepted

least code; no C interoperability guarantee

the arrays-of-names idiom above

enum, bind(c)

nothing: any integer of the kind is accepted; overlaps in select case

matches C enum; the heatflow choice

heatflow_boundary above

Derived type with a private integer component and defined == and /=

no mixing with integers, no arithmetic, no ordering unless you define it

more code, no select case on the type

next example

F2023 enumeration type

all of the above plus ordering and a closed set of values

needs a compiler that implements it

previous section

The third technique emulates the type safety of an enumeration type with a derived type whose component is private: code outside the module cannot construct a value other than the constants, cannot compare with an integer, and cannot assign an integer. The default value of the component, 0, is "unset".

module heatflow_boundary_safe
  implicit none
  private
  public :: bc_t, dirichlet, neumann, periodic
  public :: operator(==), operator(/=), bc_name, bc_from_name

  ! A derived type with a private component cannot be mixed up with plain
  ! integers or with another such type, which is the guarantee that the
  ! F2023 enumeration type provides natively.
  type :: bc_t
    private
    integer :: id = 0
  end type bc_t

  type(bc_t), parameter :: dirichlet = bc_t(1)
  type(bc_t), parameter :: neumann = bc_t(2)
  type(bc_t), parameter :: periodic = bc_t(3)

  character(len=9), parameter :: names(3) = [character(len=9) :: 'dirichlet', 'neumann', 'periodic']

  interface operator(==)
    module procedure bc_equal
  end interface
  interface operator(/=)
    module procedure bc_not_equal
  end interface

contains

  elemental logical function bc_equal(a, b)
    type(bc_t), intent(in) :: a, b
    bc_equal = a%id == b%id
  end function bc_equal

  elemental logical function bc_not_equal(a, b)
    type(bc_t), intent(in) :: a, b
    bc_not_equal = a%id /= b%id
  end function bc_not_equal

  function bc_name(bc) result(name)
    type(bc_t), intent(in) :: bc
    character(len=:), allocatable :: name
    if (bc%id >= 1 .and. bc%id <= size(names)) then
      name = trim(names(bc%id))
    else
      name = 'unset'
    end if
  end function bc_name

  function bc_from_name(name, found) result(bc)
    character(len=*), intent(in) :: name
    logical, intent(out) :: found
    type(bc_t) :: bc
    integer :: i
    found = .false.
    do i = 1, size(names)
      if (trim(names(i)) == name) then
        bc = bc_t(i)
        found = .true.
        return
      end if
    end do
  end function bc_from_name
end module heatflow_boundary_safe

program safe_enum
  use heatflow_boundary_safe
  implicit none
  type(bc_t) :: left, right
  logical :: ok

  print '(2a)', 'default value: ', bc_name(left)
  left = neumann
  right = bc_from_name('periodic', ok)
  print '(3a, l1)', 'right = ', bc_name(right), ', found = ', ok
  print '(a, l1)', 'left == right: ', left == right
  print '(a, l1)', 'left /= right: ', left /= right
  right = bc_from_name('robin', ok)
  print '(3a, l1)', 'right = ', bc_name(right), ', found = ', ok
end program safe_enum

Output (identical with GNU Fortran 16.2 and LLVM Flang 23.1.4):

default value: unset
right = periodic, found = T
left == right: F
left /= right: T
right = unset, found = F

The compiler enforces the wrapper. Each of the three statements below is rejected when the module above is compiled separately:

program safe_enum_errors
  use heatflow_boundary_safe
  implicit none
  type(bc_t) :: bc
  bc = 2
  if (bc == 2) print '(a)', 'never reached'
  if (dirichlet < periodic) print '(a)', 'no ordering either'
end program safe_enum_errors

GNU Fortran 16.2:

09_safe_enum_errors.f90:5:7:

    5 |   bc = 2
      |       1
Error: Cannot convert INTEGER(4) to TYPE(bc_t) at (1)
09_safe_enum_errors.f90:6:6:

    6 |   if (bc == 2) print '(a)', 'never reached'
      |      1
Error: Operands of comparison operator '==' at (1) are TYPE(bc_t)/INTEGER(4)
09_safe_enum_errors.f90:7:6:

    7 |   if (dirichlet < periodic) print '(a)', 'no ordering either'
      |      1
Error: Operands of comparison operator '<' at (1) are TYPE(bc_t)/TYPE(bc_t)

LLVM Flang 23.1.4:

error: Semantic errors in 09_safe_enum_errors.f90
09_safe_enum_errors.f90:5:3: error: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(bc_t) and INTEGER(4)
    bc = 2
    ^^^^^^
09_safe_enum_errors.f90:6:7: error: Operands of .EQ. must have comparable types; have TYPE(bc_t) and INTEGER(4)
    if (bc == 2) print '(a)', 'never reached'
        ^^^^^^^
09_safe_enum_errors.f90:7:7: error: Operands of .LT. must have comparable types; have TYPE(bc_t) and TYPE(bc_t)
    if (dirichlet < periodic) print '(a)', 'no ordering either'
        ^^^^^^^^^^^^^^^^^^^^

The limits of the emulation are the ones in the table: a derived type cannot be a case selector (the standard allows select case only on integer, character, logical, enum and enumeration types, C1150), so dispatch goes through a function such as bc_name, and, because the component is private, the structure constructor bc_t(1) can be written only inside the module that defines the type. When a compiler you use accepts the enumeration type, replacing the emulation by it removes bc_equal, bc_not_equal and the names table, and gives back select case.

The choice between the techniques is the following.

flowchart TD A["Need a named set of values"] --> B{"Shared with C or passed to a bind(c) procedure?"} B -- "yes" --> C["enum, bind(c) with enumerator; use integer(c_int) in the interface"] B -- "no" --> D{"Compiler accepts enumeration type?"} D -- "yes" --> E["enumeration type: closed set, ordering, no integer mixing"] D -- "no" --> F{"Is type safety worth the extra code?"} F -- "yes" --> G["derived type with a private component and defined == and /="] F -- "no" --> H["enum, bind(c) or integer parameters, plus a select case with case default"]

Upcoming in Fortran 2028

The latest public working draft of the next revision, J3/26-007r1 (3 March 2026), extends both kinds of enumeration. It is a draft; the text may change before the standard is published, and no compiler used here implements any of it, so the items below are described from the draft, not tested.

  • Scoped enumerator values (9.5). type-name%enumerator-name names an enumerator through its type, "as if it were a component", for enum types and enumeration types alike, even when the enumerator itself is not accessible in the scoping unit. With the draft’s own pattern, a module can keep its enumerators private and still let users write light = signal%amber, which stops enumerators from polluting the namespace of every program that uses the module.

  • Accessibility of enumerators. enumerator, private :: off, green sets the accessibility of single enumerators in an enum or enumeration type, and enum, bind(c), private :: name the accessibility of an enum type name (7.6.1, 7.6.2).

  • Kind of an interoperable enumeration. enum (kind=c_long), bind(c) :: name gives the enumerators an explicit kind, which matches the C23 syntax enum name : long { ... }; the draft says it follows ISO/IEC 9899:2024.

  • BOZ enumerator values. An enumerator may take a boz-literal-constant value, for flags written in binary or hexadecimal.

  • Enumeration types in I/O. Items of enumeration type may appear in list-directed and namelist data transfer and correspond to the G edit descriptor, which removes the restrictions that make them awkward to read and write today.

module traffic_signal_m
  implicit none
  enumeration type, public :: traffic_signal
    enumerator, private :: off, green, amber, red
  end enumeration type
end module traffic_signal_m

Fortran 2028 draft; accepted by no compiler; not run. Users of the module could write light = signal%amber with light of type traffic_signal (here renamed signal in a use statement), following 9.5 of the draft. See What’s New and Fortran 2028 Preview for the other work items.

Pitfalls

  • An interoperable enumeration is not a type. Any integer of the right kind is a valid "value"; the compiler warns about nothing. Validate values that come from outside (files, namelists, C), as bc_from_integer does.

  • select case does not check completeness. Neither compiler warns about a missing enumerator; add case default, and make it fail loudly during development.

  • Aliased values in one enum (two names with one value) are legal but cannot both appear in one select case: C1154 and the diagnostics above.

  • Using plain integer for the variable. It works while the enumerator kind equals the default integer kind, then fails silently on a processor or with -fshort-enums. Declare integer(kind(some_enumerator)).

  • Mixing the C enum size with Fortran. C23 allows enums wider than int; the Fortran kind is the one that is interoperable with the matching C type, so a bind(c) interface must be checked against the C compiler’s choice. Use integer(c_int) in the interface for plain C enum types, and -fshort-enums consistently.

  • Name clashes. Enumerator names are ordinary names of the scoping unit: use heatflow_boundary without only brings dirichlet, neumann and periodic into the user’s scope, where a local variable called periodic is an error. Use only or a prefix such as bc_; the scoped type%enumerator of Fortran 2028 will relieve this for enumeration types.

  • Integer arithmetic on enumerators. colour + 1 is legal for an interoperable enumeration (it is an integer) and illegal for an enumeration type; code that depends on it must be rewritten with next and previous or with ordinal positions.

  • Ordinal vs value. For an enum type or an interoperable enumeration int(a) is the stored integer; for an enumeration type it is the ordinal position, counted from 1, so the first enumerator is 1, not 0.

  • Namelist, common and equivalence. The enumeration type cannot be used in them (constraints C8109, C8110 and C8123); keep such variables outside, or use an integer or a character name in the file and convert.

  • Do not rely on compiler extensions. Flang’s -fenumeration-type is explicitly experimental, and the default flag set rejects the statement; a GNU Fortran build that does not support it fails with "Unclassifiable statement", which is a hint that the compiler predates the feature, not that your code is wrong.

Where the books are outdated

The two Fortran books consulted for this section present the interoperable enumeration in their C-interoperability chapter and the enumeration type in the chapter on the new features of Fortran 2023. Where they differ from current practice:

Area Outdated or incorrect in the books, current approach

Terminology

the "BIND© ENUM" of older text is called an interoperable enumeration in Fortran 2023, and it can now have a type name (an enum type); material written for Fortran 2003 to 2018 shows neither the typed form nor the new word

Compiler support

the enumeration type is presented as a language feature that can be used; in 2026 neither GNU Fortran 16.2 nor LLVM Flang 23.1.4 can run a program that uses it (Flang can only check it, with an experimental option), so the portable code in this guide uses enum, bind(c) or the derived-type emulation

Syntax summary

the general form of the enumeration type statement in the book’s feature chapter has unbalanced brackets and leaves type out of the end statement (an erratum on the page that also contains a correct example); the correct closing statement is end enumeration type [name] (R769)

Kind of enumerators

older text says an enum is "just an int`"; the standard says the kind is the one interoperable with the matching C `enum, which may be chosen smaller (-fshort-enums) or, with C23, larger than int

Standard version provenance and compiler support

Feature Introduced Compiler support checked on 2026-10-19

enum, bind(c), enumerator, end enum

Fortran 2003 (C interoperability; the GNU Fortran wiki lists enumeration support under its Fortran 2003 features)

GNU Fortran 16.2 and LLVM Flang 23.1.4 build and run the examples

Interoperable enumeration (new name), enum type name enum, bind(c) :: name, enum constructor

Fortran 2023 (7.6.1, Introduction)

rejected by both compilers

enumeration type (7.6.2), next, previous, huge/int on the type

Fortran 2023

rejected by GNU Fortran 16.2; Flang 23.1.4 checks part of it with -fenumeration-type and cannot compile it

-fshort-enums

GNU Fortran extension that follows GCC

GNU Fortran 16.2 only

Scoped enumerators type%enumerator, per-enumerator accessibility, enum (kind=...), BOZ enumerators, enumeration I/O

Fortran 2028 draft J3/26-007r1

no compiler

Not checked here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers. See History, Standards and Compilers for the per-compiler support table.