ZetCode

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.

u1.f90
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.

u2.f90
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.

u3.f90
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.

u4.f90
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.

u5.f90
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.

u6.f90
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.

u7.f90
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)

GNU Fortran manual

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.