Derived Types
|
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 was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all
(GNU Fortran 16.2) and run, and every example was also built and run with LLVM Flang 23; the output shown is the real
output of GNU Fortran unless it says otherwise. Examples that are deliberately wrong, or that expose a difference
between the compilers, are labelled as such and show the real diagnostics. The rules are in clause 7 of the standard
("Types"): 7.5.1 gives the concepts, 7.5.2 the type definition, 7.5.4 the components, 7.5.9 the derived-type specifier,
7.5.10 the construction of values and 7.5.11 operations and assignment. Structure components (the percent selector) are
in 9.4.2, intrinsic assignment of structures is in 10.2.1.3, and the interoperable form is in 18.3.4.
A derived type is how a Fortran program names a record: a collection of components, each with its own type, kind and
attributes, that is treated as one value when it is assigned, passed to a procedure or returned from a function.
Everything the running example, heatflow, stores about the plate is a derived type: grid_t holds the extents, the
spacings and the allocatable array of temperatures, config_t holds the simulation parameters with defaults, and the
solver hierarchy that comes later is built by extending derived types. This page covers the type itself and the values
and objects made from it. The related features have their own pages: type parameters in
Parameterized Derived Types, type extension and
class in Object Oriented Programming, procedures
bound to a type, abstract types and finalization in
Type-Bound Procedures, Abstract Types and Finalization,
and defined input and output in
Derived-Type and Asynchronous I/O.
Defining a derived type
A type definition (R726) is a scoping construct made of a type statement, an optional parameter and private or
sequence part, the components, an optional contains part for type-bound procedures, and end type:
type [[, attribute-list] ::] name [(type-parameter-names)]
[private | sequence]
component definitions
[contains
type-bound procedure part]
end type [name]
The attributes allowed on the type statement (R728) are abstract, bind(c), extends(parent), and the accessibility
attributes public and private. The double colon is optional only when there is no attribute list. If end type repeats
the name, it must be the same name (C744), and the name must not be the name of an intrinsic type (C734), so
the standard does not allow type :: integer or type :: real.
Components are declared much like variables (R737): a type specification, an optional attribute list and a list of names
with optional array bounds and initialization. The next example defines the three kinds of structure most programs
start with. point_t has two scalar components, probe_t has a character component and a derived-type component of a
previously defined type, and plate_t has an explicit-shape array component and an allocatable array component. The main
program then builds, reads and modifies values.
module heatflow_geometry
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, point_t, probe_t, plate_t
integer, parameter :: dp = real64
type :: point_t ! two scalar components
real(dp) :: x
real(dp) :: y
end type point_t
type :: probe_t ! a derived-type component, a character component
character(len=8) :: label
type(point_t) :: position ! nested structure
integer(int32) :: samples
end type probe_t
type :: plate_t ! array and allocatable components
real(dp) :: size(2) ! explicit-shape array component: length and height
integer(int32) :: cells(2)
real(dp), allocatable :: temperature(:, :)
end type plate_t
end module heatflow_geometry
program derived_type_basics
use heatflow_geometry
implicit none
type(point_t) :: corner, centre
type(probe_t) :: probe
type(plate_t) :: plate
type(point_t) :: points(3)
corner = point_t(0.0_dp, 0.0_dp) ! positional constructor
centre = point_t(y=0.5_dp, x=1.0_dp) ! keyword constructor, any order
probe = probe_t('centre', centre, 0) ! a nested structure value
probe%samples = probe%samples + 10 ! component access with %
probe%position%x = 2.0_dp ! component of a component
plate = plate_t(size=[2.0_dp, 1.0_dp], cells=[5, 3], temperature=null())
print '(a, l1)', 'temperature allocated before: ', allocated(plate%temperature)
allocate (plate%temperature(plate%cells(1), plate%cells(2)), source=20.0_dp)
plate%temperature(3, 2) = 100.0_dp
print '(a, l1, a, f6.1)', 'allocated now: ', allocated(plate%temperature), ' max=', maxval(plate%temperature)
points = [point_t(0.0_dp, 0.0_dp), point_t(1.0_dp, 0.0_dp), point_t(1.0_dp, 1.0_dp)]
print '(a, 3f5.1)', 'x of every point: ', points%x ! component of an array of structures is an array
print '(a, 2f5.1)', 'y of points 2:3 : ', points(2:3)%y
print *, 'probe label=', probe%label, ' x=', probe%position%x, ' samples=', probe%samples
print *, 'corner as a list-directed item: ', corner
print *, 'centre: ', centre
end program derived_type_basics
Output:
temperature allocated before: F
allocated now: T max= 100.0
x of every point: 0.0 1.0 1.0
y of points 2:3 : 0.0 1.0
probe label=centre x= 2.0000000000000000 samples= 10
corner as a list-directed item: 0.0000000000000000 0.0000000000000000
centre: 1.0000000000000000 0.50000000000000000
Source: J3/23-007r1: 7.5.2 Derived-type definition, 7.5.4 Components, 9.4.2 Structure components and 7.5.10 Construction of derived-type values, Fortran Programming Language: Quickstart, Derived types
A few points in that program are worth stating precisely.
-
The percent sign selects a component (R911).
probe%position%xis a component of a component; a selector may be applied to an element of an array of structures (points(2)%y) and to a whole array of structures, in which case the result is an array (points%x). Only one part of a designator may have a nonzero rank (C919), soa(1:3)%b(1:2)is invalid, and a part to the right of a part with nonzero rank cannot be allocatable or a pointer. -
A component can be anything a variable can be, with some restrictions (C749 to C759): a derived-type component that is neither a pointer nor allocatable must have a type that is already defined (see below); an array component with neither attribute must have explicit bounds that are specification expressions of the type’s parameters or constants, and with the
allocatableorpointerattribute it must have deferred shape ((:),(:, :)); a component cannot be both allocatable and a pointer;contiguousapplies only to pointer array components; and the*lenform is allowed only for character components. -
The values of a type are the sequences of values of its components (7.5.8). The value of a pointer component is its association, and the value of an allocatable component is its allocation status together with its dynamic type, bounds and value if allocated.
-
List-directed output of a structure lists its components in component order (12.6.3), which is the order of declaration (7.5.4.7). The spacing and the number of digits are compiler choices, which is why the two compilers print
x= 2.0000000000000000andx= 2.for the same value. -
The definition must come before it is used in a data component. The next listing shows what the compilers say about a
type(b_t)component of a type defined later. A pointer or allocatable component may name the type being defined or one defined later, which is what makes linked structures possible (see Recursive types). A type with no components at all is also allowed (the storage size of such a type in GNU Fortran and Flang is zero bits).
This is deliberately non-conforming and shows C749:
! INTENTIONALLY NON-CONFORMING: a derived-type component of a type that is defined later.
program order_and_empty
implicit none
type :: a_t
type(b_t) :: inner ! error: b_t is not defined yet and the component is neither pointer nor allocatable
end type a_t
type :: b_t
integer :: n = 0
end type b_t
end program order_and_empty
GNU Fortran 16.2:
18_order_and_empty.f90:6:17:
6 | type(b_t) :: inner ! error: b_t is not defined yet and the component is neither pointer nor allocatable
| 1
Error: Derived type at (1) has not been previously defined and so cannot appear in a derived type definition
LLVM Flang 23:
error: Semantic errors in 18_order_and_empty.f90
18_order_and_empty.f90:6:10: error: Derived type 'b_t' not found
type(b_t) :: inner ! error: b_t is not defined yet and the component is neither pointer nor allocatable
^^^
Kinds of components
| Component | Declaration | Remarks |
|---|---|---|
Scalar of intrinsic type |
|
the kind and length are constant expressions or type parameters |
Explicit-shape array |
|
bounds are constant or specification expressions; the shape is part of the type |
Allocatable scalar or array |
|
starts unallocated; owned by the object; copied by assignment |
Pointer scalar or array |
|
only the association is part of the value; see Pointers and Targets |
Derived type |
|
the type must be defined first |
Recursive derived type |
|
the only way to name the type being defined; see Recursive types |
Procedure pointer |
|
|
Coarray |
|
allocatable only; see Coarrays, Images and Synchronization |
Character with length |
|
deferred-length allocatable characters are allowed as components, see Characters and Strings |
Type parameters (kind and len) are not components, are effectively public (7.5.4.8, Note 1) and are covered in
Parameterized Derived Types. Constants, enumerators
and parameter attributes are not allowed on components: a component is always a variable, and a constant belongs in the
enclosing module.
Structure constructors
A derived-type definition implicitly defines a structure constructor with the same name as the type (R756). It builds a scalar value from component values:
type-name ( [component-value-list] )
Values may be given in component order (positional) or with the component name as a keyword, and the two may be mixed as long as the keyword form is last: a positional value cannot follow a keyword value (C7105). The rules, all checked by the compiler, are:
-
a value must be provided for every nonallocatable component that has no default initialization (C7104);
-
a component can be given at most once (C7102), and every keyword must be a component name (C7106);
-
the type name and every component for which a value is provided must be accessible where the constructor is written (C7107), which is what makes a type with private components impossible to construct outside its module unless the module offers its own constructor, see Custom constructors;
-
the type must not be abstract (C7101);
-
the value is converted to the component with the rules of intrinsic assignment: an
integervalue for arealcomponent is converted, a character value is padded or truncated to the component length (GNU Fortran warns about the truncation with-Wcharacter-truncation), a scalar is broadcast to every element of an array component, and the shape of an array value must conform; -
a constructor cannot appear before the type is defined.
The next program is deliberately non-conforming and breaks each rule in turn; the line after each statement says which.
! INTENTIONALLY NON-CONFORMING: invalid structure constructors.
program constructor_errors
implicit none
type :: window_t
integer :: nx ! no default
integer :: ny ! no default
real :: scale = 1.0
end type window_t
type(window_t) :: w
w = window_t(nx=10) ! error: ny has no default value
w = window_t(10, 20, 2.0, 99) ! error: too many component values
w = window_t(10, 20, depth=3.0) ! error: depth is not a component
w = window_t(nx=10, 20, 2.0) ! error: positional value after a keyword value
w = window_t(10, 'twenty', 2.0) ! error: a character value for an integer component
w = window_t(10, 20, nx=1) ! error: nx given twice
end program constructor_errors
GNU Fortran 16.2 reports the statements in this order, not in source order:
11_constructor_errors.f90:14:22:
14 | w = window_t(nx=10, 20, 2.0) ! error: positional value after a keyword value
| 1
Error: Missing keyword name in actual argument list at (1)
11_constructor_errors.f90:11:7:
11 | w = window_t(nx=10) ! error: ny has no default value
| 1
Error: No initializer for component 'ny' given in the structure constructor at (1)
11_constructor_errors.f90:12:28:
12 | w = window_t(10, 20, 2.0, 99) ! error: too many component values
| 1
Error: Too many components in structure constructor at (1)
11_constructor_errors.f90:13:7:
13 | w = window_t(10, 20, depth=3.0) ! error: depth is not a component
| 1
Error: 'depth' at (1) is not a member of the 'window_t' structure
11_constructor_errors.f90:15:19:
15 | w = window_t(10, 'twenty', 2.0) ! error: a character value for an integer component
| 1
Error: Cannot convert CHARACTER(6) to INTEGER(4) at (1)
11_constructor_errors.f90:16:26:
16 | w = window_t(10, 20, nx=1) ! error: nx given twice
| 1
Error: Component 'nx' is initialized twice in the structure constructor at (1)
LLVM Flang 23:
error: Semantic errors in 11_constructor_errors.f90
11_constructor_errors.f90:11:7: error: Structure constructor lacks a value for component 'ny'
w = window_t(nx=10) ! error: ny has no default value
^^^^^^^^
11_constructor_errors.f90:6:16: Declaration of 'ny'
integer :: ny ! no default
^^
11_constructor_errors.f90:12:29: error: Unexpected value in structure constructor
w = window_t(10, 20, 2.0, 99) ! error: too many component values
^^
11_constructor_errors.f90:13:24: error: Keyword 'depth=' does not name a component of derived type 'window_t'
w = window_t(10, 20, depth=3.0) ! error: depth is not a component
^^^^^
11_constructor_errors.f90:14:23: error: Value in structure constructor lacks a component name
w = window_t(nx=10, 20, 2.0) ! error: positional value after a keyword value
^^
11_constructor_errors.f90:14:27: error: Value in structure constructor lacks a component name
w = window_t(nx=10, 20, 2.0) ! error: positional value after a keyword value
^^^
11_constructor_errors.f90:15:20: error: Value in structure constructor of type 'CHARACTER(KIND=1,LEN=6_8)' is incompatible with component 'ny' of type 'INTEGER(4)'
w = window_t(10, 'twenty', 2.0) ! error: a character value for an integer component
^^^^^^^^
11_constructor_errors.f90:6:16: Declaration of 'ny'
integer :: ny ! no default
^^
11_constructor_errors.f90:16:24: error: Component 'nx' conflicts with another component earlier in this structure constructor
w = window_t(10, 20, nx=1) ! error: nx given twice
^^
The constructor treats the three kinds of data component differently, as the standard describes in 7.5.10:
-
A nonpointer, nonallocatable component receives the converted value. Its shape must conform.
-
A pointer component is given a
data-targetorproc-targetand becomes pointer-associated with it (or takes over the association of a pointer value, or becomes disassociated fornull()). A constructor that gives a variable to a pointer component therefore leaves a pointer to that variable in the structure, and the variable needs thetargetattribute. -
An allocatable component may be given an expression, which is allocated with the shape and value of the expression; an allocatable entity, whose allocation status and value are copied; or the function
null()with no arguments, which leaves the component unallocated. Since Fortran 2008 the component may be left out altogether, and it is then unallocated. Both compilers accept the omission, as the following program shows.
program omit_allocatable
implicit none
type :: field_t
integer :: n
real, allocatable :: u(:)
end type field_t
type(field_t) :: f
f = field_t(n=4) ! the allocatable component is omitted
print '(a, i0, a, l1)', 'n=', f%n, ' u allocated: ', allocated(f%u)
f = field_t(3, [1.0, 2.0, 3.0]) ! a non-allocatable value gives an allocated component
print '(a, i0, a, l1, a, i0)', 'n=', f%n, ' u allocated: ', allocated(f%u), ' size=', size(f%u)
end program omit_allocatable
Output:
n=4 u allocated: F
n=3 u allocated: T size=3
A constructor with constant expressions as values is itself a constant expression, so it can initialize a named constant or a variable, or give a default to a component of another type:
type(run_config_t), parameter :: reference = run_config_t(steps=0)
type(point_t), save :: origin = point_t(0.0_dp, 0.0_dp)
A constructor can also appear inside an array constructor, to build an array of structures: [point_t(0.0_dp, 0.0_dp),
point_t(1.0_dp, 0.0_dp)]. All the elements must have the same type, and, for a parameterized type, the same type
parameters.
Default initialization
A component can be given a default initialization (R743): = constant-expr for a nonpointer, nonallocatable component,
=> null() or => a target for a pointer component. Every object of the type, whether a variable, a function result,
a dummy argument with intent(out) or an allocated object, starts with those values. A component without an initializer
is undefined, except that allocatable components are always unallocated at the start (7.5.4.6). A double colon is required
in the component declaration when an initializer is present (C766).
module heatflow_defaults
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, run_config_t, node_t, defaults_demo
integer, parameter :: dp = real64
type :: run_config_t
integer(int32) :: nx = 41_int32 ! default initialization
integer(int32) :: ny = 41_int32
real(dp) :: alpha = 1.0e-2_dp
real(dp) :: lengths(2) = [1.0_dp, 1.0_dp] ! array component, array constant
character(len=16) :: solver = 'jacobi'
integer(int32) :: steps ! deliberately not initialized
real(dp), allocatable :: history(:) ! always starts unallocated
end type run_config_t
type :: node_t
integer(int32) :: value = 0_int32
type(node_t), pointer :: next => null() ! pointer components start disassociated
end type node_t
contains
subroutine defaults_demo(cfg)
type(run_config_t), intent(out) :: cfg ! intent(out) re-applies default initialization
print '(a, i0, a, a)', 'inside: nx=', cfg%nx, ' solver=', trim(cfg%solver)
end subroutine defaults_demo
end module heatflow_defaults
program default_initialization
use heatflow_defaults
implicit none
type(run_config_t) :: a ! defaults applied at declaration
type(run_config_t) :: b = run_config_t(nx=81, ny=81, alpha=2.0e-2_dp, lengths=[2.0_dp, 1.0_dp], &
solver='ftcs', steps=100, history=null())
type(run_config_t), parameter :: reference = run_config_t(steps=0) ! named constant: defaults fill the rest
type(node_t) :: head
type(run_config_t) :: c
print '(a, 2i4, es9.2, 2f5.1, 1x, a)', 'a (defaults): ', a%nx, a%ny, a%alpha, a%lengths, trim(a%solver)
print '(a, 2i4, es9.2, 2f5.1, 1x, a)', 'b (explicit): ', b%nx, b%ny, b%alpha, b%lengths, trim(b%solver)
print '(a, 2i4, es9.2, 2f5.1, 1x, a, i3)', 'reference: ', reference%nx, reference%ny, reference%alpha, &
reference%lengths, trim(reference%solver), reference%steps
print '(a, l1, a, l1)', 'a%history allocated: ', allocated(a%history), ' head%next associated: ', associated(head%next)
c%nx = 7 ! change a variable ...
call defaults_demo(c) ! ... intent(out) restores the defaults on entry
print '(a, i0)', 'after the call c%nx = ', c%nx
end program default_initialization
Output:
a (defaults): 41 41 1.00E-02 1.0 1.0 jacobi
b (explicit): 81 81 2.00E-02 2.0 1.0 ftcs
reference: 41 41 1.00E-02 1.0 1.0 jacobi 0
a%history allocated: F head%next associated: F
inside: nx=41 solver=jacobi
after the call c%nx = 41
Source: J3/23-007r1: 7.5.4.6 Default initialization for components and 8.5.10 INTENT attribute, GNU Fortran manual: Options for code generation conventions
The rules interact in ways that are easy to misjudge:
-
Explicit initialization overrides default initialization.
bin the example is declared with an initializer, and that structure constructor gives a value for every component, so none of the defaults applies. The named constantreferenceis built from a constructor with onlysteps, and the defaults fill the rest. -
A default initialization does not give the variable the
saveattribute. An initialization in a declaration does, and a default initialization in the type does not (7.5.4.6), so a local variable of a procedure is initialized again on every call. This is one of the practical differences between the two ways of writing an initial value. -
intent(out)re-applies the defaults. The standard says that anintent(out)dummy argument becomes undefined on entry, "except for any subcomponents that are default-initialized" (8.5.10). In the example the caller setc%nx = 7, the subroutine sawnx=41, and the caller got 41 back. The practical consequence is that a procedure with anintent(out)argument of a type with defaults can use that type as a clean slate, and also that it cannot be used to preserve the old value of any defaulted component. -
A partial initialization is allowed.
stepshas no default, soa%stepsis undefined: reading it is an error that the compiler is not required to detect. Initialize the components that make sense, give the rest values in the constructor, and consider giving every component a default. -
An initialization of a derived-type component can override the default of the component’s own type (7.5.4.6, Note 2).
-
A pointer component can be initialized to disassociated or to an initial target.
=> null()is by far the most common. A target must be a nonallocatable, noncoindexed variable with thetargetandsaveattributes (C770).
GNU Fortran has the option -finit-derived, which together with -finit-integer, -finit-real and its relatives fills
the uninitialized components of derived-type variables with a pattern. It is a debugging aid, for example to make an
uninitialized real component a signalling NaN with -finit-real=snan, and not a replacement for the defaults in the
type definition. The manual says that these options do not initialize pointers, allocatable arrays or variables in an
equivalence statement. LLVM Flang has -finit-local-zero for local variables without an explicit or default
initialization; it also documents, as an extension, that a saved entity of a type with a default initializer has its
components without defaults set to zero.
Custom constructors
The built-in constructor has three limits: it cannot enforce that components are consistent with each other, it needs every component to be accessible, and it cannot choose between representations. A module can add its own constructor by declaring a generic interface with the same name as the type (7.5.10, C7108). The type’s structure constructor and the specific procedures of the generic then share the name, and each reference is resolved to one of them.
module heatflow_material
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, material_t
integer, parameter :: dp = real64
type :: material_t
character(len=12) :: name = 'unnamed'
real(dp) :: conductivity = 0.0_dp ! W/(m K)
real(dp) :: density = 0.0_dp ! kg/m^3
real(dp) :: specific_heat = 0.0_dp ! J/(kg K)
real(dp) :: diffusivity = 0.0_dp ! derived: k/(rho c)
end type material_t
interface material_t ! a generic with the same name as the type
module procedure new_material
end interface material_t
contains
function new_material(name, conductivity, density, specific_heat) result(m)
character(len=*), intent(in) :: name
real(dp), intent(in) :: conductivity, density, specific_heat
type(material_t) :: m
if (density <= 0.0_dp .or. specific_heat <= 0.0_dp) error stop 'material_t: density and specific heat must be positive'
m%name = name
m%conductivity = conductivity
m%density = density
m%specific_heat = specific_heat
m%diffusivity = conductivity/(density*specific_heat) ! keeps the derived component consistent
end function new_material
end module heatflow_material
program custom_constructor
use heatflow_material
implicit none
type(material_t) :: copper, raw
copper = material_t('copper', 401.0_dp, 8960.0_dp, 385.0_dp) ! generic: 4 arguments -> new_material
print '(a, a, es10.3, a)', trim(copper%name), ' diffusivity=', copper%diffusivity, ' m2/s'
! The structure constructor is still there: five values (or keywords) do not match new_material.
raw = material_t('copper', 401.0_dp, 8960.0_dp, 385.0_dp, 0.0_dp)
print '(a, es10.3)', 'raw structure constructor diffusivity: ', raw%diffusivity
raw = material_t(name='bare')
print '(a, a, es10.3)', trim(raw%name), ' diffusivity=', raw%diffusivity
end program custom_constructor
Output:
copper diffusivity= 1.162E-04 m2/s
raw structure constructor diffusivity: 0.000E+00
bare diffusivity= 0.000E+00
Source: J3/23-007r1: 7.5.10 Construction of derived-type values, C7108 and 15.4.3.4.5 Restrictions on generic declarations, Fortran Programming Language: Quickstart, Derived types
material_t('copper', 401.0_dp, 8960.0_dp, 385.0_dp) has four arguments and matches new_material, which derives the
diffusivity. The built-in constructor is still available for the argument lists that the specific procedure does not
match: with five values, or with the keyword form material_t(name='bare'), the compiler chooses the structure
constructor. The text of the standard (7.5.10, Note 1) is that name(...) is interpreted as a generic function reference
when that is possible and as a structure constructor only otherwise; and C7108 forbids the case in which both would be valid.
That second rule matters when a specific procedure has the same signature as the structure constructor. The next
example makes make_pair(a, b) match pair_t(1, 2) exactly. GNU Fortran and Flang both resolve the call to the function:
module pair_mod
implicit none
private
public :: pair_t
type :: pair_t
integer :: a = 0
integer :: b = 0
end type pair_t
interface pair_t
module procedure make_pair
end interface pair_t
contains
function make_pair(a, b) result(p) ! same signature as the structure constructor
integer, intent(in) :: a, b
type(pair_t) :: p
p%a = a
p%b = b + 1000 ! visible proof that this function ran
end function make_pair
end module pair_mod
program constructor_precedence
use pair_mod
implicit none
type(pair_t) :: p
p = pair_t(1, 2)
print '(a, 2i6)', 'pair_t(1, 2) -> ', p%a, p%b
p = pair_t(a=1) ! make_pair needs b too: structure constructor
print '(a, 2i6)', 'pair_t(a=1) -> ', p%a, p%b
end program constructor_precedence
Output:
pair_t(1, 2) -> 1 1002
pair_t(a=1) -> 1 0
The function wins, which is why the guideline is to give the custom constructor a signature that the built-in one does not
have, or to make the built-in one inaccessible by making the components private: users then cannot reach it either, and
every value is built by the module’s code. A custom constructor is the natural place for validation (error stop or a
stat argument), for computing derived components, and for allocating components.
A custom constructor can be an elemental or a pure function, can have optional arguments, and can be accompanied by
other specific procedures with different argument lists. Because the generic has the same name as the type, one public statement
exports both, and a private type name hides both.
Accessibility of components and types
Accessibility is declared at three independent levels (7.5.2.2 and 7.5.4.8): the type’s name, the components, and (for procedures) the type-bound procedures. The standard stresses that they are independent and that all four combinations of public and private names with public and private components are possible.
-
A
privatestatement inside the type definition makes all components private by default and is allowed only in the specification part of a module (C771). Individual components can override it withpublic. -
A private component is accessible only within the module that contains the definition and its descendant submodules. Outside, it cannot be named by
%(so a program cannot read or write it) and it cannot be given a value in a structure constructor. -
A private type name (
type, private :: handle_t, or a bareprivatein the module) is accessible only inside the module. A public procedure may still take or return a value of that type, so callers can hold such values without being able to declare them.
The first example is the standard pattern for an opaque type: the type and its constructor are public, the components
are private, and all access goes through procedures. label is made public because it is meant to be read.
module heatflow_probe
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, probe_t, new_probe, record, mean_value, sample_count
integer, parameter :: dp = real64
type :: probe_t
private ! every component is private by default
character(len=16), public :: label = '' ! except this one
integer(int32) :: n = 0_int32
real(dp) :: total = 0.0_dp
end type probe_t
contains
function new_probe(label) result(p)
character(len=*), intent(in) :: label
type(probe_t) :: p
p%label = label
end function new_probe
subroutine record(p, value)
type(probe_t), intent(inout) :: p
real(dp), intent(in) :: value
p%n = p%n + 1_int32
p%total = p%total + value
end subroutine record
pure function sample_count(p) result(n) ! read-only accessor
type(probe_t), intent(in) :: p
integer(int32) :: n
n = p%n
end function sample_count
pure function mean_value(p) result(m)
type(probe_t), intent(in) :: p
real(dp) :: m
m = merge(p%total/real(p%n, dp), 0.0_dp, p%n > 0)
end function mean_value
end module heatflow_probe
program information_hiding
use heatflow_probe
implicit none
type(probe_t) :: p
p = new_probe('centre')
call record(p, 20.0_dp)
call record(p, 30.0_dp)
print '(a, a, a, i0, a, f5.1)', 'probe ', trim(p%label), ': n=', sample_count(p), ' mean=', mean_value(p)
end program information_hiding
Output:
probe centre: n=2 mean= 25.0
The following program is deliberately non-conforming. A module that defines a type with private components is used by a program that tries to construct the type with the built-in constructor, assign a component and read a component.
! INTENTIONALLY NON-CONFORMING: private components and the structure constructor.
module heatflow_probe
implicit none
private
public :: probe_t
type :: probe_t
private
integer :: n = 0
real :: total = 0.0
end type probe_t
end module heatflow_probe
program private_misuse
use heatflow_probe
implicit none
type(probe_t) :: p, q
q = probe_t(n=1, total=2.0) ! error: the components are not accessible here
p%n = 3 ! error: private component
print *, p%total ! error: private component
end program private_misuse
GNU Fortran 16.2:
04b_private_errors.f90:18:6:
18 | p%n = 3 ! error: private component
| 1
Error: Component 'n' at (1) is a PRIVATE component of 'probe_t'
04b_private_errors.f90:19:19:
19 | print *, p%total ! error: private component
| 1
Error: Component 'total' at (1) is a PRIVATE component of 'probe_t'
04b_private_errors.f90:17:7:
17 | q = probe_t(n=1, total=2.0) ! error: the components are not accessible here
| 1
Error: Component 'n' at (1) is a PRIVATE component of 'probe_t'
LLVM Flang 23:
error: Semantic errors in 04b_private_errors.f90
04b_private_errors.f90:17:17: error: PRIVATE name 'n' is accessible only within module 'heatflow_probe'
q = probe_t(n=1, total=2.0) ! error: the components are not accessible here
^
04b_private_errors.f90:17:26: error: PRIVATE name 'total' is accessible only within module 'heatflow_probe'
q = probe_t(n=1, total=2.0) ! error: the components are not accessible here
^^^
04b_private_errors.f90:18:5: error: PRIVATE name 'n' is accessible only within module 'heatflow_probe'
p%n = 3 ! error: private component
^
04b_private_errors.f90:19:14: error: PRIVATE name 'total' is accessible only within module 'heatflow_probe'
print *, p%total ! error: private component
^^^^^
Unlike a private module variable, which GNU Fortran reports as "has no IMPLICIT type" under implicit none, a private
component is reported by its real name, because the component name is looked up in the type, which is accessible.
Making the type name private is a different statement of intent, as the next deliberately non-conforming program shows. A public function can return a value of a private type, and a caller can pass it on, but a declaration in the program cannot name the type:
! INTENTIONALLY NON-CONFORMING: a private type name cannot be used outside its module.
module heatflow_handles
implicit none
private
public :: open_handle, handle_value
type :: handle_t ! the type name is private, its components are public
integer :: value = 0
end type handle_t
contains
function open_handle(v) result(h)
integer, intent(in) :: v
type(handle_t) :: h ! a public function may return a private type
h%value = v
end function open_handle
integer function handle_value(h)
type(handle_t), intent(in) :: h
handle_value = h%value
end function handle_value
end module heatflow_handles
program private_type_name
use heatflow_handles
implicit none
type(handle_t) :: h ! error: handle_t is not accessible here
print *, handle_value(open_handle(42)) ! fine on its own: no declaration names the type
end program private_type_name
GNU Fortran 16.2:
04c_private_type.f90:28:17:
28 | type(handle_t) :: h ! error: handle_t is not accessible here
| 1
Error: Derived type 'handle_t' at (1) is being used before it is defined
LLVM Flang 23:
error: Semantic errors in 04c_private_type.f90
04c_private_type.f90:28:8: error: Derived type 'handle_t' not found
type(handle_t) :: h ! error: handle_t is not accessible here
^^^^^^^^
04c_private_type.f90:28:21: error: No explicit type declared for 'h'
type(handle_t) :: h ! error: handle_t is not accessible here
^
With the offending declaration removed, the program prints 42 on both compilers. The GNU Fortran message, "being used
before it is defined", is misleading, but the declaration is the cause: the name of the type is not accessible. The
usual reason to want a private type name is an opaque handle (the caller must not construct or inspect the value), but a
type with a public name and private components achieves that and also lets callers declare variables, so it is the more
common choice.
Intrinsic assignment, copies and equality
a = b is defined for two entities of the same derived type (10.2.1.2) without any code from the programmer. It is
performed as if each component were assigned (10.2.1.3), and the kind of component decides what that means:
| Component | What a = b does to it |
|---|---|
Nonpointer, nonallocatable of intrinsic type |
ordinary intrinsic assignment, with conversion and padding rules |
Nonpointer, nonallocatable of derived type |
intrinsic assignment of that type, or its type-bound defined assignment if it has one that is consistent with the component |
Pointer |
pointer assignment: the receiving pointer becomes associated with the same target as the source; nothing is copied |
Allocatable |
if the receiving component is allocated it is deallocated; if the source component is allocated, the receiver is allocated with the same dynamic type, type parameters and bounds, and the value is copied; if the source is unallocated the receiver ends unallocated |
Coarray allocatable |
intrinsic assignment, with the extra rule that an unallocated source requires an unallocated target |
So a derived type is a value type as long as it has only nonpointer components, and a shared-reference type as soon as it has a pointer component. The standard also forbids intrinsic assignment when the variable is polymorphic and not allocatable (10.2.1.2), a restriction that matters in the object-oriented pages. The example below puts an allocatable component and a pointer component in one type and shows what the copy shares and what it does not.
module heatflow_snapshot
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, snapshot_t
integer, parameter :: dp = real64
type :: snapshot_t
integer(int32) :: step = 0_int32
real(dp), allocatable :: u(:) ! owned data: copied by assignment
real(dp), pointer :: view(:) => null() ! borrowed data: only the pointer is copied
end type snapshot_t
end module heatflow_snapshot
program derived_type_assignment
use heatflow_snapshot
implicit none
real(dp), target :: shared(3) = [1.0_dp, 2.0_dp, 3.0_dp]
type(snapshot_t) :: a, b
a%step = 1
a%u = [10.0_dp, 20.0_dp, 30.0_dp]
a%view => shared
b = a ! intrinsic assignment, component by component
print '(a, l1, a, i0)', 'b%u allocated: ', allocated(b%u), ' size=', size(b%u)
print '(a, l1)', 'pointer components associated with the same target? ', associated(a%view, b%view)
a%u(1) = -1.0_dp ! does not touch b%u: the allocatable component was copied
a%view(1) = -1.0_dp ! changes shared(1), seen through b%view as well
print '(a, 3f6.1)', 'a%u = ', a%u
print '(a, 3f6.1)', 'b%u = ', b%u
print '(a, 3f6.1)', 'b%view = ', b%view
deallocate (a%u) ! b is not affected
print '(a, l1, a, l1)', 'a%u allocated: ', allocated(a%u), ' b%u allocated: ', allocated(b%u)
end program derived_type_assignment
Output:
b%u allocated: T size=3
pointer components associated with the same target? T
a%u = -1.0 20.0 30.0
b%u = 10.0 20.0 30.0
b%view = -1.0 2.0 3.0
a%u allocated: F b%u allocated: T
Source: J3/23-007r1: 10.2.1.2 Intrinsic assignment statement and 10.2.1.3 Interpretation of intrinsic assignments, Fortran Programming Language: Quickstart, Derived types
The changes to a%u do not reach b%u, and deallocate (a%u) leaves b%u allocated, because the copy has its own storage.
The write through a%view changes shared, which b%view also sees, because only the association was copied. When a
type contains pointer components that represent ownership (the structure allocated them), the shallow copy is a
latent double deallocation. Make such components allocatable so that assignment deep-copies, or write a type-bound defined
assignment, see Type-Bound Procedures, Abstract Types and Finalization.
No intrinsic comparison
There are no intrinsic operations on derived types other than assignment (7.5.11). In particular == and /= do not exist,
so this deliberately non-conforming program is rejected:
! INTENTIONALLY NON-CONFORMING: there is no intrinsic == for derived types.
program no_equality
implicit none
type :: point_t
real :: x, y
end type point_t
type(point_t) :: a, b
a = point_t(1.0, 2.0)
b = point_t(1.0, 2.0)
if (a == b) print *, 'equal'
end program no_equality
GNU Fortran 16.2:
05b_no_equality.f90:10:6:
10 | if (a == b) print *, 'equal'
| 1
Error: Operands of comparison operator '==' at (1) are TYPE(point_t)/TYPE(point_t)
LLVM Flang 23:
error: Semantic errors in 05b_no_equality.f90
05b_no_equality.f90:10:7: error: Operands of .EQ. must have comparable types; have TYPE(point_t) and TYPE(point_t)
if (a == b) print *, 'equal'
^^^^^^
Operations are defined with a generic interface for the operator (15.4.3.4.2) and defined assignment is a subroutine with
assignment(=). Both are covered in Interfaces, Generics and Operator Overloading; the short form follows. Comparing
the real components of a structure for exact equality is rarely what is wanted, and -Wextra warns about it
(-Wcompare-reals), so the function uses a tolerance. An elemental function makes the operator work on arrays of
structures for free.
module heatflow_points
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, point_t, operator(==), operator(-)
integer, parameter :: dp = real64
real(dp), parameter :: tolerance = 1.0e-12_dp ! equality of reals is a tolerance test, not a bit test
type :: point_t
real(dp) :: x = 0.0_dp
real(dp) :: y = 0.0_dp
end type point_t
interface operator(==) ! intrinsic == does not exist for derived types
module procedure points_equal
end interface
interface operator(-)
module procedure points_difference
end interface
contains
elemental logical function points_equal(a, b)
type(point_t), intent(in) :: a, b
points_equal = abs(a%x - b%x) <= tolerance .and. abs(a%y - b%y) <= tolerance
end function points_equal
elemental function points_difference(a, b) result(d)
type(point_t), intent(in) :: a, b
type(point_t) :: d
d = point_t(a%x - b%x, a%y - b%y)
end function points_difference
end module heatflow_points
program derived_type_equality
use heatflow_points
implicit none
type(point_t) :: a, b(3)
a = point_t(1.0_dp, 2.0_dp)
b = [point_t(1.0_dp, 2.0_dp), point_t(0.0_dp, 0.0_dp), point_t(1.0_dp, 2.0_dp)]
print '(a, l1)', 'a == b(1): ', a == b(1)
print '(a, 3l2)', 'elemental on an array: ', a == b
print '(a, 2f5.1)', 'b(2) - a: ', b(2) - a
end program derived_type_equality
Output:
a == b(1): T
elemental on an array: T F T
b(2) - a: -1.0 -2.0
Source: J3/23-007r1: 15.4.3.4.2 Defined operations and 7.5.11 Derived-type operations and assignment
Note that operator(==) and operator(-) are listed in the public statement of the module: operators are generic
identifiers and have their own accessibility, so a private module default hides them unless they are exported.
Recursive types
A type cannot contain itself directly, because it would have infinite size, but a component that is a pointer or is allocatable can have the type being defined (C749 requires a previously defined type only for components that are neither). There are two ways to build a list or a tree.
-
A pointer component,
type(node_t), pointer :: next => null(), is the oldest form: pointer components date from Fortran 90, and the=> null()default initialization from Fortran 95. Nodes are allocated one at a time withallocate (p), linked withp%next => q, and freed explicitly, one at a time. Assignment of a node copies only the pointer, so two lists share their tails. The full treatment, including leaks and dangling pointers, is in Pointers and Targets. -
An allocatable component,
type(node_t), allocatable :: next, is the Fortran 2008 form. The object owns its successor:deallocate (head)frees the whole chain, and assignment must copy the whole chain.move_allocrearranges nodes without copying.
The next program builds a chain of sample temperatures with the allocatable form. push_front uses move_alloc to hand
the old chain to the new node, and the traversals are recursive functions, which is the natural way to walk a type
that is itself recursive.
module heatflow_samples
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, node_t, sample_list_t, push_front, count_nodes, sum_nodes
integer, parameter :: dp = real64
type :: node_t ! allocatable recursive component
real(dp) :: temperature = 0.0_dp
type(node_t), allocatable :: next
end type node_t
type :: sample_list_t
type(node_t), allocatable :: head
end type sample_list_t
contains
subroutine push_front(list, temperature)
type(sample_list_t), intent(inout) :: list
real(dp), intent(in) :: temperature
type(node_t), allocatable :: fresh
allocate (fresh)
fresh%temperature = temperature
call move_alloc(list%head, fresh%next) ! the old chain becomes the tail, no copy
call move_alloc(fresh, list%head)
end subroutine push_front
function count_nodes(list) result(n)
type(sample_list_t), intent(in) :: list
integer(int32) :: n
n = 0_int32
if (allocated(list%head)) n = count_from(list%head)
end function count_nodes
function sum_nodes(list) result(total)
type(sample_list_t), intent(in) :: list
real(dp) :: total
total = 0.0_dp
if (allocated(list%head)) total = sum_from(list%head)
end function sum_nodes
recursive function sum_from(node) result(total)
type(node_t), intent(in) :: node
real(dp) :: total
total = node%temperature
if (allocated(node%next)) total = total + sum_from(node%next)
end function sum_from
recursive function count_from(node) result(n)
type(node_t), intent(in) :: node
integer(int32) :: n
n = 1_int32
if (allocated(node%next)) n = n + count_from(node%next)
end function count_from
end module heatflow_samples
program recursive_types
use heatflow_samples
implicit none
type(sample_list_t) :: list
integer :: i
do i = 1, 5
call push_front(list, 20.0_dp + real(i, dp))
end do
print '(a, i0, a, f5.1)', 'nodes: ', count_nodes(list), ' newest=', list%head%temperature
print '(a, f6.1)', 'sum of temperatures: ', sum_nodes(list)
deallocate (list%head) ! deallocates every node, recursively
print '(a, l1, a, i0)', 'head allocated: ', allocated(list%head), ' nodes: ', count_nodes(list)
end program recursive_types
Output:
nodes: 5 newest= 25.0
sum of temperatures: 115.0
head allocated: F nodes: 0
Source: J3/23-007r1: 7.5.1 Derived type concepts, 7.5.4.1 Component definition statement and 9.7.3.2 Deallocation of allocatable variables, GCC Wiki: Fortran 2008 status
Two costs come with the allocatable form. Deallocating or copying a chain recurses once per node, so a list with millions of nodes can overflow the stack; and each node is a separate heap allocation, so a list is a poor container for numeric data and an array of nodes with integer indices, or a plain allocatable array, is usually better.
A compiler defect: copies of long recursive chains
Assignment of a type with a recursive allocatable component must copy the whole chain (10.2.1.3 step 2). On GNU Fortran 16.2 it does not. This program, which is conforming, builds a chain of four nodes, copies the container, overwrites every level of the original and prints the copy:
module heatflow_chain
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, node_t, chain_t, append_value
integer, parameter :: dp = real64
type :: node_t
real(dp) :: temperature = 0.0_dp
type(node_t), allocatable :: next
end type node_t
type :: chain_t
type(node_t), allocatable :: head
end type chain_t
contains
subroutine append_value(chain, temperature) ! walks to the end by recursion
type(chain_t), intent(inout) :: chain
real(dp), intent(in) :: temperature
if (.not. allocated(chain%head)) then
allocate (chain%head)
chain%head%temperature = temperature
else
call append_node(chain%head, temperature)
end if
end subroutine append_value
recursive subroutine append_node(node, temperature)
type(node_t), intent(inout) :: node
real(dp), intent(in) :: temperature
if (allocated(node%next)) then
call append_node(node%next, temperature)
else
allocate (node%next)
node%next%temperature = temperature
end if
end subroutine append_node
end module heatflow_chain
program copy_depth
use heatflow_chain
implicit none
type(chain_t) :: a, b
integer :: i
do i = 1, 4
call append_value(a, real(i, dp)) ! the chain holds 1.0, 2.0, 3.0, 4.0
end do
b = a ! intrinsic assignment: deep copy required by 10.2.1.3
a%head%temperature = -1.0_dp ! overwrite every level of a
a%head%next%temperature = -2.0_dp
a%head%next%next%temperature = -3.0_dp
a%head%next%next%next%temperature = -4.0_dp
print '(a, 4f6.1)', 'b after changing a: ', b%head%temperature, b%head%next%temperature, &
b%head%next%next%temperature, b%head%next%next%next%temperature
end program copy_depth
GNU Fortran 16.2:
b after changing a: 1.0 2.0 -3.0 -4.0
LLVM Flang 23:
b after changing a: 1.0 2.0 3.0 4.0
GNU Fortran copied the first two levels and shared the rest, so the copy shows -3.0 and -4.0; Flang printed the
expected 1.0 2.0 3.0 4.0. A variant of this program that went on to deallocate (a%head) and then read the copy crashed
with a segmentation fault, because the deeper nodes had been freed together with the original. The same defect is
described for a chain of three nodes in Pointers and Targets.
A workaround that works on both compilers is to give the node type a type-bound defined assignment that copies one node and
calls itself for the next, and to invoke it on the node rather than on the container (in this test GNU Fortran did not use the
component’s defined assignment when the container was assigned with =):
module heatflow_chain
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, node_t, chain_t, append_value
integer, parameter :: dp = real64
type :: node_t
real(dp) :: temperature = 0.0_dp
type(node_t), allocatable :: next
contains
procedure, private :: assign_node
generic :: assignment(=) => assign_node ! defined assignment: the component is copied level by level
end type node_t
type :: chain_t
type(node_t), allocatable :: head
end type chain_t
contains
recursive subroutine assign_node(lhs, rhs)
class(node_t), intent(out) :: lhs
class(node_t), intent(in) :: rhs
lhs%temperature = rhs%temperature
if (allocated(rhs%next)) then
allocate (lhs%next)
lhs%next = rhs%next ! recursion through the defined assignment
end if
end subroutine assign_node
subroutine append_value(chain, temperature)
type(chain_t), intent(inout) :: chain
real(dp), intent(in) :: temperature
if (.not. allocated(chain%head)) then
allocate (chain%head)
chain%head%temperature = temperature
else
call append_node(chain%head, temperature)
end if
end subroutine append_value
recursive subroutine append_node(node, temperature)
type(node_t), intent(inout) :: node
real(dp), intent(in) :: temperature
if (allocated(node%next)) then
call append_node(node%next, temperature)
else
allocate (node%next)
node%next%temperature = temperature
end if
end subroutine append_node
end module heatflow_chain
program copy_depth_fixed
use heatflow_chain
implicit none
type(chain_t) :: a, b
integer :: i
do i = 1, 4
call append_value(a, real(i, dp))
end do
allocate (b%head)
b%head = a%head ! call the defined assignment on the node itself
a%head%temperature = -1.0_dp
a%head%next%temperature = -2.0_dp
a%head%next%next%temperature = -3.0_dp
a%head%next%next%next%temperature = -4.0_dp
print '(a, 4f6.1)', 'b after changing a: ', b%head%temperature, b%head%next%temperature, &
b%head%next%next%temperature, b%head%next%next%next%temperature
deallocate (a%head)
print '(a, f4.1)', 'tail of b after deallocating a: ', b%head%next%next%next%temperature
end program copy_depth_fixed
Output on both compilers:
b after changing a: 1.0 2.0 3.0 4.0
tail of b after deallocating a: 4.0
Source: J3/23-007r1: 10.2.1.3 Interpretation of intrinsic assignments and 7.5.5 Type-bound procedures
Until the compiler is fixed, treat deep copies of recursive allocatable chains as something to test on every compiler
you support, or avoid them by passing chains by reference and moving them with move_alloc.
Arrays of structures and structures of arrays
An array of derived-type values is declared like any other array, and a component selector applied to it yields an array
of the components: row%temperature is the real array of the temperatures of all the elements, it can be assigned to as
a whole (row%temperature = [...]), used with a vector subscript (row([1, 5])%fixed = .true.), or used as a mask. The
rank rule (C919) allows only one part of the designator to have a nonzero rank, so soa%a(1:2)%b is accepted only when
one of the two is scalar.
The choice between an array of structures (an array of cell_t, one record per cell) and a structure of arrays
(one cells_t with one array per field) is a layout decision with performance consequences: the first keeps the fields
of one element together, the second keeps one field of all elements together, which is what vectorized loops and stencils
want. Performance and Memory Layout explains the
trade-off. The program shows the access syntax of both and converts one into the other.
module heatflow_cells
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
private
public :: dp, cell_t, cells_t, hottest
integer, parameter :: dp = real64
type :: cell_t ! array of structures: one record per cell
real(dp) :: temperature = 0.0_dp
real(dp) :: conductivity = 1.0_dp
logical :: fixed = .false.
end type cell_t
type :: cells_t ! structure of arrays: one array per field
real(dp), allocatable :: temperature(:)
real(dp), allocatable :: conductivity(:)
logical, allocatable :: fixed(:)
end type cells_t
contains
pure function hottest(c) result(i)
type(cell_t), intent(in) :: c(:)
integer(int32) :: i
i = int(maxloc(c%temperature, dim=1), int32) ! a component of an array of structures is an array
end function hottest
end module heatflow_cells
program arrays_of_structures
use heatflow_cells
implicit none
type(cell_t) :: row(5)
type(cells_t) :: soa
integer :: i
row%temperature = [20.0_dp, 25.0_dp, 80.0_dp, 25.0_dp, 20.0_dp] ! assign a component of every element
row([1, 5])%fixed = .true. ! vector subscript on the structure array
row(2:4)%conductivity = [1.0_dp, 2.0_dp, 1.0_dp]
print '(a, i0)', 'hottest cell: ', hottest(row)
print '(a, 5l2)', 'fixed: ', row%fixed
print '(a, f6.1)', 'mean of the free cells: ', sum(row%temperature, mask=.not. row%fixed)/count(.not. row%fixed)
soa%temperature = row%temperature ! SoA from AoS, one component at a time
soa%conductivity = row%conductivity
soa%fixed = row%fixed
print '(a, 5f6.1)', 'soa%temperature: ', soa%temperature
do i = 1, size(soa%temperature)
if (.not. soa%fixed(i)) soa%temperature(i) = soa%temperature(i) + 1.0_dp
end do
print '(a, 5f6.1)', 'after the update:', soa%temperature
end program arrays_of_structures
Output:
hottest cell: 3
fixed: T F F F T
mean of the free cells: 43.3
soa%temperature: 20.0 25.0 80.0 25.0 20.0
after the update: 20.0 26.0 81.0 26.0 20.0
Source: J3/23-007r1: 9.4.2 Structure components and 9.5.3 Array elements and array sections, Fortran Programming Language: Quickstart, Derived types
hottest receives the array of structures as an assumed-shape dummy argument and uses maxloc over a component, which
works without copying: the selector c%temperature of an assumed-shape array is a strided section.
The conversion from the array of structures to the structure of arrays is written as three assignments rather than a single structure constructor on purpose. On GNU Fortran 16.2 this valid program, a constructor whose allocatable component receives an array of components, prints the correct size and sum but fails when the array itself is written:
program aos_constructor
implicit none
type :: cell_t
real :: temperature = 0.0
end type cell_t
type :: cells_t
real, allocatable :: temperature(:)
end type cells_t
type(cell_t) :: row(3)
type(cells_t) :: soa
row%temperature = [1.0, 2.0, 3.0]
soa = cells_t(temperature=row%temperature) ! valid: an array expression for an allocatable component
print *, 'size=', size(soa%temperature), ' sum=', sum(soa%temperature)
print *, soa%temperature
end program aos_constructor
GNU Fortran 16.2 (backtrace frames omitted):
At line 15 of file 08b_aos_constructor_defect.f90 (unit = 6, file = 'stdout')
Internal Error: list_formatted_write(): Bad type
size= 3 sum= 6.00000000
LLVM Flang 23:
size= 3 sum= 6.
1. 2. 3.
The descriptor that the constructor stored in soa%temperature evidently lacks the element type, so list-directed output
stops with an internal error; assigning the components one at a time (soa%temperature = row%temperature), as in the main
example, or constructing from a temporary array, avoids it. The defect was seen on the GNU Fortran 16.2 used for this page
and may be fixed in later releases.
Procedure pointer components
A component can be a pointer to a procedure (R741). It must have the pointer attribute (C761), the access attributes
public or private, and, when the interface is implicit or has no arguments, nopass (C762); otherwise the first
argument of the target is the passed-object dummy argument that receives the structure itself (pass is the default,
pass(name) selects another argument, C763). Procedure pointer components make a structure carry behaviour that can
be chosen at run time, which is the "strategy" pattern; the fixed alternative is a type-bound procedure, see
Type-Bound Procedures, Abstract Types and Finalization.
module heatflow_sources
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, source_t, constant_source, pulse_source
integer, parameter :: dp = real64
abstract interface
pure function source_fn(t) result(q) ! heat source as a function of time
import :: dp
real(dp), intent(in) :: t
real(dp) :: q
end function source_fn
end interface
type :: source_t
character(len=12) :: name = 'none'
procedure(source_fn), pointer, nopass :: rate => null() ! procedure pointer component
end type source_t
contains
pure function constant_source(t) result(q)
real(dp), intent(in) :: t
real(dp) :: q
q = 5.0_dp + 0.0_dp*t
end function constant_source
pure function pulse_source(t) result(q)
real(dp), intent(in) :: t
real(dp) :: q
q = merge(100.0_dp, 0.0_dp, t < 0.5_dp)
end function pulse_source
end module heatflow_sources
program procedure_components
use heatflow_sources
implicit none
type(source_t) :: s(2), empty
s(1) = source_t('constant', constant_source) ! a procedure as a structure constructor value
s(2) = source_t(name='pulse', rate=pulse_source)
print '(a, f6.1, f6.1)', s(1)%name, s(1)%rate(0.0_dp), s(1)%rate(1.0_dp)
print '(a, f6.1, f6.1)', s(2)%name, s(2)%rate(0.0_dp), s(2)%rate(1.0_dp)
print '(a, l1)', 'default component associated: ', associated(empty%rate)
end program procedure_components
Output:
constant 5.0 5.0
pulse 100.0 0.0
default component associated: F
A structure constructor takes a procedure as the value of a procedure pointer component (a proc-target, C7109), either by
position or by keyword. A procedure pointer component without an initializer is undefined; here it is initialized with
=> null(). Calling a disassociated procedure pointer is an error that, as for data pointers, the compiler is not required to detect.
Storage: sequence types, bind(c) types and layout
By default the standard says nothing about how components are stored: the processor may reorder them, pad them and keep them in any form (7.5.1). The order of declaration matters only for the places that name the component order, which are structure constructors without keywords, intrinsic assignment, namelist input and the expansion of a structure in formatted or list-directed I/O (7.5.2.3, Note 2). Two statements give stronger guarantees.
-
sequence(R731, 7.5.2.3) makes the order of the components the storage order, and makes the type usable incommonandequivalencewith the storage association rules of 19.5.3. Its constraints are strict (C745): at least one component, every data component of intrinsic type or of a sequence type, no type parameters and no type-bound procedure part; and a sequence type cannot be extended (C740). It does not remove padding between components. -
bind(c)(7.5.2, 18.3.4) makes the type interoperable with a Cstruct: the layout is the one the companion C processor uses. The constraints (C1801 to C1806) are: nosequence, no type parameters, noextends, no type-bound procedures, at least one component, and every component a nonpointer, nonallocatable data component of interoperable type. Anything with a descriptor or a length that C does not know is excluded.
| Property | sequence type |
bind(c) type |
|---|---|---|
Storage order |
order of declaration |
as C lays out the same members |
Component types |
intrinsic or sequence types, any kind |
interoperable types only: |
Allocatable and pointer components |
allowed when the type is intrinsic or a sequence type (C745), but the type is then not a numeric or character sequence type |
none |
Extension, type-bound procedures, parameters |
none |
none |
Use |
storage association, same-type rule across scopes, |
calls to and from C, see Interoperability with C |
The program shows a sequence type used on both sides of an interface body (see the same-type rule in the next section) and
what storage_size reports for it and for an ordinary type with the same components. In both GNU Fortran and Flang the two
sizes are equal, and both exceed the sum of the components, which shows the padding that sequence does not remove.
program sequence_types
use, intrinsic :: iso_fortran_env, only: int32, real32, int8
implicit none
type :: reading_t ! a SEQUENCE type: order of components is the storage order
sequence
integer(int32) :: id
real(real32) :: value
integer(int8) :: flag
end type reading_t
type :: plain_t ! an ordinary type: storage order and padding are processor-dependent
integer(int32) :: id
real(real32) :: value
integer(int8) :: flag
end type plain_t
interface
subroutine show(r) ! the interface body repeats the SEQUENCE definition: same type
use, intrinsic :: iso_fortran_env, only: int32, real32, int8
type :: reading_t
sequence
integer(int32) :: id
real(real32) :: value
integer(int8) :: flag
end type reading_t
type(reading_t), intent(in) :: r
end subroutine show
end interface
type(reading_t) :: r
type(plain_t) :: p
r = reading_t(7_int32, 21.5_real32, 1_int8)
p = plain_t(7_int32, 21.5_real32, 1_int8)
call show(r)
print '(a, i0, a, i0)', 'storage_size in bits, sequence type: ', storage_size(r), ' plain type: ', storage_size(p)
print '(a, i0)', 'sum of the component sizes in bits: ', storage_size(r%id) + storage_size(r%value) + storage_size(r%flag)
end program sequence_types
subroutine show(r)
use, intrinsic :: iso_fortran_env, only: int32, real32, int8
implicit none
type :: reading_t
sequence
integer(int32) :: id
real(real32) :: value
integer(int8) :: flag
end type reading_t
type(reading_t), intent(in) :: r
print '(a, i0, a, f5.1, a, i0)', 'show: id=', r%id, ' value=', r%value, ' flag=', r%flag
end subroutine show
Output:
show: id=7 value= 21.5 flag=1
storage_size in bits, sequence type: 96 plain type: 96
sum of the component sizes in bits: 72
The sum of the component sizes is 72 bits (32 plus 32 plus 8), and the object takes 96 bits: three bytes of padding
align the 4-byte members. storage_size returns the size in bits of an element, and it is the portable way to ask; it
is processor dependent for ordinary types.
The bind(c) form is tested against a real C compiler in the next program. The C function receives a pointer to the
struct, doubles one member and returns sizeof the struct; c_sizeof gives the same number on the Fortran side. The
Fortran interface declares the dummy argument as type(heatflow_cell_t), intent(inout), which is passed by
reference, that is, as the C pointer.
#include <stddef.h>
typedef struct {
int id;
double temperature;
double position[2];
} heatflow_cell_t;
/* Doubles the temperature of one cell and returns the size of the struct. */
size_t heatflow_heat_cell(heatflow_cell_t *c) {
c->temperature *= 2.0;
return sizeof(heatflow_cell_t);
}
Source: J3/23-007r1: 18.3.4 Interoperability of derived types and C structure types and 18.2.3.8 C_SIZEOF
program bind_c_type
use, intrinsic :: iso_c_binding, only: c_int, c_double, c_size_t, c_sizeof
implicit none
type, bind(c) :: heatflow_cell_t ! interoperable with the C struct of the same layout
integer(c_int) :: id
real(c_double) :: temperature
real(c_double) :: position(2)
end type heatflow_cell_t
interface
function heatflow_heat_cell(c) bind(c, name='heatflow_heat_cell') result(nbytes)
import :: heatflow_cell_t, c_size_t
type(heatflow_cell_t), intent(inout) :: c ! passed by reference: a C pointer to the struct
integer(c_size_t) :: nbytes
end function heatflow_heat_cell
end interface
type(heatflow_cell_t) :: cell
integer(c_size_t) :: c_size
cell = heatflow_cell_t(id=1_c_int, temperature=21.5_c_double, position=[0.25_c_double, 0.75_c_double])
c_size = heatflow_heat_cell(cell)
print '(a, f5.1)', 'temperature after the C call: ', cell%temperature
print '(a, i0, a, i0)', 'sizeof in C: ', c_size, ' c_sizeof in Fortran: ', c_sizeof(cell)
end program bind_c_type
Built with gcc -c 15_cell.c and gfortran 15_bind_c_type.f90 15_cell.o (strict flags as above), and run:
temperature after the C call: 43.0
sizeof in C: 32 c_sizeof in Fortran: 32
Flang 23 linked against the same object file printed the same two lines.
Source: J3/23-007r1: 18.3.4 Interoperability of derived types and C structure types and 18.2.3.8 C_SIZEOF, GNU Fortran manual: Options to request or suppress errors and warnings
The constraints are enforced. This deliberately non-conforming module has an allocatable component, a pointer component, a
component of default real, and an empty bind(c) type:
! INTENTIONALLY NON-CONFORMING: components that cannot be in a BIND(C) type.
module bad
use, intrinsic :: iso_c_binding, only: c_double
implicit none
type, bind(c) :: cell_t
real(c_double), allocatable :: history(:) ! error: allocatable
real :: z ! only a warning: the kind is not named by iso_c_binding
integer, pointer :: p ! error: pointer
end type cell_t
type, bind(c) :: empty_t ! error: no components
end type empty_t
end module bad
GNU Fortran 16.2:
15b_bind_c_errors.f90:6:45:
5 | type, bind(c) :: cell_t
| 2
6 | real(c_double), allocatable :: history(:) ! error: allocatable
| 1
Error: Component 'history' at (1) cannot have the ALLOCATABLE attribute because it is a member of the BIND(C) derived type 'cell_t' at (2)
15b_bind_c_errors.f90:7:13:
7 | real :: z ! only a warning: the kind is not named by iso_c_binding
| 1
Warning: Component 'z' in derived type 'cell_t' at (1) may not be C interoperable, even though derived type 'cell_t' is BIND(C) [-Wc-binding-type]
15b_bind_c_errors.f90:8:25:
5 | type, bind(c) :: cell_t
| 2
......
8 | integer, pointer :: p ! error: pointer
| 1
Error: Component 'p' at (1) cannot have the POINTER attribute because it is a member of the BIND(C) derived type 'cell_t' at (2)
15b_bind_c_errors.f90:8:25:
8 | integer, pointer :: p ! error: pointer
| 1
Warning: Component 'p' in derived type 'cell_t' at (1) may not be C interoperable, even though derived type 'cell_t' is BIND(C) [-Wc-binding-type]
15b_bind_c_errors.f90:10:71:
10 | type, bind(c) :: empty_t ! error: no components
| 1
Error: GNU Extension: Derived type 'empty_t' with BIND(C) attribute at (1) has no components
LLVM Flang 23 rejects the allocatable and pointer components and does not mention the others:
error: Semantic errors in 15b_bind_c_errors.f90
15b_bind_c_errors.f90:6:36: error: An interoperable derived type cannot have a pointer or allocatable component
real(c_double), allocatable :: history(:) ! error: allocatable
^^^^^^^
15b_bind_c_errors.f90:8:25: error: An interoperable derived type cannot have a pointer or allocatable component
integer, pointer :: p ! error: pointer
^
GNU Fortran 16.2 with -std=f2023 treats the empty bind(c) type as an error ("GNU Extension") because C1805 requires
a component, and it warns, under -Wc-binding-type, that a component whose kind is spelled real instead of
real(c_float) "may not be C interoperable". The warning is a reminder to use the kind constants of iso_c_binding
instead of default kinds, even though the default real happens to be the same as c_float on most processors.
An ordinary type with no components, in contrast, is allowed:
program empty_type
implicit none
type :: tag_t ! a type with no components is allowed
end type tag_t
type :: c_t
type(c_t), pointer :: next => null() ! fine: a pointer to the type being defined
end type c_t
type(tag_t) :: t
type(c_t) :: c
print '(a, l1, a, i0)', 'next associated: ', associated(c%next), ' storage_size of an empty type in bits: ', storage_size(t)
end program empty_type
Output:
next associated: F storage_size of an empty type in bits: 0
Are two types the same type?
Two definitions with the same name are two types, with one exception (7.5.2.4). Declarations with reference to the same
definition, reached by use or host association, name the same type, even through renaming. Definitions in different scoping
units with the same name are the same type only if all of them are sequence types or all are bind(c) types, none has a
private component, and the components agree in order, name and attributes. That is how the sequence example above can
define reading_t in the main program and again inside the interface body and still call show with it, and why it
is the only way to repeat a type definition without a shared module. Otherwise, two copies of a definition are two
types, even if they are identical, and a value of one cannot be assigned to or passed as the other:
! INTENTIONALLY NON-CONFORMING: two definitions with the same name are two different types.
module geometry_a
implicit none
type :: point_t
real :: x, y
end type point_t
end module geometry_a
module geometry_b
implicit none
type :: point_t ! same name, same components, but a different type
real :: x, y
end type point_t
end module geometry_b
program different_types
use geometry_a, only: pa => point_t
use geometry_b, only: pb => point_t
implicit none
type(pa) :: a
type(pb) :: b
a = pa(1.0, 2.0)
b = a ! error: different derived types
b = pb(a%x, a%y) ! fine: build the second type explicitly
print *, b%x, b%y
end program different_types
GNU Fortran 16.2:
16_same_type_errors.f90:23:6:
23 | b = a ! error: different derived types
| 1
Error: Cannot convert TYPE(point_t) to TYPE(point_t) at (1)
LLVM Flang 23:
error: Semantic errors in 16_same_type_errors.f90
16_same_type_errors.f90:23:3: error: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(pb) and TYPE(pa)
b = a ! error: different derived types
^^^^^
The usual cause in real code is a type copied into two modules, or a type defined in an include file that two program
units each include without a shared module. The cure is to define the type in one module and use it from every unit
that needs it; the renaming (pa => point_t) in the example is only there to give the two types different local names.
Derived types in input and output
A structure may be an item of a read, write or print list. For intrinsic formatted and list-directed I/O (12.6.3) it is
treated as if its components appeared in the list in component order, so the format must supply a matching sequence of
edit descriptors, and the components must be accessible where the statement is, and must not be pointers or allocatable.
For unformatted I/O the structure is a single value in a processor-dependent form. Both restrictions disappear if the type
has a defined input/output procedure, the dt edit descriptor, which is how grid_t is printed later in the page. This
deliberately non-conforming program writes a structure with an allocatable component:
! INTENTIONALLY NON-CONFORMING: intrinsic I/O of a type with an allocatable component.
program io_allocatable
implicit none
type :: field_t
integer :: n = 3
real, allocatable :: u(:)
end type field_t
type(field_t) :: f
f%u = [1.0, 2.0, 3.0]
print *, f
end program io_allocatable
GNU Fortran 16.2:
13_io_allocatable.f90:10:12:
10 | print *, f
| 1
Error: Data transfer element at (1) cannot have ALLOCATABLE components unless it is processed by a defined input/output procedure
LLVM Flang 23:
error: Semantic errors in 13_io_allocatable.f90
13_io_allocatable.f90:10:12: error: Derived type 'field_t' in I/O cannot have an allocatable or pointer direct component 'u' unless using defined I/O
print *, f
^
13_io_allocatable.f90:6:26: Declaration of 'u'
real, allocatable :: u(:)
^
An allocatable or pointer ultimate component therefore needs a defined I/O procedure or an explicit write of each
component. Namelist input of a structure follows the same rules and is described in
Internal Files, Namelist and Non-Advancing I/O.
Derived types in heatflow
heatflow defines its configuration and its grid as derived types. config_t is a pure record of parameters, and every
component has a default, so a bare declaration is a complete default configuration and a constructor with keywords
changes only what it names. It is the type that the namelist reader fills in
(Internal Files, Namelist and Non-Advancing I/O).
type :: config_t
integer(ip) :: nx = 21_ip
integer(ip) :: ny = 21_ip
real(dp) :: lx = 1.0_dp
real(dp) :: ly = 1.0_dp
real(dp) :: alpha = default_alpha
real(dp) :: dt = 1.0e-4_dp
real(dp) :: hot_temperature = 100.0_dp
real(dp) :: boundary_value = 0.0_dp
integer(ip) :: max_steps = default_max_steps
real(dp) :: tolerance = default_tolerance
integer(bc_kind) :: bc = dirichlet
character(len=16) :: solver = 'ftcs'
character(len=64) :: output = 'heatflow.csv'
end type config_t
grid_t combines scalar components with an allocatable temperature array, so that assignment of a grid is a deep copy, and
adds type-bound procedures and a defined formatted output, which are the subject of the following pages:
type :: grid_t
integer(ip) :: nx = 0_ip
integer(ip) :: ny = 0_ip
real(dp) :: dx = 0.0_dp
real(dp) :: dy = 0.0_dp
real(dp), allocatable :: u(:, :)
contains
procedure :: apply_boundary => grid_apply_boundary
procedure :: set_hot_spot => grid_set_hot_spot
procedure :: max_temperature => grid_max_temperature
generic :: write(formatted) => write_formatted
procedure, private :: write_formatted => grid_write_formatted
end type grid_t
Both come from the sources in the project (src/heatflow_config.f90 and src/heatflow_grid.f90) and are compiled together
with heatflow_kinds, heatflow_constants and heatflow_boundary. The driver below uses default initialization, a
keyword constructor, a snapshot made by assignment, and the defined output of grid_t:
program heatflow_types
use heatflow_kinds, only: dp
use heatflow_boundary, only: neumann, boundary_name
use heatflow_config, only: config_t
use heatflow_grid, only: grid_t, make_grid
implicit none
type(config_t) :: cfg ! every component has a default: no constructor call needed
type(config_t) :: custom
type(grid_t) :: g, snapshot
print '(a, 2i3, a, a)', 'default config: nx, ny =', cfg%nx, cfg%ny, ' bc=', boundary_name(cfg%bc)
custom = config_t(nx=11, ny=11, bc=neumann, solver='jacobi') ! keywords, the rest keep their defaults
print '(a, 2i3, 2a)', 'custom config: nx, ny =', custom%nx, custom%ny, ' bc=', boundary_name(custom%bc)
print '(3a, f6.1)', 'solver=', trim(custom%solver), ' hot=', custom%hot_temperature
g = make_grid(custom%nx, custom%ny, custom%lx, custom%ly) ! grid_t has an allocatable component u
call g%set_hot_spot(custom%hot_temperature)
snapshot = g ! deep copy of u
call g%apply_boundary(custom%bc, custom%boundary_value)
g%u(6, 6) = 0.0_dp
print *, g ! defined derived-type output of grid_t
print *, snapshot
print '(a, l1, a, i0, a, i0)', 'u allocated: ', allocated(snapshot%u), ' shape ', size(snapshot%u, 1), 'x', size(snapshot%u, 2)
end program heatflow_types
Output with GNU Fortran 16.2:
default config: nx, ny = 21 21 bc=dirichlet
custom config: nx, ny = 11 11 bc=neumann
solver=jacobi hot= 100.0
grid_t(11x11) max= 0.000E+00
grid_t(11x11) max= 1.000E+02
u allocated: T shape 11x11
Source: J3/23-007r1: 7.5.4.6 Default initialization for components, 10.2.1.3 Interpretation of intrinsic assignments and 12.6.4.8 Defined input/output, Fortran Programming Language: Quickstart, Derived types
Flang prints the same lines, except that its list-directed output has no leading blank in front of the two grid_t lines.
The snapshot kept the maximum of 100.0 of the hot spot while g, after apply_boundary and the explicit write to the centre
cell, had a maximum of zero: the allocatable u was copied, not shared.
Runtime errors with derived types
Most mistakes with structures are mistakes about the status of a component. Two are shown with real output, because they behave differently on the two compilers.
An allocatable component that has not been allocated is unallocated, and referring to its elements is an error that GNU Fortran
reports only with -fcheck=bounds (part of -fcheck=all):
program unallocated_component
implicit none
type :: field_t
real, allocatable :: u(:)
end type field_t
type(field_t) :: f
print *, 'about to read an unallocated component'
print *, f%u(1)
end program unallocated_component
GNU Fortran 16.2 (backtrace frames omitted):
about to read an unallocated component
At line 8 of file 12_unallocated_component.f90
Fortran runtime error: Index '1' of dimension 1 of array 'f%u' above upper bound of 0
LLVM Flang 23, without extra options:
Segmentation fault
A disassociated pointer component is worse, because the standard does not require any diagnosis and the behaviour is undefined:
program null_pointer_component
implicit none
type :: node_t
integer :: value = 0
type(node_t), pointer :: next => null()
end type node_t
type(node_t), target :: head
type(node_t), pointer :: p
print *, 'head%value =', head%value
p => head%next
print *, 'associated(p) =', associated(p)
print *, 'p%value =', p%value
end program null_pointer_component
GNU Fortran 16.2 with -fcheck=all printed 0 for the value of the target of a disassociated pointer, a result
that means nothing and could as well have been a crash; LLVM Flang 23 died with a segmentation fault:
head%value = 0
associated(p) = F
p%value = 0
Segmentation fault
Test associated (p) before dereferencing a pointer component whose status is not certain, and prefer allocatable
components, which have a status that allocated can always test.
Where the books are outdated
The books consulted for this section (listed in the bibliography) were written against older revisions of the language, and material derived from Fortran 90 and 95 courses is still common. For derived types the differences that matter are:
-
Allocatable components and recursive allocatable components. Older texts use pointer components for every dynamic or recursive component. Allocatable components (Fortran 2003, from TR 15581) are owned by the object, are deep-copied by assignment and are freed automatically; recursive allocatable components (Fortran 2008) extend this to lists and trees. The pointer form is now the exception, for sharing and for graphs.
-
Custom constructors. A generic interface with the name of the type (Fortran 2003) gives a type its own constructor while keeping the built-in one; older texts say that the structure constructor cannot be extended.
-
Omitted allocatable components. Since Fortran 2008 a structure constructor can leave out an allocatable component.
-
The authors' deprecations. The appendix of deprecated features in one of the books recommends avoiding
sequenceand storage association. That is the authors' recommendation, not the standard’s:sequenceis not among the obsolescent features of J3/23-007r1 (B.3). Preferbind(c)or a normal type unless storage association is really needed, and keepsequencefor the cases above. -
Non-standard records. Fortran 77 programs often model structures with parallel arrays or with the DEC
structureandrecordextension. GNU Fortran still accepts the extension with-fdec-structure, "for compatibility only", and Flang lists the structure and record statements among its extensions (itsunionandmapare not yet supported); derived types replace both. -
No compiler information. The books say nothing about which compiler accepts what. The defect with recursive allocatable copies and the constructor defect above are examples of what a real compiler may still get wrong.
Standard version provenance and compiler support
| Feature | Introduced | Compiler support checked on 2026-10-19 |
|---|---|---|
Derived types, structure constructors, |
Fortran 90 |
all compilers |
Default initialization of components, |
Fortran 95 |
GNU Fortran 16.2 and LLVM Flang 23 |
Allocatable components (TR 15581), |
Fortran 2003 |
both |
Recursive allocatable components, omitting an allocatable component from a structure constructor |
Fortran 2008 |
both accept the programs; the deep copy of a chain longer than two nodes is wrong in GNU Fortran 16.2 |
Component constructed from |
Fortran 90 syntax, allocatable component Fortran 2003 |
valid; GNU Fortran 16.2 produces a descriptor that list-directed output rejects |
DEC |
extension ( |
not in the standard |
The introduction editions follow the standard’s own history and the GNU Fortran status pages for Fortran 2003 and Fortran 2008; the lists of the Fortran 2023 additions in J3/23-007r1 do not touch the features of this page.
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.
Pitfalls
-
Forgetting that a derived type with a pointer component shares storage on assignment.
b = acopies the pointer; both objects then own the same target, and onedeallocateinvalidates the other. -
Assuming a value for every component. A component without a default is undefined in a new variable. Give every component a default, or build the value with a constructor that supplies all of them.
-
Believing a default initialization implies
save. It does not (7.5.4.6); only explicit initialization in a declaration does. -
Using
intent(out)on a structure to keep part of its old value.intent(out)resets the defaulted components and leaves the rest undefined; useintent(inout). -
A custom constructor that shadows the built-in one. If its signature matches the structure constructor, the function is called for every use, including those in the module itself.
-
A public constructor for a type whose components are private. Outside the module the built-in constructor is not usable, so a custom one is the only way to create values; export it with the type.
-
Writing the type name twice. Two copies of one definition are two different types; define the type once in a module.
-
Reading a private entity and getting an unrelated message. A private module variable shows up as "no IMPLICIT type", a private type name as "used before it is defined"; the cause in both cases is accessibility.
-
Printing a structure with an allocatable or pointer component with
print *. It is rejected at compile time; write the components or provide a defined output procedure. -
Comparing structures with
==or comparing their real components exactly. There is no intrinsic==, and an exact equality test onrealcomponents is almost never right. -
Deep recursion in recursive allocatable chains. Copy and deallocation recurse once per node.
-
Trusting a deep copy of a long recursive chain on every compiler. GNU Fortran 16.2 copies only the first two levels.
-
Dereferencing a disassociated pointer component without checking. The result is undefined, not necessarily a crash.
-
Relying on the layout of an ordinary type. Only
sequenceandbind(c)types have a defined order, and neither removes padding.
See also Best Practices and Style, Common Gotchas and Pitfalls and, for the older alternatives to derived types, Legacy Features and Modernization.