Type-Bound Procedures, Abstract Types and Finalization
|
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 with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2)
and run, and each example was also built and run with LLVM Flang 23; the output shown is real output, and it was
identical for both compilers unless the text 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.5 of the
standard: the passed-object dummy argument is 7.5.4.5, type-bound procedures 7.5.5, final subroutines 7.5.6 (the FINAL
statement, the finalization process, when finalization occurs and the entities that are not finalized), and abstract
types, inheritance and overriding 7.5.7. Generic bindings must obey the ambiguity rules of 15.4.3.4.5, a type-bound
reference is resolved by 15.5.6, and the defined input/output procedures are in 12.6.4.8. Annex C has the standard’s own
examples (C.3.3 generic type-bound procedures and C.3.4 abstract types). According to the table on
History, Standards and Compilers, type-bound
procedures, abstract types and final are Fortran 2003 features; all three are supported by both compilers used here.
A type-bound procedure is a procedure that is attached to a derived type and called through an object of that type,
call grid%apply_boundary(bc, value). It is the Fortran counterpart of a method: the object is passed implicitly as
an argument (the passed-object dummy argument), the binding is inherited by type extensions, and an extension can
override it so that the version executed depends on the dynamic type of the object. An abstract type declares
bindings without implementing them, and obliges every concrete extension to supply the implementation. A final
subroutine is the nearest thing Fortran has to a destructor: a subroutine that the processor calls when an object of
the type is finalized. This page uses the solver hierarchy of the running example, heatflow: the abstract solver_t
with its deferred step binding, the concrete jacobi_solver_t and ftcs_solver_t, and the final subroutine that
jacobi_solver_t has. Type extension, class(...), polymorphic allocation and select type are covered in
Object Oriented Programming; plain derived types,
components and structure constructors in Derived Types; modules,
private entities and submodules in
Modules and Submodules; and generic interfaces and
operator overloading outside types in
Interfaces, Generics and Operator Overloading.
Declaring type-bound procedures
Bindings go in the type-bound procedure part of a derived type: everything after the contains statement of the type
definition (R746, 7.5.5). It can start with a private statement and then holds three kinds of binding statements.
| Statement | Meaning | Clause |
|---|---|---|
|
declares a specific type-bound procedure; the procedure is a module procedure or an external procedure with an explicit interface (C774); without |
7.5.5 |
|
declares bindings that have an interface but no body; |
7.5.5 |
|
declares a generic binding that groups specific bindings under a name, a defined operator, a defined assignment or a defined input/output specification (R751) |
7.5.5 |
|
declares finalizers (R753); not a procedure binding with a name |
7.5.6.1 |
The binding attributes (R752) are given after procedure, separated by commas:
| Attribute | Effect | Clause |
|---|---|---|
|
the object is passed as the first dummy argument, or as the dummy argument called |
7.5.4.5 |
|
the binding has no passed-object dummy argument; it must be given when the interface has no dummy argument of the type (C784) |
7.5.4.5 |
|
extensions cannot override the binding (C790); cannot be combined with |
7.5.5 |
|
the binding has no implementation in this type, only in its extensions; allowed only in an |
7.5.5, 7.5.7.1 |
|
accessibility of the binding, |
7.5.5 |
The passed-object dummy argument has to be a scalar, nonpointer, nonallocatable dummy data object of the type being
defined, and class(type_name) when the type is extensible, with all length type parameters assumed (C765). It cannot
have the value attribute. In practice a binding of an extensible type declares it as class(type_name), as the next
example does; declaring it type(type_name) is rejected at compile time. GNU Fortran 16.2 reports
Error: Non-polymorphic passed-object dummy argument of 'bump' at (1) and LLVM Flang 23 reports
error: Passed-object dummy argument 'self' of procedure 'bump' must be polymorphic because 't' is extensible. The example shows the four basic forms: a binding with the same name as its procedure, a binding renamed
with =>, a binding whose object is not the first argument (pass(source)) and a nopass binding.
module heat_sources
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, heat_source_t
integer, parameter :: dp = real64
!> A point heat source with three kinds of binding.
type :: heat_source_t
real(dp) :: x = 0.0_dp ! position
real(dp) :: power = 0.0_dp ! W
contains
procedure :: temperature_at ! binding name = procedure name
procedure :: shift => move_source ! binding name differs
procedure, pass(source) :: scaled_by ! passed object is not the first argument
procedure, nopass :: unit_name ! no passed-object dummy
end type heat_source_t
contains
pure function temperature_at(self, xq) result(t)
class(heat_source_t), intent(in) :: self
real(dp), intent(in) :: xq
real(dp) :: t
t = self%power/(1.0_dp + abs(xq - self%x))
end function temperature_at
subroutine move_source(self, dx)
class(heat_source_t), intent(inout) :: self
real(dp), intent(in) :: dx
self%x = self%x + dx
end subroutine move_source
function scaled_by(factor, source) result(s)
real(dp), intent(in) :: factor
class(heat_source_t), intent(in) :: source
type(heat_source_t) :: s
s = heat_source_t(source%x, factor*source%power)
end function scaled_by
function unit_name() result(name)
character(len=:), allocatable :: name
name = 'kelvin'
end function unit_name
end module heat_sources
program binding_basics
use heat_sources
implicit none
type(heat_source_t) :: s, big
s = heat_source_t(x=1.0_dp, power=100.0_dp)
print '(a, f8.3)', 'temperature_at(3.0) = ', s%temperature_at(3.0_dp)
call s%shift(0.5_dp)
print '(a, f8.3)', 'after shift, x = ', s%x
big = s%scaled_by(2.0_dp) ! the object is the second actual argument
print '(a, f8.3)', 'scaled power = ', big%power
print '(2a)', 'unit = ', s%unit_name()
end program binding_basics
Output:
temperature_at(3.0) = 33.333
after shift, x = 1.500
scaled power = 200.000
unit = kelvin
Source: J3/23-007r1: 7.5.4.5 The passed-object dummy argument, 7.5.5 Type-bound procedures, R746 to R752
The same output was produced by LLVM Flang 23. A few details of that program are worth stating:
-
s%temperature_at(3.0_dp)is the same as callingtemperature_atwithsas its first argument. The object does not appear in the argument list. Forscaled_bythe object is the second dummy argument, sos%scaled_by(2.0_dp)supplies2.0_dpas the first. -
The rule for how a reference is resolved is in 15.5.6: for a specific binding, the procedure called is the one bound to that name in the dynamic type of the object; for a generic binding, the generic is looked up in the declared type and then dispatches on to the specific binding by the same rule.
-
A
nopassbinding is still called through an object (s%unit_name()). Unlike static methods in some other languages, it cannot be called through the type name. This deliberately wrong fragment, taken from the same module, is rejected. GNU Fortran 16.2 reports:01_nopass_via_type.f90:60:61: 60 | print '(2a)', 'unit (via type) = ', heat_source_t%unit_name() | 1 Error: Function 'heat_source_t' requires an argument list at (1)and LLVM Flang 23:
error: Semantic errors in 01_nopass_via_type.f90 01_nopass_via_type.f90:60:48: error: 'heat_source_t' is not an object and may not be used as the base of a component reference or type parameter inquiry -
The object before the
%must be a variable, not an arbitrary expression.(t + u)%to_celsius()andmake_tracked(8)%idare syntax errors; assign the value to a variable first, as the generic-bindings example below does. -
Binding names live in the type’s own name space (C775): a binding name cannot repeat inside one type, and it can coincide with the name of the procedure that implements it, as
temperature_atdoes. A binding name does not have to be accessible as a procedure name outside the module.
Inside the module, a binding can also be called with its procedure name as an ordinary procedure, passing the object
explicitly, call move_source(s, 0.5_dp). That bypasses dynamic dispatch and is rarely what a program wants; call
through the object.
Accessibility and information hiding
Accessibility of a binding follows the type definition (7.5.5). A binding is public unless the type definition contains
a binding-private statement, a private statement immediately after contains, in which case the default becomes
private; an explicit public or private attribute on a binding overrides the default. A private binding can be
called only inside the module that contains the type definition and inside the modules and submodules that are its
descendants. Note 2 of 7.5.5 states that the two private statements are independent: a private statement in the
component part (before contains) hides components and does not affect bindings, and the binding-private statement hides
bindings and does not affect components.
module counters
implicit none
private
public :: counter_t
type :: counter_t
private ! components are private (component part)
integer :: n = 0
contains
private ! binding-private-stmt: bindings are private by default
procedure, public :: increment ! explicitly public
procedure, public :: value => get_value
procedure :: reset ! private: usable only inside the module
procedure, public :: restart ! public wrapper that uses the private binding
end type counter_t
contains
subroutine increment(self)
class(counter_t), intent(inout) :: self
self%n = self%n + 1
end subroutine increment
integer function get_value(self)
class(counter_t), intent(in) :: self
get_value = self%n
end function get_value
subroutine reset(self)
class(counter_t), intent(inout) :: self
self%n = 0
end subroutine reset
subroutine restart(self)
class(counter_t), intent(inout) :: self
call self%reset() ! allowed: same module
end subroutine restart
end module counters
program binding_private
use counters
implicit none
type(counter_t) :: c
call c%increment()
call c%increment()
print '(a, i0)', 'value = ', c%value()
call c%restart()
print '(a, i0)', 'after restart = ', c%value()
end program binding_private
Output:
value = 2
after restart = 0
Source: J3/23-007r1: R747 binding-private-stmt, C772, 7.5.5 Type-bound procedures (accessibility and Note 2)
A private statement in the binding part is only allowed when the type is defined in the specification part of a module
(C772). Using what is private from outside is an error at compile time. This deliberately wrong program calls a private
binding and reads a private component of the same type; GNU Fortran 16.2 reports both:
21_bad_private_use.f90:23:14:
23 | call c%reset()
| 1
Error: 'reset' of 'counter_t' is PRIVATE at (1)
21_bad_private_use.f90:24:14:
24 | print *, c%n
| 1
Error: Component 'n' at (1) is a PRIVATE component of 'counter_t'
and LLVM Flang 23:
error: Semantic errors in 21_bad_private_use.f90
21_bad_private_use.f90:23:10: error: PRIVATE name 'reset' is accessible only within module 'counters'
call c%reset()
^^^^^
21_bad_private_use.f90:24:14: error: PRIVATE name 'n' is accessible only within module 'counters'
print *, c%n
^
The usual pattern is a public type with private components, public bindings as the interface of the type, and private
bindings for the specific procedures that only exist to implement a generic. Because the generic is public while
the specific bindings are private, a client can call x%put(1.0) but not x%a(1.0). C.3.3 in the standard uses the same
arrangement for the rational type, and notes one practical benefit: use rational_numbers, only: rational does not
block the type-bound operators, because they travel with the type, which is not true of an interface block whose generic
identifier is not imported.
Overriding and inheritance
A type extension inherits all components and all nonoverridden bindings of its parent (7.5.7.2). It overrides a binding by declaring a specific binding with the same binding name (7.5.7.3). An extension also contains the parent component, a component with the parent’s type and name, which can be used to call the parent’s version of an overridden binding.
module heat_sources
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, heat_source_t, pulsed_source_t
integer, parameter :: dp = real64
type :: heat_source_t
real(dp) :: x = 0.0_dp
real(dp) :: power = 0.0_dp
contains
procedure :: temperature_at => base_temperature_at
procedure, non_overridable :: label ! cannot be overridden in an extension
end type heat_source_t
type, extends(heat_source_t) :: pulsed_source_t
real(dp) :: duty = 0.5_dp ! fraction of the period the source is on
contains
procedure :: temperature_at => pulsed_temperature_at ! overrides the inherited binding
end type pulsed_source_t
contains
pure function base_temperature_at(self, xq) result(t)
class(heat_source_t), intent(in) :: self
real(dp), intent(in) :: xq
real(dp) :: t
t = self%power/(1.0_dp + abs(xq - self%x))
end function base_temperature_at
pure function pulsed_temperature_at(self, xq) result(t)
class(pulsed_source_t), intent(in) :: self ! the passed object may be narrowed
real(dp), intent(in) :: xq
real(dp) :: t
! reuse the parent's behaviour through the parent component
t = self%duty*self%heat_source_t%temperature_at(xq)
end function pulsed_temperature_at
function label(self) result(s)
class(heat_source_t), intent(in) :: self
character(len=:), allocatable :: s
s = 'source at x='
block
character(len=16) :: buf
write (buf, '(f0.2)') self%x
s = s//trim(buf)
end block
end function label
end module heat_sources
program override_demo
use heat_sources
implicit none
type(heat_source_t), target :: plain
type(pulsed_source_t), target :: pulsed
class(heat_source_t), pointer :: p
plain = heat_source_t(x=1.0_dp, power=100.0_dp)
pulsed = pulsed_source_t(x=1.0_dp, power=100.0_dp, duty=0.25_dp)
print '(a, f8.3)', 'plain at 3.0: ', plain%temperature_at(3.0_dp)
print '(a, f8.3)', 'pulsed at 3.0: ', pulsed%temperature_at(3.0_dp)
p => plain
print '(a, f8.3)', 'through class(heat_source_t) pointer, plain : ', p%temperature_at(3.0_dp)
p => pulsed
print '(a, f8.3)', 'through class(heat_source_t) pointer, pulsed: ', p%temperature_at(3.0_dp)
! the parent component is an ordinary object of the parent type
print '(a, f8.3)', 'pulsed%heat_source_t at 3.0: ', pulsed%heat_source_t%temperature_at(3.0_dp)
print '(2a)', 'label (inherited): ', pulsed%label()
end program override_demo
Output:
plain at 3.0: 33.333
pulsed at 3.0: 8.333
through class(heat_source_t) pointer, plain : 33.333
through class(heat_source_t) pointer, pulsed: 8.333
pulsed%heat_source_t at 3.0: 33.333
label (inherited): source at x=1.00
Flang 23 produced the same output. The third and fourth lines are the point of the example: the pointer is declared as
class(heat_source_t), but p%temperature_at runs the overriding procedure when the pointer targets a
pulsed_source_t. That is dynamic dispatch: the declared type of the object selects the binding name, and the dynamic
type selects the procedure. The fifth line shows the parent component, pulsed%heat_source_t, being used as an ordinary
object of the parent type. Inside the overriding procedure the same component, self%heat_source_t%temperature_at(xq),
calls the parent’s binding without dispatch, which is Fortran’s equivalent of super. This only works for bindings that
have an implementation; a deferred binding has none to call.
An overriding binding and the overridden one have to match closely (7.5.7.3). The conditions are:
| Condition | Detail |
|---|---|
Passed object |
either both have a passed-object dummy argument or neither does; the passed-object dummy arguments correspond by name and position, and only their type may differ (it is the extension in the overriding procedure) |
Dummy arguments |
the same number, and those that correspond by position have the same names and characteristics (type, kind, rank, |
Result |
both are subroutines, or both are functions with the same result characteristics |
|
if the overridden binding is |
Accessibility |
if the overridden binding is |
|
an overriding binding is |
|
a binding cannot override an inherited |
The compiler checks these when the extension is compiled. This module declares non_overridable in the parent and
overrides in the child. GNU Fortran 16.2 reports:
03_bad_nonoverridable.f90:9:13:
9 | procedure :: id => child_id
| 1
Error: 'id' at (1) overrides a procedure binding declared NON_OVERRIDABLE
and LLVM Flang 23:
error: Semantic errors in 03_bad_nonoverridable.f90
03_bad_nonoverridable.f90:9:18: error: Override of NON_OVERRIDABLE 'id' is not permitted
procedure :: id => child_id
^^
03_bad_nonoverridable.f90:5:35: Declaration of 'id'
procedure, non_overridable :: id
^^
A mismatch of characteristics is caught too. In the next module the overriding procedure declares its real dummy
argument as double precision where the overridden binding has default real. GNU Fortran 16.2 reports:
05_bad_override_type.f90:9:13:
9 | procedure :: scale => child_scale
| 1
Error: Argument mismatch for the overriding procedure 'scale' at (1): Type mismatch in argument 'factor' (REAL(8)/REAL(4))
and LLVM Flang 23:
error: Semantic errors in 05_bad_override_type.f90
05_bad_override_type.f90:9:18: error: A type-bound procedure and its override must have compatible interfaces
procedure :: scale => child_scale
^^^^^
05_bad_override_type.f90:5:18: Declaration of 'scale'
procedure :: scale
^^^^^
The dummy-argument name is part of the interface too, because a call can use keywords. In this module the override
names its dummy argument f where the parent calls it factor. GNU Fortran 16.2 rejects it:
04_bad_override.f90:9:13:
9 | procedure :: scale => child_scale
| 1
Error: Dummy argument 'f' of 'scale' at (1) should be named 'factor' as to match the corresponding argument of the overridden procedure
LLVM Flang 23 accepts the same module without any diagnostic (compiled with flang-23 -c, exit status 0), although 7.5.7.3 requires
the names to be identical. This is a case where one of the two compilers is more permissive than the standard, which
is the reason to keep the argument names of overridden bindings identical anyway.
Designing for overriding
A base type is a contract. The parts that follow from the rules above, and a few conventions that make hierarchies easier to maintain:
-
Put the passed object at the front, name it the same way everywhere (
selfin these pages), and useintenton it.class(base), intent(in)for observers andintent(inout)for mutators. Overriding requires the sameintenton every other dummy argument as well. -
Template method. Write the algorithm once in a concrete binding of the base type and let it call deferred or overridable bindings for the steps that vary.
solver_t%runin the example below callsself%stepin a loop and never needs to know which solver it is. The extensions implementstep; nobody overridesrun. -
Close what must not change with
non_overridable: invariants, bookkeeping bindings and anything the base type relies on to stay correct. A non-overridable binding can still be inherited and called by every extension. -
Keep specific bindings of a generic private, so that the public surface is the generic name and the set of specific procedures can change freely.
-
Call the parent version through the parent component (
self%parent_t%binding(...)) when an override extends rather than replaces behaviour. If every override has to remember to call the parent, make the parent’s binding the template method and give it a smaller overridable hook instead. -
Return polymorphic objects through allocatable results. A function result can be polymorphic only if it is allocatable or a pointer. A factory returning
class(base), allocatable, likemake_solverbelow, is the idiom for creating an object whose dynamic type is chosen at run time. -
Prefer composition when no substitution is intended. Embedding a component of another type is simpler than extension and does not commit the type to the parent’s bindings; extension is for "is a" relationships in which the extension can stand in for the parent.
Generic bindings
A generic binding groups specific bindings under one name or one operator. It is the type-bound form of the generic
interface of
Interfaces, Generics and Operator Overloading.
The generic-spec can be a generic name, operator(op), assignment(=), or a defined input/output specification
(read(formatted), read(unformatted), write(formatted), write(unformatted)) (R751).
module temperatures
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, temperature_t
integer, parameter :: dp = real64
!> A temperature stored in kelvin, with generic bindings for a named
!> method, operators and assignment.
type :: temperature_t
private
real(dp) :: kelvin = 0.0_dp
contains
procedure :: to_celsius
procedure, private :: set_kelvin, set_celsius ! specific bindings
procedure, private :: add_temp, add_real
procedure, private, pass(b) :: real_add
procedure, private :: assign_real, less_than
generic :: set => set_kelvin, set_celsius ! generic name, called as t%set(...)
generic :: operator(+) => add_temp, add_real, real_add ! type-bound defined operator
generic :: operator(<) => less_than
generic :: assignment(=) => assign_real ! t = 300.0_dp
end type temperature_t
contains
elemental function to_celsius(self) result(c)
class(temperature_t), intent(in) :: self
real(dp) :: c
c = self%kelvin - 273.15_dp
end function to_celsius
subroutine set_kelvin(self, kelvin)
class(temperature_t), intent(inout) :: self
real(dp), intent(in) :: kelvin
self%kelvin = kelvin
end subroutine set_kelvin
subroutine set_celsius(self, celsius)
class(temperature_t), intent(inout) :: self
integer, intent(in) :: celsius ! distinguishes it from set_kelvin
self%kelvin = real(celsius, dp) + 273.15_dp
end subroutine set_celsius
elemental function add_temp(a, b) result(r)
class(temperature_t), intent(in) :: a
class(temperature_t), intent(in) :: b
type(temperature_t) :: r
r%kelvin = a%kelvin + b%kelvin
end function add_temp
elemental function add_real(a, b) result(r)
class(temperature_t), intent(in) :: a
real(dp), intent(in) :: b
type(temperature_t) :: r
r%kelvin = a%kelvin + b
end function add_real
elemental function real_add(a, b) result(r) ! real + temperature: object is the 2nd operand
real(dp), intent(in) :: a
class(temperature_t), intent(in) :: b
type(temperature_t) :: r
r%kelvin = a + b%kelvin
end function real_add
elemental subroutine assign_real(lhs, rhs)
class(temperature_t), intent(inout) :: lhs
real(dp), intent(in) :: rhs
lhs%kelvin = rhs
end subroutine assign_real
elemental function less_than(a, b) result(r)
class(temperature_t), intent(in) :: a, b
logical :: r
r = a%kelvin < b%kelvin
end function less_than
end module temperatures
program generic_bindings
use temperatures
implicit none
type(temperature_t) :: t, u, r, v(3)
call t%set(300.0_dp) ! resolves to set_kelvin
call u%set(20) ! resolves to set_celsius
print '(a, f7.2, a, f7.2)', 't = ', t%to_celsius(), ' C, u = ', u%to_celsius()
r = t + u ! an expression is not a data-ref: store it before using %
print '(a, f7.2)', 't + u = ', r%to_celsius()
r = t + 1.5_dp
print '(a, f7.2)', 't + 1.5 = ', r%to_celsius()
r = 1.5_dp + t
print '(a, f7.2)', '1.5 + t = ', r%to_celsius()
print '(a, l1)', 'u < t = ', u < t
v = [250.0_dp, 275.0_dp, 300.0_dp] ! elemental defined assignment on an array
v = v + 10.0_dp ! elemental defined operator on an array
print '(a, 3f8.2)', 'v (Celsius) = ', v%to_celsius()
end program generic_bindings
Output:
t = 26.85 C, u = 20.00
t + u = 320.00
t + 1.5 = 28.35
1.5 + t = 28.35
u < t = T
v (Celsius) = -13.15 11.85 36.85
LLVM Flang 23 produced the same output. (Adding two temperatures adds their kelvin values, which is why t + u is 320 C; the
example is about the mechanics.) What to take from it:
-
Named generic.
generic :: set => set_kelvin, set_celsiusis called ascall t%set(...); the specific is chosen by the argument types (real(dp)orinteger) as for any generic. The generic name can differ from every specific name but it cannot be the same as a specific binding name of the same type (the standard’s Note 1 in 7.5.5, and the diagnostic below). -
Defined operators. The specifics of
operator(+)must have the interface that 15.4.3.4.2 requires: functions whose one or two dummy arguments areintent(in). The object can be either operand.add_tempandadd_realhave the object as the left operand;real_addhaspass(b), so the object is the right operand and1.5_dp + tworks as well ast + 1.5_dp. Every specific binding of an operator has a passed-object dummy argument (C779), so a type-bound operator always has at least one operand of the type and cannot definereal + real. -
Defined assignment. The specific is a subroutine with exactly two arguments; the first is the left side and has
intent(out)orintent(inout), the second the right side withintent(in)(15.4.3.4.3). It is used instead of the intrinsic assignment of the type for the combinations it covers. Intrinsic assignment oftemperature_t = temperature_tremains available because the generic does not cover it. A defined assignment where both sides are the same type replaces the intrinsic one, which is the usual way to implement deep copying (see Finalization). -
Elemental specifics.
elementalspecifics work on arrays, asv = v + 10.0_dpandv%to_celsius()show. When a generic reference is consistent with both a nonelemental and an elemental specific, the nonelemental one wins (15.5.6). -
Dispatch on the declared type. A generic binding is resolved from the declared type of the object, and then the specific procedure is the one bound to that specific binding name in the dynamic type (15.5.6). So generic bindings are overridden by overriding the specific bindings behind them.
Specifics of one generic must be distinguishable (C1511 to C1514 in 15.4.3.4.5): for a named generic the rules compare the dummy arguments other than the passed object, by type, kind, rank, or by keyword, as in an ordinary generic interface. This deliberately wrong type has two bindings that differ only in the name of the dummy argument and are used by the same position. GNU Fortran 16.2 reports:
07_bad_generic_ambig.f90:6:21:
6 | generic :: put => a, b
| 1
Error: 'a' and 'b' for GENERIC 'put' at (1) are ambiguous
and LLVM Flang 23:
error: Semantic errors in 07_bad_generic_ambig.f90
07_bad_generic_ambig.f90:6:16: error: Generic 'put' may not have specific procedures 'a' and 'b' as their interfaces are not distinguishable
generic :: put => a, b
^^^
07_bad_generic_ambig.f90:5:27: Declaration of 'a'
procedure, private :: a, b
^
07_bad_generic_ambig.f90:5:30: Declaration of 'b'
procedure, private :: a, b
^
Because a generic name cannot equal a specific binding name of the same type, this is also an error:
25_bad_generic_name.f90:6:22:
6 | generic :: put => put_int ! generic with the same name: not allowed
| 1
Error: There's already a non-generic procedure with binding name 'put' for the derived type 't' at (1)
reported by GNU Fortran 16.2, and by LLVM Flang 23 as:
error: Semantic errors in 25_bad_generic_name.f90
25_bad_generic_name.f90:6:16: error: Type parameter, component, or procedure binding 'put' already defined in this type
generic :: put => put_int ! generic with the same name: not allowed
^^^
25_bad_generic_name.f90:5:18: Previous definition of 'put'
procedure :: put ! specific binding named put
^^^
Each additional generic statement with the same generic-spec adds to the generic interface, also in a type extension: a
generic binding in the extension that has the same generic-spec as an inherited one extends it, provided the
combined set still satisfies 15.4.3.4.5 (7.5.7.2).
module fields
implicit none
private
public :: field_t, labelled_field_t
type :: field_t
real :: value = 0.0
contains
procedure, private :: set_real
generic :: set => set_real
end type field_t
type, extends(field_t) :: labelled_field_t
character(len=8) :: label = ''
contains
procedure, private :: set_label
generic :: set => set_label ! extends the inherited generic interface
end type labelled_field_t
contains
subroutine set_real(self, x)
class(field_t), intent(inout) :: self
real, intent(in) :: x
self%value = x
end subroutine set_real
subroutine set_label(self, text)
class(labelled_field_t), intent(inout) :: self
character(len=*), intent(in) :: text
self%label = text
end subroutine set_label
end module fields
program extend_generic
use fields
implicit none
type(labelled_field_t) :: f
call f%set(2.5) ! inherited specific binding
call f%set('rod') ! added in the extension
print '(a, f4.1, 2a)', 'value=', f%value, ' label=', f%label
end program extend_generic
Output:
value= 2.5 label=rod
Both compilers print the same line; label is padded to eight characters by the declaration. The private specifics
are still reachable through the public generic, and the extension did not have to repeat the inherited specific.
Defined input/output as a generic binding
A generic :: write(formatted) => name binding makes print, write and list-directed output of an object of the type
call name, with the interface of 12.6.4.8: a dtv argument of type class(t), the unit, the iotype string (LISTDIRECTED,
NAMELIST, or DT followed by the characters of the edit descriptor), the v_list integer array from the parentheses of
the DT edit descriptor, and iostat and iomsg. The binding gives the type its own text representation without a
separate interface block. The details of the child data transfer statement (what a defined output procedure may do) belong to
Derived-Type and Asynchronous I/O; the example shows only the
binding.
module readings
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, reading_t
integer, parameter :: dp = real64
type :: reading_t
real(dp) :: celsius = 0.0_dp
integer :: sensor = 0
contains
procedure, private :: write_reading
generic :: write(formatted) => write_reading ! defined output for reading_t
end type reading_t
contains
subroutine write_reading(dtv, unit, iotype, v_list, iostat, iomsg)
class(reading_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
character(len=32) :: fmt
integer :: decimals
decimals = 1
if (size(v_list) >= 1) decimals = v_list(1)
write (fmt, '(a, i0, a)') '(a, i0, a, f0.', decimals, ', a)'
select case (iotype)
case ('LISTDIRECTED')
write (unit, '(a, i0, a, f0.1, a)', iostat=iostat, iomsg=iomsg) &
'sensor ', dtv%sensor, ' reads ', dtv%celsius, ' C'
case default ! 'NAMELIST' or 'DT' followed by the string
write (unit, fmt, iostat=iostat, iomsg=iomsg) 'sensor ', dtv%sensor, ' reads ', dtv%celsius, ' C'
end select
end subroutine write_reading
end module readings
program dtio_binding
use readings
implicit none
type(reading_t) :: r
r = reading_t(celsius=21.456_dp, sensor=7)
print *, r ! list-directed: iotype 'LISTDIRECTED', empty v_list
write (*, '(DT"short"(3))') r ! iotype 'DTshort', v_list = [3]
write (*, '(DT(1))') r ! iotype 'DT', v_list = [1]
end program dtio_binding
Output with GNU Fortran 16.2:
sensor 7 reads 21.5 C
sensor 7 reads 21.456 C
sensor 7 reads 21.5 C
LLVM Flang 23 prints the same three lines except that the first line (the list-directed print *) has no leading blank,
whereas GNU Fortran starts a list-directed record with one. Both are conforming; list-directed output has no fixed layout
(and the leading blank is the processor’s choice for the start of the record, so do not align by it). The DT"short"(3) edit descriptor gives iotype
DTshort and v_list = [3], which the procedure uses as the number of decimals.
Abstract types and deferred bindings
An abstract type cannot be instantiated; its purpose is to be extended (7.5.7.1). It defines the interface of a family of
types. A deferred binding is declared in the abstract type with an interface only, using an abstract interface
(abstract interface, 15.4.1) for the characteristics, and every non-abstract extension has to bind a procedure to it.
module rod_solvers
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, solver_t, jacobi_rod_t, ftcs_rod_t, make_solver
integer, parameter :: dp = real64
!> Abstract solver for a 1-D rod: owns the state, defers the update rule.
type, abstract :: solver_t
real(dp), allocatable :: u(:)
integer :: steps = 0
contains
procedure(step_iface), deferred :: step ! no body here, every extension must supply one
procedure :: run ! concrete: calls the deferred binding
procedure :: name => solver_name ! concrete default, may be overridden
end type solver_t
abstract interface
subroutine step_iface(self, residual)
import :: solver_t, dp
class(solver_t), intent(inout) :: self
real(dp), intent(out) :: residual
end subroutine step_iface
end interface
type, extends(solver_t) :: jacobi_rod_t
contains
procedure :: step => jacobi_step
procedure :: name => jacobi_name
end type jacobi_rod_t
type, extends(solver_t) :: ftcs_rod_t
real(dp) :: r = 0.4_dp ! alpha*dt/dx**2, stable for r <= 0.5
contains
procedure :: step => ftcs_step
end type ftcs_rod_t
contains
subroutine run(self, max_steps, tol, residual)
class(solver_t), intent(inout) :: self
integer, intent(in) :: max_steps
real(dp), intent(in) :: tol
real(dp), intent(out) :: residual
integer :: n
residual = huge(1.0_dp)
do n = 1, max_steps
call self%step(residual) ! dynamic dispatch on the extension
self%steps = self%steps + 1
if (residual < tol) exit
end do
end subroutine run
function solver_name(self) result(s)
class(solver_t), intent(in) :: self
character(len=:), allocatable :: s
s = 'generic solver'
end function solver_name
function jacobi_name(self) result(s)
class(jacobi_rod_t), intent(in) :: self
character(len=:), allocatable :: s
s = 'Jacobi relaxation'
end function jacobi_name
subroutine jacobi_step(self, residual)
class(jacobi_rod_t), intent(inout) :: self
real(dp), intent(out) :: residual
real(dp), allocatable :: unew(:)
integer :: n
n = size(self%u)
unew = self%u
unew(2:n - 1) = 0.5_dp*(self%u(1:n - 2) + self%u(3:n))
residual = maxval(abs(unew - self%u))
self%u = unew
end subroutine jacobi_step
subroutine ftcs_step(self, residual)
class(ftcs_rod_t), intent(inout) :: self
real(dp), intent(out) :: residual
real(dp), allocatable :: unew(:)
integer :: n
n = size(self%u)
unew = self%u
unew(2:n - 1) = self%u(2:n - 1) + self%r*(self%u(1:n - 2) - 2.0_dp*self%u(2:n - 1) + self%u(3:n))
residual = maxval(abs(unew - self%u))
self%u = unew
end subroutine ftcs_step
!> Factory: the result is polymorphic, its dynamic type chosen at run time.
function make_solver(kind, n) result(s)
character(len=*), intent(in) :: kind
integer, intent(in) :: n
class(solver_t), allocatable :: s
select case (kind)
case ('jacobi')
allocate (jacobi_rod_t :: s)
case ('ftcs')
allocate (ftcs_rod_t :: s)
case default
error stop 'make_solver: unknown kind'
end select
allocate (s%u(n), source=0.0_dp)
s%u(1) = 100.0_dp ! hot left end, cold right end
end function make_solver
end module rod_solvers
program abstract_demo
use rod_solvers
implicit none
class(solver_t), allocatable :: s
character(len=6), parameter :: kinds(2) = [character(len=6) :: 'jacobi', 'ftcs']
real(dp) :: res
integer :: k
do k = 1, size(kinds)
s = make_solver(trim(kinds(k)), 11)
call s%run(max_steps=5000, tol=1.0e-6_dp, residual=res)
print '(2a, i0, a, es9.2, a, f6.2)', s%name(), ': steps=', s%steps, ' residual=', res, &
' mid=', s%u(6)
end do
end program abstract_demo
Output:
Jacobi relaxation: steps=312 residual= 9.80E-07 mid= 50.00
generic solver: steps=370 residual= 9.82E-07 mid= 50.00
LLVM Flang 23 produced the same output. Points to note:
-
solver_tisabstract, so the declarationtype(solver_t) :: sis illegal, butclass(solver_t), allocatable :: sis fine, and the dynamic type ofsis always a non-abstract extension. The dynamic type of an object cannot be abstract, so a deferred binding can never actually be invoked (Note 2 of 7.5.7.1). -
The abstract interface needs
import :: solver_t, dpbecause an interface body has no host association. The passed object of a deferred binding isclass(solver_t), the abstract type itself. -
runis the template method: concrete, written once, and dispatching to the extension’sstep. -
nameis concrete in the abstract type (the "default implementation"), overridden byjacobi_rod_tand inherited as is byftcs_rod_t. Hence the second line saysgeneric solver. -
An abstract type can have components and concrete bindings; an abstract type can also extend a non-abstract type, and an extension of an abstract type does not have to be abstract if it implements all deferred bindings (Note 2 of 7.5.7.1).
-
make_solveris the factory. The function result isclass(solver_t), allocatableand theallocate (jacobi_rod_t :: s)chooses the dynamic type;s = make_solver(...)in the main program then moves it into a polymorphic variable.
The compiler checks the obligations. Declaring an object of abstract type, or allocating one without saying which type, is an error. GNU Fortran 16.2 reports:
11_bad_abstract_instance.f90:19:20:
19 | type(shape_t) :: s ! error: an object of abstract type
| 1
Error: 's' at (1) is of the ABSTRACT type 'shape_t'
11_bad_abstract_instance.f90:21:13:
21 | allocate (t) ! error: allocation without a type-spec
| 1
Error: Allocating t of ABSTRACT base type at (1) requires a type-spec or source-expr
and LLVM Flang 23:
error: Semantic errors in 11_bad_abstract_instance.f90
11_bad_abstract_instance.f90:19:3: error: ABSTRACT derived type may not be used here
type(shape_t) :: s ! error: an object of abstract type
^^^^^^^^^^^^^^^^^^
11_bad_abstract_instance.f90:21:13: error: Either type-spec or source-expr must appear in ALLOCATE when allocatable object is of abstract type
allocate (t) ! error: allocation without a type-spec
^
A non-abstract extension that forgets one of the deferred bindings is an error too. In the module that was compiled, square_t
implements area but not perimeter. GNU Fortran 16.2 printed this diagnostic three times (once per use of the type in
the module), which is shown once:
12_bad_missing_deferred.f90:15:83:
15 | type, extends(shape_t) :: square_t ! not abstract, but only implements area
| 1
Error: Derived-type 'square_t' declared at (1) must be ABSTRACT because 'perimeter' is DEFERRED and not overridden
and LLVM Flang 23:
error: Semantic errors in 12_bad_missing_deferred.f90
12_bad_missing_deferred.f90:15:29: error: Non-ABSTRACT extension of ABSTRACT derived type 'shape_t' lacks a binding for DEFERRED procedure 'perimeter'
type, extends(shape_t) :: square_t ! not abstract, but only implements area
^^^^^^^^
12_bad_missing_deferred.f90:6:40: Declaration of 'perimeter'
procedure(area_iface), deferred :: perimeter
^^^^^^^^^
deferred is only allowed in an abstract type. A type that forgets the abstract keyword is reported by
GNU Fortran 16.2:
13_bad_deferred_nonabstract.f90:5:43:
5 | procedure(step_iface), deferred :: step
| 1
Error: Type 'plain_t' containing DEFERRED binding at (1) is not ABSTRACT
and by LLVM Flang 23:
error: Semantic errors in 13_bad_deferred_nonabstract.f90
13_bad_deferred_nonabstract.f90:5:40: error: Procedure bound to non-ABSTRACT derived type 'plain_t' may not be DEFERRED
procedure(step_iface), deferred :: step
^^^^
13_bad_deferred_nonabstract.f90:3:11: Declaration of 'plain_t'
type :: plain_t ! missing the ABSTRACT attribute
^^^^^^^
The real heatflow hierarchy
The heatflow_solver module of the running example has the same shape as rod_solvers, with solver_t holding a
grid_t, the boundary condition and a step counter, and jacobi_solver_t and ftcs_solver_t as extensions whose step
implementations live in submodules (see
Modules and Submodules). The following program stores
different solvers in a heterogeneous container: all the elements of a class(solver_t) array have the same dynamic type, so a
collection of different solvers uses a wrapper type with a polymorphic allocatable component and an array of wrappers.
program heatflow_dispatch
use heatflow_kinds, only: dp
use heatflow_boundary, only: dirichlet
use heatflow_grid, only: grid_t, make_grid
use heatflow_solver, only: solver_t, make_solver
implicit none
type :: solver_box ! a heterogeneous container: one slot per solver
class(solver_t), allocatable :: s
end type solver_box
type(grid_t) :: g
type(solver_box) :: boxes(2)
character(len=6), parameter :: kinds(2) = [character(len=6) :: 'jacobi', 'ftcs']
real(dp) :: res
integer :: k
g = make_grid(21, 21, 1.0_dp, 1.0_dp, initial=0.0_dp)
call g%set_hot_spot(100.0_dp)
do k = 1, 2
boxes(k)%s = make_solver(trim(kinds(k)), g, dirichlet, 0.0_dp, 1.0_dp, 1.0e-4_dp)
call boxes(k)%s%run(200, 1.0e-12_dp, res) ! run is inherited; step is dispatched
print '(a, i0, a, es10.3, a, f8.3)', trim(kinds(k))//': steps=', boxes(k)%s%steps, &
' residual=', res, ' max=', boxes(k)%s%grid%max_temperature()
end do
end program heatflow_dispatch
Output (built against the heatflow modules, with the compiler’s own module files for each compiler):
jacobi: steps=200 residual= 1.679E-01 max= 0.168
ftcs: steps=200 residual= 5.113E-03 max= 1.006
LLVM Flang 23 produced the same two lines. run is the inherited concrete binding of solver_t; each call dispatches
to the step of the dynamic type, and boxes(k)%s%grid%max_temperature() calls a binding of the grid_t component.
The Jacobi iteration relaxes the interior towards the zero boundary values, whereas the explicit FTCS scheme advances the
heat equation in time, which is why their maxima differ after the same number of steps.
Finalization
A final subroutine is declared with the final statement in the binding part of a type (R753). The processor calls it
when an object of the type is finalized, which happens for the events listed below. The jacobi_solver_t of the
running example (an excerpt of the compiled heatflow source) uses it to release its work array:
type, extends(solver_t) :: jacobi_solver_t
real(dp), allocatable :: work(:, :)
contains
procedure :: step => jacobi_step
final :: jacobi_finalize
end type jacobi_solver_t
The rules on the subroutine itself (C791 to C794, 7.5.6.1):
-
It is a module procedure with exactly one dummy argument, which is nonoptional, noncoarray, nonpointer, nonallocatable, nonpolymorphic (
type(t), notclass(t)), has assumed length type parameters, and does not haveintent(out)orvalue. Typically it istype(t), intent(inout). -
A type can have several final subroutines, but no two for the same kind parameters and rank (C793). That allows one for scalars and one for each rank that needs separate handling, or one
elementalsubroutine for all ranks. -
Final subroutines are not inherited and cannot be overridden: a final subroutine of the parent type is called after the extension’s own (Note 2 of 7.5.6.1). They are effectively always accessible, whatever the accessibility of the type, its other bindings or the subroutine name (Note 1).
-
The two common mistakes are reported at compile time. A final dummy argument with
intent(out)is reported by GNU Fortran 16.2 as:14_bad_final_intent_out.f90:8:18: 8 | subroutine fin(x) | 1 Error: Argument of FINAL procedure at (1) must not be INTENT(OUT)and by LLVM Flang 23 as:
error: Semantic errors in 14_bad_final_intent_out.f90 14_bad_final_intent_out.f90:5:14: error: FINAL subroutine 'fin' of derived type 't' must not have a dummy argument with INTENT(OUT) final :: fin ^^^ 14_bad_final_intent_out.f90:9:29: Declaration of 'x' type(t), intent(out) :: x ! error: INTENT(OUT) is not allowed on a final dummy ^and a polymorphic dummy argument by GNU Fortran 16.2 as:
15_bad_final_class.f90:8:18: 8 | subroutine fin(x) | 1 Error: Argument of FINAL procedure at (1) must be of type 't'and by LLVM Flang 23 as:
error: Semantic errors in 15_bad_final_class.f90 15_bad_final_class.f90:5:14: error: FINAL subroutine 'fin' of derived type 't' must not have a polymorphic dummy argument final :: fin ^^^ 15_bad_final_class.f90:9:32: Declaration of 'x' class(t), intent(inout) :: x ! error: the dummy argument shall be nonpolymorphic ^
A type is finalizable if it has a final subroutine or a nonpointer, nonallocatable component of finalizable type (7.5.6.1). A nonpointer object of finalizable type is a finalizable entity; pointers and allocatables are not finalizable themselves, but the objects they point to are finalized when they are deallocated.
The finalization process
When an entity is finalized, three steps are carried out in sequence (7.5.6.2):
For an array entity the first step picks the final subroutine whose dummy argument has the same kind and rank as the entity;
if there is none, an elemental final subroutine is called (once per element), and an assumed-rank one is called once with
the whole array. Whatever happens in the first step, the components of every element are finalized
separately in the second step. If several entities are finalized as the consequence of the same event, the order is
processor dependent.
When finalization happens
7.5.6.3 lists the events. Each row of the table is shown by one of the cases in the program that follows it.
| Case | Event | Clause |
|---|---|---|
1 |
a nonpointer, nonallocatable local object that is not a dummy argument or function result is finalized immediately before it would become undefined by a |
7.5.6.3, 19.6.6 |
2 |
an allocatable local is deallocated, and therefore finalized, in the same circumstances |
7.5.6.3 |
3 |
a nonpointer, nonallocatable local variable of a |
7.5.6.3 |
4 |
intrinsic assignment: the variable is finalized after the right side is evaluated and before it is defined, unless it is an unallocated allocatable |
7.5.6.3, 10.2.1.3 |
5 |
an unallocated allocatable on the left of an intrinsic assignment has nothing to finalize; the right side may be a function result, which is finalized separately (see 8) |
7.5.6.3 |
6 |
|
7.5.6.3, 9.7.3 |
7 |
|
7.5.6.3, 9.7.3 |
8 |
a function result in an expression is finalized after the innermost executable construct that contains the reference |
7.5.6.3 |
9 |
an |
7.5.6.3 |
module tracked
implicit none
private
public :: tracked_t, make_tracked, id_of
type :: tracked_t
integer :: id = 0
contains
final :: finalize_tracked
end type tracked_t
contains
subroutine finalize_tracked(self)
type(tracked_t), intent(inout) :: self
print '(a, i0)', ' finalize id=', self%id
end subroutine finalize_tracked
function make_tracked(id) result(t)
integer, intent(in) :: id
type(tracked_t) :: t
t%id = id
end function make_tracked
pure integer function id_of(t)
type(tracked_t), intent(in) :: t
id_of = t%id
end function id_of
end module tracked
subroutine local_variable()
use tracked
implicit none
type(tracked_t) :: a
a%id = 1
print '(a)', ' body of local_variable'
end subroutine local_variable
subroutine local_allocatable()
use tracked
implicit none
type(tracked_t), allocatable :: a
allocate (a)
a%id = 2
print '(a)', ' body of local_allocatable'
end subroutine local_allocatable
subroutine with_intent_out(t)
use tracked
implicit none
type(tracked_t), intent(out) :: t
print '(a)', ' inside with_intent_out (t was finalized on entry)'
t%id = 99
end subroutine with_intent_out
program final_when
use tracked
implicit none
type(tracked_t) :: x, y
type(tracked_t), allocatable :: z
type(tracked_t), pointer :: p
integer :: v
print '(a)', 'case 1: local variable, finalized at return'
call local_variable()
print '(a)', 'case 2: local allocatable, deallocated at return'
call local_allocatable()
print '(a)', 'case 3: block construct'
block
type(tracked_t) :: b
b%id = 3
print '(a)', ' inside block'
end block
print '(a)', ' after block'
print '(a)', 'case 4: intrinsic assignment finalizes the old value of the variable'
x%id = 4
y%id = 40
x = y
print '(a, i0)', ' x%id is now ', x%id
print '(a)', 'case 5: assignment to an unallocated allocatable finalizes nothing'
z = make_tracked(5)
print '(a, i0)', ' z%id is ', z%id
print '(a)', 'case 6: deallocate'
deallocate (z)
print '(a)', 'case 7: pointer deallocation finalizes the target'
allocate (p)
p%id = 7
deallocate (p)
print '(a)', 'case 8: function result, finalized after the statement'
v = id_of(make_tracked(8))
print '(a, i0)', ' v = ', v
print '(a)', 'case 9: intent(out) dummy'
call with_intent_out(x)
print '(a)', 'end of main program: x and y are not finalized'
end program final_when
Output:
case 1: local variable, finalized at return
body of local_variable
finalize id=1
case 2: local allocatable, deallocated at return
body of local_allocatable
finalize id=2
case 3: block construct
inside block
finalize id=3
after block
case 4: intrinsic assignment finalizes the old value of the variable
finalize id=4
x%id is now 40
case 5: assignment to an unallocated allocatable finalizes nothing
finalize id=5
z%id is 5
case 6: deallocate
finalize id=5
case 7: pointer deallocation finalizes the target
finalize id=7
case 8: function result, finalized after the statement
finalize id=8
v = 8
case 9: intent(out) dummy
finalize id=40
inside with_intent_out (t was finalized on entry)
end of main program: x and y are not finalized
LLVM Flang 23 produced identical output. In detail:
-
In case 4 the old value of
x(id 4) is finalized and then overwritten, andy, the source, is not finalized. -
In case 5 the line
finalize id=5is the finalization of the function result ofmake_tracked(5), which is a temporary, not ofz:zwas unallocated, so nothing happened to it. It isdeallocate (z)in case 6 that finalizesz(also id 5), because intrinsic assignment copied the value. -
In case 9 the object that is finalized is the actual argument’s previous value (id 40, from case 4), at the moment of the call, and the message from the procedure body follows. The finalization is treated as occurring in the invoked procedure; for
elementalprocedures anintent(out)argument is finalized only if a scalar or elemental final subroutine exists, whatever the rank of the actual argument (7.5.6.3). -
The two variables
xandyof the main program are never finalized (see the next section). -
Unreachable pointer targets: if an object allocated through a pointer becomes unreachable because every pointer to it has changed association, it is processor dependent whether it is finalized, and when (7.5.6.3). Do not depend on it; deallocate through a pointer explicitly.
Entities that are not finalized
Finalization is not guaranteed at the end of the program (7.5.6.4):
-
If image execution is terminated by an error (for example an allocation failure), or by a
stop,error stoporend programstatement, the entities that exist at that moment are not finalized. -
A
saved object is never finalized as the consequence of areturnorendstatement. Variables of the main program are implicitly saved, and a local variable that is initialized in its declaration also has thesaveattribute.
module guard
implicit none
type :: guard_t
character(len=8) :: name = ''
contains
final :: finalize_guard
end type guard_t
contains
subroutine finalize_guard(self)
type(guard_t), intent(inout) :: self
print '(2a)', ' finalize ', trim(self%name)
end subroutine finalize_guard
end module guard
subroutine uses_save()
use guard
implicit none
type(guard_t), save :: kept = guard_t('kept')
type(guard_t) :: fresh = guard_t('fresh') ! an initialized local variable is implicitly saved
print '(a)', ' inside uses_save'
end subroutine uses_save
subroutine stops_early()
use guard
implicit none
type(guard_t) :: local
local%name = 'local'
print '(a)', ' about to stop'
stop 'stopped'
end subroutine stops_early
program final_stop_save
use guard
implicit none
print '(a)', 'saved variables are never finalized by return:'
call uses_save()
print '(a)', 'stop terminates without finalizing:'
call stops_early()
end program final_stop_save
Standard output of both compilers:
Fortran STOP: stopped
saved variables are never finalized by return:
inside uses_save
stop terminates without finalizing:
about to stop
The first line of the combined output is the message of the stop statement, which is written to standard error and so
appears separately: GNU Fortran 16.2 prints STOP stopped and LLVM Flang 23 prints Fortran STOP: stopped. Neither compiler
calls the final subroutine of local, and neither calls those of kept and fresh. The consequence for a design: a
final subroutine is a safety net for resources that live inside a scope, not a place for work that must happen at
program exit. Flush and close files and write results explicitly.
Order, extension and arrays
module resources
implicit none
private
public :: buffer_t, tagged_buffer_t, probe_t
type :: probe_t ! a finalizable component type
integer :: n = 0
contains
final :: finalize_probe
end type probe_t
type :: buffer_t
integer :: id = 0
type(probe_t) :: probe ! finalizable nonallocatable component
contains
final :: finalize_buffer_scalar ! rank 0
final :: finalize_buffer_rank1 ! rank 1
end type buffer_t
type, extends(buffer_t) :: tagged_buffer_t
character(len=4) :: tag = 'none'
contains
final :: finalize_tagged
end type tagged_buffer_t
contains
subroutine finalize_probe(self)
type(probe_t), intent(inout) :: self
print '(a)', ' finalize_probe'
end subroutine finalize_probe
subroutine finalize_buffer_scalar(self)
type(buffer_t), intent(inout) :: self
print '(a, i0)', ' finalize_buffer_scalar id=', self%id
end subroutine finalize_buffer_scalar
subroutine finalize_buffer_rank1(self)
type(buffer_t), intent(inout) :: self(:)
print '(a, i0, a)', ' finalize_buffer_rank1 size=', size(self), ' (once for the whole array)'
end subroutine finalize_buffer_rank1
subroutine finalize_tagged(self)
type(tagged_buffer_t), intent(inout) :: self
print '(2a)', ' finalize_tagged tag=', self%tag
end subroutine finalize_tagged
end module resources
subroutine one_extended()
use resources
implicit none
type(tagged_buffer_t) :: t
t%id = 1
t%tag = 'abcd'
end subroutine one_extended
subroutine a_scalar()
use resources
implicit none
type(buffer_t) :: b
b%id = 2
end subroutine a_scalar
subroutine an_array()
use resources
implicit none
type(buffer_t) :: a(3)
a%id = [10, 20, 30]
end subroutine an_array
subroutine a_rank2_array()
use resources
implicit none
type(buffer_t) :: a(2, 2)
a%id = 0
end subroutine a_rank2_array
program final_order
implicit none
print '(a)', 'extended type: its own final, then the parent component'
call one_extended()
print '(a)', 'scalar'
call a_scalar()
print '(a)', 'rank-1 array: the rank-1 final subroutine matches'
call an_array()
print '(a)', 'rank-2 array: no matching final subroutine, only the components are finalized'
call a_rank2_array()
end program final_order
Output:
extended type: its own final, then the parent component
finalize_tagged tag=abcd
finalize_buffer_scalar id=1
finalize_probe
scalar
finalize_buffer_scalar id=2
finalize_probe
rank-1 array: the rank-1 final subroutine matches
finalize_buffer_rank1 size=3 (once for the whole array)
finalize_probe
finalize_probe
finalize_probe
rank-2 array: no matching final subroutine, only the components are finalized
finalize_probe
finalize_probe
finalize_probe
finalize_probe
LLVM Flang 23 produced identical output and, in addition, a compile-time warning for the last subroutine, where the
buffer_t array has rank 2 and the type has final subroutines only for ranks 0 and 1:
17_final_order_arrays.f90:74:21: warning: 'a' of derived type 'buffer_t' does not have a FINAL subroutine for its rank (2) [-Wfinal]
What the output shows:
-
For the extension
tagged_buffer_tits own final subroutine runs first, then, because the parent type is finalizable, the parent component is finalized with the parent’s rules: the scalar final subroutine ofbuffer_t, then itsprobecomponent. -
For the rank-1 array a single call with the whole array is made, and every element’s
probecomponent is finalized afterwards. -
For the rank-2 array no final subroutine matches the rank and none is
elemental, so nothing is called for the array itself but the finalizable components of the four elements are still finalized. Declaring oneelementalfinal subroutine covers every rank (and is called per element); an assumed-rank one covers every rank with one call.
Polymorphic objects use the dynamic type:
module shapes
implicit none
type :: base_t
integer :: id = 0
contains
final :: fin_base
end type base_t
type, extends(base_t) :: derived_t
contains
final :: fin_derived
end type derived_t
contains
subroutine fin_base(self)
type(base_t), intent(inout) :: self
print '(a, i0)', ' fin_base id=', self%id
end subroutine fin_base
subroutine fin_derived(self)
type(derived_t), intent(inout) :: self
print '(a, i0)', ' fin_derived id=', self%id
end subroutine fin_derived
subroutine take_out(x)
class(base_t), intent(out) :: x
print '(a)', ' inside take_out'
end subroutine take_out
end module shapes
program final_edge
use shapes
implicit none
class(base_t), allocatable :: p, q
type(derived_t) :: d
print '(a)', 'polymorphic allocatable, dynamic type derived_t, deallocate:'
allocate (derived_t :: p)
p%id = 1
deallocate (p)
print '(a)', 'move_alloc moves the object without finalizing it, deallocate then finalizes:'
allocate (derived_t :: p)
p%id = 2
call move_alloc(p, q)
print '(a, l1, l1)', ' allocated(p), allocated(q) = ', allocated(p), allocated(q)
deallocate (q)
print '(a)', 'intent(out) polymorphic dummy with dynamic type derived_t:'
d%id = 3
call take_out(d)
print '(a)', 'source= allocation copies, then the copy is finalized on deallocate:'
d%id = 4
allocate (p, source=d)
deallocate (p)
print '(a)', 'done'
end program final_edge
Output:
polymorphic allocatable, dynamic type derived_t, deallocate:
fin_derived id=1
fin_base id=1
move_alloc moves the object without finalizing it, deallocate then finalizes:
allocated(p), allocated(q) = FT
fin_derived id=2
fin_base id=2
intent(out) polymorphic dummy with dynamic type derived_t:
fin_derived id=3
fin_base id=3
inside take_out
source= allocation copies, then the copy is finalized on deallocate:
fin_derived id=4
fin_base id=4
done
Source: J3/23-007r1: 7.5.6.2 The finalization process, 7.5.6.3 When finalization occurs, 16.9.147 MOVE_ALLOC
Again, both compilers print the same lines. A deallocate of a class(base_t) entity whose dynamic type is
derived_t calls the final subroutine of derived_t and then, through the parent component, that of base_t.
move_alloc moves the allocation without finalizing it. An intent(out) polymorphic dummy argument is finalized
according to its dynamic type, and allocate (p, source=d) copies the object without finalizing d (only the copy is
finalized when it is deallocated).
Finalizers and copies: the rule of three
A final subroutine that releases a resource has a classic hazard. Intrinsic assignment copies a pointer component as a
pointer, so two objects share the target, and the first finalization frees the storage the other one still uses.
module owned
implicit none
private
public :: owner_t
type :: owner_t
real, pointer :: data(:) => null() ! owned storage
contains
final :: release
end type owner_t
contains
subroutine release(self)
type(owner_t), intent(inout) :: self
if (associated(self%data)) then
print '(a)', ' release: deallocating'
deallocate (self%data)
end if
end subroutine release
end module owned
program shallow_copy
use owned
implicit none
type(owner_t) :: a, b
allocate (a%data(10))
a%data = 1.0
b = a ! intrinsic assignment copies the pointer, not the data
print '(a, l1)', 'b%data aliases a%data: ', associated(b%data, a%data)
print '(a)', 'leaving the main program does not finalize a or b; leaving a subroutine would'
call leave_scope(a)
print '(a, l1)', 'a%data is still "associated": ', associated(a%data) ! stale: the storage is gone
deallocate (a%data) ! second deallocation of the same storage
contains
subroutine leave_scope(x)
type(owner_t), intent(in) :: x
type(owner_t) :: copy
copy = x ! shares x%data
print '(a)', 'returning: copy is finalized and frees the storage x still points to'
end subroutine leave_scope
end program shallow_copy
Output (standard output, then the runtime’s message, which is on standard error and is the same for both compilers apart from the backtrace):
b%data aliases a%data: T
leaving the main program does not finalize a or b; leaving a subroutine would
returning: copy is finalized and frees the storage x still points to
release: deallocating
a%data is still "associated": T
free(): double free detected in tcache 2
Program received signal SIGABRT: Process abort signal.
Both compilers aborted with exit status 134. The pointer a%data stays "associated" with storage that no longer exists;
associated cannot detect that (see
Pointers and Targets). Three fixes are possible, in order of
preference:
-
Do not own storage through pointers. Use
allocatablecomponents. Intrinsic assignment of a derived type with an allocatable component allocates and copies the component (a deep copy), and deallocation of an allocatable component at the end of the scope is automatic. No final subroutine is needed.jacobi_finalizein heatflow exists mostly to show the syntax and to release the work array early; a type whose only owned resources are allocatables does not need one. -
Define the assignment with a generic
assignment(=)binding so the copy is deep, as in the next example. -
Make the type non-copyable by convention, and pass it by reference.
module owned
implicit none
private
public :: owner_t
type :: owner_t
real, pointer :: data(:) => null()
contains
final :: release
procedure, private :: assign
generic :: assignment(=) => assign ! deep copy replaces the shallow default
end type owner_t
contains
subroutine release(self)
type(owner_t), intent(inout) :: self
if (associated(self%data)) then
print '(a)', ' release: deallocating'
deallocate (self%data)
end if
end subroutine release
subroutine assign(lhs, rhs)
class(owner_t), intent(inout) :: lhs
class(owner_t), intent(in) :: rhs
if (associated(lhs%data, rhs%data)) return ! self-assignment
if (associated(lhs%data)) deallocate (lhs%data)
if (associated(rhs%data)) then
allocate (lhs%data(size(rhs%data)))
lhs%data = rhs%data
end if
end subroutine assign
end module owned
program deep_copy
use owned
implicit none
type(owner_t) :: a
allocate (a%data(10))
a%data = 1.0
call leave_scope(a)
print '(a, l1, a, f4.1)', 'a%data still associated: ', associated(a%data), ' a%data(1) = ', a%data(1)
contains
subroutine leave_scope(x)
type(owner_t), intent(in) :: x
type(owner_t) :: copy
copy = x ! calls the defined assignment: copy owns its own storage
print '(a, l1)', 'copy aliases x: ', associated(copy%data, x%data)
end subroutine leave_scope
end program deep_copy
Output:
copy aliases x: F
release: deallocating
a%data still associated: T a%data(1) = 1.0
LLVM Flang 23 produced the same output. Here copy = x calls the defined assignment, so copy owns its own storage
(associated(copy%data, x%data) is F), and the single release message is the finalization of copy when leave_scope
returns, while a%data is still valid afterwards. The standard remarks (note to 7.5.6.3) that finalization used for storage
management often needs to be combined with defined assignment; the defined assignment must also handle self-assignment, as
assign does.
Notice that a defined assignment replaces intrinsic assignment, and with it the finalization of the left side that
intrinsic assignment performs: assign has to free the old storage itself, as it does with the deallocate (lhs%data) before
reallocating. A final subroutine is not called for the left side of a defined assignment.
Practical guidance
-
Reach for
finalonly when the type owns something the language does not release by itself: a file unit, a handle from a C library, memory obtained fromc_ptr, a pointer-allocated structure or a lock. Allocatable components and automatic objects are released by the language. -
If a type has a final subroutine that releases a resource, it needs a defined assignment (or has to be non-copyable), as above.
-
Make the final subroutine idempotent: test
allocated/associatedbefore releasing, so that a second call is harmless. -
Do not use
stoporerror stopas a way to run finalizers; they do not. -
Do not rely on finalization order between different objects or between components of one object; the standard leaves it processor dependent. Do rely on this: an extension’s final subroutine runs before its parent’s.
-
A final subroutine must not reference or define other entities that are finalized by the same event and have already been finalized (7.5.6.2).
-
Remember that function results are finalized too: a function that returns a finalizable
type(t)makes one finalization of the temporary per call.
Comparing the mechanisms
| Need | Type-bound procedure | Procedure pointer component | Generic interface block |
|---|---|---|---|
Call syntax |
|
|
|
Choice of implementation |
fixed per dynamic type; overridden by extensions |
per object, assigned at run time |
fixed at compile time by argument types |
Inheritance |
inherited, overridable |
inherited as a component, no override mechanism |
not part of the type |
Polymorphic dispatch |
yes |
only through the pointer’s current target |
only through |
Use it for |
the operations that define a type |
strategies and callbacks that vary per object |
operations on several types, or on types you do not own |
Procedure pointers and abstract interfaces are covered in Procedures, Functions and Subroutines.
Compiler behaviour
All examples in this page were built with GNU Fortran 16.2 and LLVM Flang 23.1.4 and the observable behaviour was the
same, except for the differences in the table. The other compilers (Intel ifx, NAG, Cray) were not available; their
manuals are linked from Compilers and Compiler Options.
| Topic | GNU Fortran 16.2 | LLVM Flang 23 |
|---|---|---|
Override with a different dummy-argument name |
error, as 7.5.7.3 requires |
accepted without a diagnostic |
Final subroutine missing for the rank of an array |
no diagnostic |
warning |
Leading blank on list-directed output through a defined output procedure |
one blank at the start of the record |
no leading blank |
Diagnostic for a missing deferred binding |
reported once per use of the type |
reported once |
Message printed by |
|
|
Everything else on this page, including the order and the number of final-subroutine calls in every case above, was identical. For the events that the standard calls processor dependent (order between objects, order between components, unreachable pointer targets) the output of one compiler is no guide for another, so test with every compiler a project supports.
Pitfalls
-
Forgetting
classon the passed object. A binding of an extensible type needsclass(t); withtype(t)the compiler rejects it (C765). Atype(t)passed object would also stop the override from receiving extensions. -
Narrowing the passed object in an override and assuming too much. The overriding procedure receives the extension (
class(child_t)), but the other dummy arguments must keep the base type. Inspect them withselect typewhen needed. -
Mixing up the declared and the dynamic type. The binding name is looked up in the declared type; the procedure comes from the dynamic type. A binding added only in an extension is not visible through a
class(base)variable. -
Calling a
nopassbinding through the type name. It needs an object. -
Expecting
(a + b)%method(). The base of a%is a variable, not an expression. -
Two specifics that differ only by argument name or by
intent. They are ambiguous (C1514). -
A generic binding named like a specific binding of the same type. Not allowed.
-
Overriding with other argument names or characteristics. Rejected by GNU Fortran, silently accepted by Flang 23, and wrong in both.
-
Giving a derived type a pointer component, a final subroutine and no defined assignment. The double free shown above.
-
Relying on finalization at program end, after
stop,error stopor forsaved and main-program variables. -
A final dummy argument with
intent(out),class(...)orvalue. Not allowed (C791). -
Expecting a final subroutine to be inherited or overridden. Neither happens; each type lists its own, and the parent’s are still called.
-
Instantiating an abstract type. Use
class(abstract_t), allocatablewith a type-spec orsource=.
Where the books are outdated
The two Fortran books consulted for this section (see the bibliography of the landing page) describe type-bound procedures, abstract types and finalization in their chapter on object-oriented programming. The concepts are unchanged in Fortran 2023; the differences are around them.
| Area | Outdated in the books, current approach |
|---|---|
Compiler support |
the main reference has no per-compiler support information and one old NAG compiler release as its only compiler string; the current support of |
Standard identity |
no ISO number or date for Fortran 2023 is given; the rules above are from ISO/IEC 1539-1:2023 (J3/23-007r1 final draft), clause 7.5 |
Build tools |
only |
Next revision |
templates and generic subprograms are only a future direction there; Fortran 2028 is at Committee Draft stage with them, and does not change type-bound procedures, abstract types or |