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

procedure [[, binding-attr-list] ::] binding-name [=> procedure-name]

declares a specific type-bound procedure; the procedure is a module procedure or an external procedure with an explicit interface (C774); without => procedure-name the procedure has the same name as the binding (R749, R750)

7.5.5

procedure(interface-name), binding-attr-list :: binding-name-list

declares bindings that have an interface but no body; deferred must be among the attributes, and deferred appears if and only if an interface name does (C788)

7.5.5

generic [, access-spec] :: generic-spec => binding-name-list

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

final [::] subroutine-name-list

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

pass or pass(arg-name)

the object is passed as the first dummy argument, or as the dummy argument called arg-name; this is the default

7.5.4.5

nopass

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

non_overridable

extensions cannot override the binding (C790); cannot be combined with deferred (C787)

7.5.5

deferred

the binding has no implementation in this type, only in its extensions; allowed only in an abstract type

7.5.5, 7.5.7.1

public, private

accessibility of the binding, public unless the type has a binding-level private statement

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

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 calling temperature_at with s as its first argument. The object does not appear in the argument list. For scaled_by the object is the second dummy argument, so s%scaled_by(2.0_dp) supplies 2.0_dp as 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 nopass binding 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() and make_tracked(8)%id are 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_at does. 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

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, intent, optional, and so on)

Result

both are subroutines, or both are functions with the same result characteristics

pure, simple, elemental

if the overridden binding is pure the overriding one is also pure, and likewise for simple; they are either both elemental or neither is

Accessibility

if the overridden binding is public, the overriding one is not private

deferred

an overriding binding is deferred only if the overridden one is (C789)

non_overridable

a binding cannot override an inherited non_overridable binding (C790)

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 (self in these pages), and use intent on it. class(base), intent(in) for observers and intent(inout) for mutators. Overriding requires the same intent on 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%run in the example below calls self%step in a loop and never needs to know which solver it is. The extensions implement step; nobody overrides run.

  • 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, like make_solver below, 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_celsius is called as call t%set(...); the specific is chosen by the argument types (real(dp) or integer) 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 are intent(in). The object can be either operand. add_temp and add_real have the object as the left operand; real_add has pass(b), so the object is the right operand and 1.5_dp + t works as well as t + 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 define real + real.

  • Defined assignment. The specific is a subroutine with exactly two arguments; the first is the left side and has intent(out) or intent(inout), the second the right side with intent(in) (15.4.3.4.3). It is used instead of the intrinsic assignment of the type for the combinations it covers. Intrinsic assignment of temperature_t = temperature_t remains 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. elemental specifics work on arrays, as v = v + 10.0_dp and v%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_t is abstract, so the declaration type(solver_t) :: s is illegal, but class(solver_t), allocatable :: s is fine, and the dynamic type of s is 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, dp because an interface body has no host association. The passed object of a deferred binding is class(solver_t), the abstract type itself.

  • run is the template method: concrete, written once, and dispatching to the extension’s step.

  • name is concrete in the abstract type (the "default implementation"), overridden by jacobi_rod_t and inherited as is by ftcs_rod_t. Hence the second line says generic 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_solver is the factory. The function result is class(solver_t), allocatable and the allocate (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), not class(t)), has assumed length type parameters, and does not have intent(out) or value. Typically it is type(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 elemental subroutine 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):

flowchart TD A[finalize entity of dynamic type T] --> B{T has a final subroutine matching kind and rank?} B -->|yes| C[call it] B -->|no| D{elemental or assumed-rank final subroutine matches?} D -->|yes| C D -->|no| E[no call at this step] C --> F[finalize the nonallocatable finalizable components in processor-dependent order] E --> F F --> G{T is an extension and its parent type is finalizable?} G -->|yes| H[finalize the parent component same three steps, applied to the parent type] G -->|no| I[done] H --> I

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 return or end statement

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 block construct is finalized when the construct terminates

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

deallocate of an allocatable entity finalizes it

7.5.6.3, 9.7.3

7

deallocate of a pointer finalizes its target

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 intent(out) nonpointer, nonallocatable dummy argument is finalized when the procedure is invoked, before it becomes undefined

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, and y, the source, is not finalized.

  • In case 5 the line finalize id=5 is the finalization of the function result of make_tracked(5), which is a temporary, not of z: z was unallocated, so nothing happened to it. It is deallocate (z) in case 6 that finalizes z (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 elemental procedures an intent(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 x and y of 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 stop or end program statement, the entities that exist at that moment are not finalized.

  • A saved object is never finalized as the consequence of a return or end statement. Variables of the main program are implicitly saved, and a local variable that is initialized in its declaration also has the save attribute.

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_t its 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 of buffer_t, then its probe component.

  • For the rank-1 array a single call with the whole array is made, and every element’s probe component 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 one elemental final 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

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:

  1. Do not own storage through pointers. Use allocatable components. 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_finalize in 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.

  2. Define the assignment with a generic assignment(=) binding so the copy is deep, as in the next example.

  3. 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 final only when the type owns something the language does not release by itself: a file unit, a handle from a C library, memory obtained from c_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/associated before releasing, so that a second call is harmless.

  • Do not use stop or error stop as 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

obj%op(args); object passed implicitly

obj%fp(args); passed object only with pass

op(obj, args)

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 select type in the procedure

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 -Wfinal, printed without any option

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 stop 'text'

STOP text

Fortran STOP: text

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 class on the passed object. A binding of an extensible type needs class(t); with type(t) the compiler rejects it (C765). A type(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 with select type when 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 nopass binding 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 stop or for saved and main-program variables.

  • A final dummy argument with intent(out), class(...) or value. 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), allocatable with a type-spec or source=.

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 final, deferred bindings and generic bindings is shown in the table of the history page, and the examples on this page were run on GNU Fortran 16.2 and LLVM Flang 23

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 make recompilation cascades are discussed; the type-bound procedure examples here are modules, whose .mod files and build order are handled by fpm, CMake and Meson, see Building with Make, CMake and Meson

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 final in ways that affect this page