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 |
|
|
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 |
no |
Type name |
optional ( |
required |
Mixing with integers |
free (an enumerator is an integer) |
not allowed, except through the constructor and |
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 |
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 :: aacceptredas an actual argument. -
Intrinsics.
int(x)returns the integer value of an enum-typex(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,ZandGedit 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 moreenumerator [::] name-liststatements, thenend enumeration type [name](R766 to R769). Anaccess-specmay 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 isi;imust 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 = 1anda = other_type_enumeratorare 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 functionsnext(a [, stat])andprevious(a [, stat])return the following or preceding enumerator, and stay on the last or first one respectively (16.9.151 and 16.9.164). Whenstatis 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,OorZedit 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 anequivalence(8.10.1) or in acommonblock (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 |
|---|---|
|
accepted by semantic analysis |
Constructor |
accepted by semantic analysis |
Array constructor with type specification, |
accepted by semantic analysis |
|
rejected, with the messages |
Generating code for a |
internal compiler error (a crash with a stack dump) in the program tested |
Generating code for |
"not yet implemented: intrinsic: INT", then the compiler aborts |
A program unit that |
fatal internal error while reading the |
List-directed |
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 ( |
nothing: any integer is accepted |
least code; no C interoperability guarantee |
the arrays-of-names idiom above |
|
nothing: any integer of the kind is accepted; overlaps in |
matches C |
|
Derived type with a private integer component and defined |
no mixing with integers, no arithmetic, no ordering unless you define it |
more code, no |
next example |
F2023 |
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.
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-namenames 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 enumeratorsprivateand still let users writelight = signal%amber, which stops enumerators from polluting the namespace of every program that uses the module. -
Accessibility of enumerators.
enumerator, private :: off, greensets the accessibility of single enumerators in anenumorenumeration type, andenum, bind(c), private :: namethe accessibility of an enum type name (7.6.1, 7.6.2). -
Kind of an interoperable enumeration.
enum (kind=c_long), bind(c) :: namegives the enumerators an explicit kind, which matches the C23 syntaxenum name : long { ... }; the draft says it follows ISO/IEC 9899:2024. -
BOZ enumerator values. An
enumeratormay take aboz-literal-constantvalue, 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
Gedit 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_integerdoes. -
select casedoes not check completeness. Neither compiler warns about a missing enumerator; addcase 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 oneselect case: C1154 and the diagnostics above. -
Using plain
integerfor the variable. It works while the enumerator kind equals the default integer kind, then fails silently on a processor or with-fshort-enums. Declareinteger(kind(some_enumerator)). -
Mixing the C
enumsize with Fortran. C23 allows enums wider thanint; the Fortran kind is the one that is interoperable with the matching C type, so abind(c)interface must be checked against the C compiler’s choice. Useinteger(c_int)in the interface for plain Cenumtypes, and-fshort-enumsconsistently. -
Name clashes. Enumerator names are ordinary names of the scoping unit:
use heatflow_boundarywithoutonlybringsdirichlet,neumannandperiodicinto the user’s scope, where a local variable calledperiodicis an error. Useonlyor a prefix such asbc_; the scopedtype%enumeratorof Fortran 2028 will relieve this for enumeration types. -
Integer arithmetic on enumerators.
colour + 1is legal for an interoperable enumeration (it is an integer) and illegal for an enumeration type; code that depends on it must be rewritten withnextandpreviousor 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,
commonandequivalence. 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-typeis 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.
See also Common Gotchas and Pitfalls.
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 |
Syntax summary |
the general form of the |
Kind of enumerators |
older text says an enum is "just an |
Standard version provenance and compiler support
| Feature | Introduced | Compiler support checked on 2026-10-19 |
|---|---|---|
|
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 |
Fortran 2023 (7.6.1, Introduction) |
rejected by both compilers |
|
Fortran 2023 |
rejected by GNU Fortran 16.2; Flang 23.1.4 checks part of it with |
|
GNU Fortran extension that follows GCC |
GNU Fortran 16.2 only |
Scoped enumerators |
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.