Fortran inheritance and polymorphism
last modified November 14, 2026
A derived type describes one kind of value. Real programs often have several
kinds that share most of their data: a car is a vehicle with a model and a
number of doors, a savings account is an account with an interest rate.
Fortran calls this inheritance: a type extends another one,
keeps all of its components, and adds components and procedures of its own. A
variable declared with class(parent) can then hold a value of the
parent type or of any type that extends it, and the procedure that runs is the
one that belongs to the value's actual type, a behaviour called
polymorphism. This article extends types, overrides their procedures,
declares abstract types that cannot be instantiated, and inspects polymorphic
values with select type.
Extending a type
The declaration type, extends(vehicle) :: car creates a type whose
first components are exactly those of vehicle, followed by the new
ones. A car therefore has a label and a wheels
component without declaring them again, and its structure constructor takes the
parent components first: car('car', 4, 'golf', 5) fills
label, wheels, model and
doors, in that order. The procedure show accepts
class(vehicle), so it can print a plain vehicle and a car with the
same code.
module fleet
implicit none
private
public :: vehicle, car, show
type :: vehicle
character(len=12) :: label = 'vehicle'
integer :: wheels = 4
end type vehicle
type, extends(vehicle) :: car
character(len=12) :: model = 'unknown'
integer :: doors = 4
end type car
contains
subroutine show(v)
class(vehicle), intent(in) :: v
print '(a, a, i0)', trim(v%label), ', wheels: ', v%wheels
end subroutine show
end module fleet
program u1
use fleet
implicit none
class(vehicle), allocatable :: p
type(car) :: c
c = car('car', 4, 'golf', 5)
print *, 'label: ', trim(c%label)
print *, 'model: ', trim(c%model)
print *, 'doors: ', c%doors
call show(c)
p = c
print *, 'through a class(vehicle) variable:'
call show(p)
print *, 'still a car: ', same_type_as(p, c)
end program u1
The car keeps its model and door count as its own components, while
show sees only the part it knows about and prints
car, wheels: 4. The assignment p = c works because
p is an allocatable class(vehicle) variable: it takes
the whole car, including its extra components, and
same_type_as(p, c) confirms that the dynamic type was not lost.
Assigning a car to a plain type(vehicle) variable is not allowed at
all, so Fortran does not silently cut off the extra components the way some
languages do; a parent value has to be built explicitly when that is wanted.
$ gfortran u1.f90 -o u1 $ ./u1 label: car model: golf doors: 5 car, wheels: 4 through a class(vehicle) variable: car, wheels: 4 still a car: T
Overriding a binding
A type that extends another may declare a binding with the same name and give
it a new implementation; this is called overriding. The base procedure
describe is inherited unchanged, but it calls
self%speak() on its passed object. When the object is a dog, that
call reaches dog_speak; when it is a cat, it reaches
cat_speak. The decision is made from the value at run time, which
is the essence of polymorphism.
module zoo
implicit none
private
public :: animal, dog, cat
type :: animal
character(len=10) :: name = 'animal'
contains
procedure :: speak => animal_speak
procedure :: describe => animal_describe
end type animal
type, extends(animal) :: dog
integer :: tricks = 0
contains
procedure :: speak => dog_speak
end type dog
type, extends(animal) :: cat
contains
procedure :: speak => cat_speak
end type cat
contains
subroutine animal_speak(self)
class(animal), intent(in) :: self
print '(a, a)', trim(self%name), ' makes a sound'
end subroutine animal_speak
subroutine dog_speak(self)
class(dog), intent(in) :: self
print '(a, a, i0, a)', trim(self%name), ' barks, knows ', &
self%tricks, ' tricks'
end subroutine dog_speak
subroutine cat_speak(self)
class(cat), intent(in) :: self
print '(a, a)', trim(self%name), ' meows'
end subroutine cat_speak
subroutine animal_describe(self)
class(animal), intent(in) :: self
print '(a, a, a)', 'described: ', trim(self%name), ' says:'
call self%speak()
end subroutine animal_describe
end module zoo
program u2
use zoo
implicit none
type(animal) :: plain
class(animal), allocatable :: pet
plain = animal('generic')
call plain%describe()
pet = dog('rex', 5)
call pet%describe()
pet = cat('tom')
call pet%describe()
end program u2
The first call uses a plain animal and produces the generic sound.
The other two use the same variable pet, which is allocated afresh
by each assignment, so the same statement prints the dog version and then the
cat version. The overriding procedures are declared with
class(dog) and class(cat), which lets a further
extension override them again; a binding meant to be final would use
type(...) or the non_overridable attribute, a detail
that matters only in larger hierarchies.
$ gfortran u2.f90 -o u2 $ ./u2 described: generic says: generic makes a sound described: rex says: rex barks, knows 5 tricks described: tom says: tom meows
Abstract types and deferred procedures
Some base types exist only to describe what their extensions have in common.
Such a type is declared abstract, cannot be instantiated, and may
contain deferred bindings: procedures that are declared but not
implemented, so every concrete extension must supply one. The name and the
interface of a deferred binding are given by a separate
abstract interface block, and import makes the type
visible inside it. A shape has no area of its own, but every shape
that can be created must know how to compute one.
module shapes
implicit none
private
public :: shape, circle, rectangle
type, abstract :: shape
character(len=12) :: label = 'shape'
contains
procedure(area_iface), deferred :: area
procedure :: describe => shape_describe
end type shape
abstract interface
pure real function area_iface(self)
import :: shape
class(shape), intent(in) :: self
end function area_iface
end interface
type, extends(shape) :: circle
real :: radius = 1.0
contains
procedure :: area => circle_area
end type circle
type, extends(shape) :: rectangle
real :: width = 1.0, height = 1.0
contains
procedure :: area => rectangle_area
end type rectangle
contains
pure real function circle_area(self) result(a)
class(circle), intent(in) :: self
a = 3.14159265 * self%radius**2
end function circle_area
pure real function rectangle_area(self) result(a)
class(rectangle), intent(in) :: self
a = self%width * self%height
end function rectangle_area
subroutine shape_describe(self)
class(shape), intent(in) :: self
print '(a, a, f8.2)', trim(self%label), ' area ', self%area()
end subroutine shape_describe
end module shapes
program u3
use shapes
implicit none
type(circle) :: c
type(rectangle) :: r
c = circle('circle', 2.0)
r = rectangle('rect', 3.0, 4.0)
call c%describe()
call r%describe()
print *, 'circle area: ', c%area()
print *, 'rectangle area:', r%area()
end program u3
The circle of radius two has an area of about 12.57, the rectangle of three by
four an area of 12, and both are printed by the same inherited
describe procedure. The label travels as the first component of
each constructor call, since it belongs to the parent. Forgetting to implement
area in an extension is a compile-time error rather than a
surprise at run time, which is the point of declaring the binding deferred.
$ gfortran u3.f90 -o u3 $ ./u3 circle area 12.57 rect area 12.00 circle area: 12.5663710 rectangle area: 12.0000000
A collection of different types
An array must have one declared type, so a plain
class(account), allocatable :: items(:) cannot hold accounts of
three different dynamic types: allocation of such an array needs a concrete
type, and assigning a different dynamic type to its elements is not allowed.
The usual Fortran solution is a small wrapper type with a single allocatable
polymorphic component, one wrapper per slot. Each component is allocated with
source=, which copies the value and fixes its dynamic type.
module accounts
implicit none
private
public :: account, savings, checking, account_box
type :: account
character(len=10) :: owner = 'none'
real :: balance = 0.0
contains
procedure :: describe => account_describe
end type account
type, extends(account) :: savings
real :: rate = 0.02
contains
procedure :: describe => savings_describe
end type savings
type, extends(account) :: checking
real :: limit = 500.0
contains
procedure :: describe => checking_describe
end type checking
type :: account_box
class(account), allocatable :: item
end type account_box
contains
subroutine account_describe(self)
class(account), intent(in) :: self
print '(a, a, f8.2)', trim(self%owner), ' balance ', self%balance
end subroutine account_describe
subroutine savings_describe(self)
class(savings), intent(in) :: self
print '(a, a, f8.2, a, f5.3)', 'savings ', trim(self%owner), &
self%balance, ', rate ', self%rate
end subroutine savings_describe
subroutine checking_describe(self)
class(checking), intent(in) :: self
print '(a, a, f8.2, a, f7.2)', 'checking ', trim(self%owner), &
self%balance, ', limit ', self%limit
end subroutine checking_describe
end module accounts
program u4
use accounts
implicit none
type(account_box) :: list(3)
integer :: i
allocate (list(1)%item, source=account('alice', 100.0))
allocate (list(2)%item, source=savings('bob', 250.0, 0.035))
allocate (list(3)%item, source=checking('carol', 40.0, 1000.0))
do i = 1, size(list)
select type (a => list(i)%item)
type is (savings)
print '(a, f5.3)', ' savings rate: ', a%rate
type is (checking)
print '(a, f7.2)', ' overdraft limit: ', a%limit
type is (account)
print *, ' plain account, no extras'
end select
call list(i)%item%describe()
end do
end program u4
The loop uses select type to look at the dynamic type of each
item. A type is (savings) branch matches only a savings account,
while class is (account) would also match every extension of
account; the keyword class default catches whatever
no earlier branch claimed. Inside a branch the associate name a
has the matched type, so the program can read a%rate and
a%limit, components the base type does not have. The call after
the select uses the polymorphic component directly and reaches the
overridden describe.
$ gfortran u4.f90 -o u4 $ ./u4 plain account, no extras alice balance 100.00 savings rate: 0.035 savings bob 250.00, rate 0.035 overdraft limit: 1000.00 checking carol 40.00, limit 1000.00
Unlimited polymorphic class(*)
The declaration class(*) is the unlimited polymorphic type: a
dummy argument declared that way accepts a value of any type, including
integers, reals and character strings. The only thing the procedure may do with
such an argument before examining it is pass it on or compare its dynamic type;
select type opens the value and gives each branch a name with the
matching type. This is how one routine can print values of many unrelated
types.
module printing
implicit none
private
public :: show
contains
subroutine show(value)
class(*), intent(in) :: value
select type (value)
type is (integer)
print '(a, i0)', 'integer: ', value
type is (real)
print '(a, f0.3)', 'real: ', value
type is (character(*))
print '(a, a)', 'string: ', trim(value)
type is (logical)
print '(a, l1)', 'logical: ', value
class default
print *, 'unsupported type'
end select
end subroutine show
end module printing
program u5
use printing
implicit none
call show(42)
call show(2.5)
call show('Fortran')
call show(.true.)
end program u5
Each call selects the branch that fits: the integer, the real, the string and
the logical value all travel through the same subroutine. The branch
type is (character(*)) matches a string of any length, and inside
it trim removes the padding that the actual argument may carry.
A type that matches no branch falls to class default, which is the
place to report an unsupported value instead of stopping the program.
$ gfortran u5.f90 -o u5 $ ./u5 integer: 42 real: 2.500 string: Fortran logical: T
Finalizers
A type can name a finalizer with the final statement: a
subroutine that runs just before a value of the type is destroyed. The
finalizer is called when a variable is overwritten by an assignment, when an
allocatable object is deallocated, and when a local variable leaves its scope.
Its dummy argument is the value itself, declared with type rather
than class and with intent(in), because the value is
already on its way out and must not be changed.
module tracking
implicit none
private
public :: tracer, released
integer :: released = 0
type :: tracer
character(len=16) :: name = 'unnamed'
integer :: id = 0
contains
final :: tracer_final
end type tracer
contains
subroutine tracer_final(self)
type(tracer), intent(in) :: self
released = released + 1
print '(a, a, a, i0)', 'release ', trim(self%name), ' id ', self%id
end subroutine tracer_final
end module tracking
program u6
use tracking
implicit none
type(tracer) :: a, b
a = tracer('first', 1)
b = tracer('second', 2)
a = tracer('replacement', 3)
print *, 'released so far:', released
block
type(tracer) :: local
local = tracer('local', 4)
print *, 'inside block'
end block
print *, 'released after block:', released
end program u6
Every assignment first destroys the value that was stored before it, so the
first three statements produce three releases: two of the default value and one
of the first tracer. The local variable inside the block adds two
more, one when the assignment replaces its default value and one when the block
ends, which raises the count to five. Each release line names the value that is
being destroyed, not the one that takes its place, so the ids run 0, 0, 1, 0
and 4. One compiler detail is worth knowing: GNU Fortran 13.2 runs the
finalizer of a scalar on deallocation but skips the elements of an array, so a
program that depends on finalization should test the behaviour of its compiler
before relying on it for arrays.
$ gfortran u6.f90 -o u6 $ ./u6 release unnamed id 0 release unnamed id 0 release first id 1 released so far: 3 release unnamed id 0 inside block release local id 4 released after block: 5
Type inquiry
Two intrinsic functions answer questions about dynamic types.
same_type_as(a, b) is true when both values have exactly the same
dynamic type, and extends_type_of(a, b) is true when the dynamic
type of a is an extension of the dynamic type of b.
Neither function looks at the components, only at the type, and both accept
polymorphic arguments, which is why the examples below keep the values in
class variables.
module kinds_of_vehicle
implicit none
private
public :: vehicle, car, truck
type :: vehicle
character(len=12) :: label = 'vehicle'
end type vehicle
type, extends(vehicle) :: car
integer :: doors = 4
end type car
type, extends(vehicle) :: truck
real :: payload = 0.0
end type truck
end module kinds_of_vehicle
program u7
use kinds_of_vehicle
implicit none
class(vehicle), allocatable :: a, b
allocate (a, source=vehicle('bike'))
allocate (b, source=car('sedan', 2))
print *, 'same type: ', same_type_as(a, b)
print *, 'b extends a: ', extends_type_of(b, a)
print *, 'a extends b: ', extends_type_of(a, b)
print *, 'b extends car: ', extends_type_of(b, car('other', 4))
deallocate (a)
allocate (a, source=truck('van', 3.5))
print *, 'now same type: ', same_type_as(a, b)
print *, 'b extends truck: ', extends_type_of(b, a)
end program u7
A car and a bike are different types, so same_type_as is false,
while extends_type_of(b, a) is true because a car extends a
vehicle. Reversing the arguments makes the answer false, and comparing a car
with a car gives true. After a is replaced by a truck, the car no
longer extends it, so that answer is false as well. These tests are useful when
a procedure must reject an unexpected type, or when a container wants to
describe what it holds.
$ gfortran u7.f90 -o u7 $ ./u7 same type: F b extends a: T a extends b: F b extends car: T now same type: F b extends truck: F
Next steps
The type-bound procedures article
introduces the bindings that are overridden here and explains
pass and nopass. The
operator overloading article
extends the same idea to operators, so that extended types can be added,
compared and written with the usual symbols. The
derived types article covers the
components and constructors, and the
modules article explains why the types and their
procedures live in one module.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have extended a type and kept its parent components,
overridden a binding and watched the call follow the dynamic type, declared an
abstract type with a deferred procedure, collected several dynamic types in a
wrapper and taken them apart with select type, passed unrelated
types through class(*), released values with a finalizer, and
asked about dynamic types with same_type_as and
extends_type_of. The previous article,
Fortran type-bound procedures,
shows how a type gets its own procedures in the first place.
Author
List all Fortran tutorials.