Fortran type-bound procedures
last modified November 14, 2026
A derived type groups data, but the
procedures that work on that data still stand outside it: every call passes the
value as an argument, as in distance(a, b). A type-bound
procedure moves the procedure into the type definition, so that it is
called through the value itself, as in a%distance(b). Other
languages call such a procedure a method, and the value before the
percent sign the object. Fortran keeps the model explicit: the object
is still an ordinary first argument of the procedure, and the compiler merely
allows the friendlier call syntax.
This article declares procedures inside a type, hides components behind a
private boundary, groups several procedures under one generic name, distinguishes
the pass and nopass bindings, and builds a stack, an
account, a calendar date and a polynomial as types that carry their own
behaviour.
Declaring a type-bound procedure
The component list of a type ends at contains, and every line
after it binds a name to a module procedure. The form
procedure :: norm => point_norm says that
norm is the name seen by the caller and point_norm is
the procedure that implements it. The first dummy argument of that procedure is
the passed object: it receives the variable written before the percent
sign. It is declared with class(point) rather than
type(point) so that values of an extended type can be passed as
well, a subject the inheritance article takes up.
module points
implicit none
private
public :: point
type :: point
real :: x = 0.0, y = 0.0
contains
procedure :: norm => point_norm
procedure :: move => point_move
procedure :: show => point_show
end type point
contains
pure real function point_norm(self) result(d)
class(point), intent(in) :: self
d = sqrt(self%x**2 + self%y**2)
end function point_norm
subroutine point_move(self, dx, dy)
class(point), intent(inout) :: self
real, intent(in) :: dx, dy
self%x = self%x + dx
self%y = self%y + dy
end subroutine point_move
subroutine point_show(self)
class(point), intent(in) :: self
print '(a, f6.2, a, f6.2, a)', 'point(', self%x, ', ', self%y, ')'
end subroutine point_show
end module points
program t1
use points
implicit none
type(point) :: p
p = point(3.0, 4.0)
print *, 'norm:', p%norm()
call p%move(1.0, -1.0)
call p%show()
print *, 'new norm:', p%norm()
end program t1
The point (3, 4) has a norm of 5, and moving it by
(1, -1) gives (4, 3), whose norm is still 5. A
function binding is used inside an expression, while a subroutine binding needs
call, exactly like the underlying procedure. The binding changes
only the syntax: p%norm() is the same call as
point_norm(p), and the compiler checks the arguments of both in the
same way.
$ gfortran t1.f90 -o t1 $ ./t1 norm: 5.00000000 point( 4.00, 3.00) new norm: 5.00000000
Private components
A type can hide its components with private, and the hiding is
enforced by the compiler: code outside the module can name the type and call
its procedures, but a reference to a%balance is an error.
The components then form the state of the object, which only the
procedures of the type may read or change. This is the same idea as a
private variable of a module, applied to one value rather than to
the whole module.
module bank
implicit none
private
public :: account
type :: account
private
character(len=12) :: owner = 'unknown'
real :: balance = 0.0
integer :: moves = 0
contains
procedure :: open => account_open
procedure :: deposit => account_deposit
procedure :: withdraw => account_withdraw
procedure :: report => account_report
procedure, nopass :: currency => account_currency
end type account
contains
subroutine account_open(self, owner, amount)
class(account), intent(out) :: self
character(len=*), intent(in) :: owner
real, intent(in) :: amount
self%owner = owner
self%balance = amount
self%moves = 0
end subroutine account_open
subroutine account_deposit(self, amount)
class(account), intent(inout) :: self
real, intent(in) :: amount
self%balance = self%balance + amount
self%moves = self%moves + 1
end subroutine account_deposit
subroutine account_withdraw(self, amount)
class(account), intent(inout) :: self
real, intent(in) :: amount
if (amount > self%balance) then
print '(a, f8.2)', ' refused, balance is', self%balance
return
end if
self%balance = self%balance - amount
self%moves = self%moves + 1
end subroutine account_withdraw
subroutine account_report(self)
class(account), intent(in) :: self
print '(a, a, a, f8.2, a, i0, a)', trim(self%owner), ': ', &
self%currency(), self%balance, ' after ', self%moves, ' moves'
end subroutine account_report
pure function account_currency() result(c)
character(len=3) :: c
c = 'EUR'
end function account_currency
end module bank
program t2
use bank
implicit none
type(account) :: a
call a%open('alice', 100.0)
call a%report()
call a%deposit(50.0)
call a%withdraw(30.0)
call a%withdraw(500.0)
call a%report()
print *, 'currency: ', a%currency()
end program t2
The account starts with 100, receives 50 and gives up 30, so the second report
shows 120 after two successful moves. The attempted withdrawal of 500 is
refused before anything changes, which is why the count stays at two. The
currency binding is declared nopass: it has no passed
object, and the call a%currency() only uses the name
a as a way to reach the procedure. Without the private
line inside the type, the program could have changed balance
directly and bypassed the check.
$ gfortran t2.f90 -o t2 $ ./t2 alice: EUR 100.00 after 0 moves refused, balance is 120.00 alice: EUR 120.00 after 2 moves currency: EUR
A stack as a type
A type with an array component can implement a small container whose state
survives between calls. The stack below keeps its values in
items and the index of the last one in top; pushing
adds at top + 1 and popping removes from top, so the
last value pushed is the first one popped. Both components are private, which
guarantees that no caller can move top behind the procedure's
back.
module stacks
implicit none
private
public :: stack, max_size
integer, parameter :: max_size = 100
type :: stack
private
integer :: items(max_size) = 0
integer :: top = 0
contains
procedure :: push => stack_push
procedure :: pop => stack_pop
procedure :: size => stack_size
procedure :: is_empty => stack_empty
procedure :: show => stack_show
end type stack
contains
subroutine stack_push(self, value)
class(stack), intent(inout) :: self
integer, intent(in) :: value
if (self%top >= max_size) then
print *, 'push refused: stack is full'
return
end if
self%top = self%top + 1
self%items(self%top) = value
end subroutine stack_push
integer function stack_pop(self) result(v)
class(stack), intent(inout) :: self
if (self%top == 0) then
print *, 'pop refused: stack is empty'
v = -1
return
end if
v = self%items(self%top)
self%top = self%top - 1
end function stack_pop
pure integer function stack_size(self) result(n)
class(stack), intent(in) :: self
n = self%top
end function stack_size
pure logical function stack_empty(self) result(flag)
class(stack), intent(in) :: self
flag = self%top == 0
end function stack_empty
subroutine stack_show(self)
class(stack), intent(in) :: self
integer :: i
print '(a, 10(i0, 1x))', 'top to bottom:', &
(self%items(i), i = self%top, 1, -1)
end subroutine stack_show
end module stacks
program t3
use stacks
implicit none
type(stack) :: s
integer :: i
print *, 'empty:', s%is_empty()
do i = 1, 5
call s%push(i * i)
end do
print *, 'size: ', s%size()
call s%show()
print *, 'popped:', s%pop(), s%pop()
print *, 'size: ', s%size()
print *, 'empty:', s%is_empty()
end program t3
Five squares go in and the stack reports a size of five; the display runs from
the top down, so it starts with 25 and ends with 1. The two pops return 25 and
16, the size falls to three, and is_empty is still false. The
initializer items(max_size) = 0 is not strictly needed, because
only elements up to top are read, but it makes a freshly declared
stack deterministic and costs nothing at this size.
$ gfortran t3.f90 -o t3 $ ./t3 empty: T size: 5 top to bottom:25 16 9 4 1 popped: 25 16 size: 3 empty: F
Generic bindings
One type can offer several procedures under a single name by declaring them as
a generic binding. The line
generic :: set => set_real, set_ints creates a set of
specific bindings, and the compiler chooses among them from the number
and the types of the arguments, exactly as it does for an overloaded operator.
The choice is made once, at compile time, so a call that matches none of the
specifics is an error and a call that matches two is ambiguous.
module vectors
implicit none
private
public :: vec
type :: vec
real :: x = 0.0, y = 0.0
contains
procedure :: set_real => vec_set_real
procedure :: set_ints => vec_set_ints
generic :: set => set_real, set_ints
procedure :: show => vec_show
end type vec
contains
subroutine vec_set_real(self, r)
class(vec), intent(inout) :: self
real, intent(in) :: r
self%x = r
self%y = r
end subroutine vec_set_real
subroutine vec_set_ints(self, i, j)
class(vec), intent(inout) :: self
integer, intent(in) :: i, j
self%x = real(i)
self%y = real(j)
end subroutine vec_set_ints
subroutine vec_show(self)
class(vec), intent(in) :: self
print '(a, f5.1, a, f5.1, a)', '(', self%x, ', ', self%y, ')'
end subroutine vec_show
end module vectors
program t4
use vectors
implicit none
type(vec) :: v
call v%set(2.5)
call v%show()
call v%set(3, 4)
call v%show()
call v%set_real(7.0)
call v%show()
end program t4
The first call passes one real and reaches set_real, the second
passes two integers and reaches set_ints, and the third names the
specific binding directly. A generic binding does not hide its specifics; it
only adds a shorter way to reach them. Note that the two procedures differ in
the number of arguments, which is enough to tell them apart; two procedures
with identical argument lists could not share a generic name.
$ gfortran t4.f90 -o t4 $ ./t4 ( 2.5, 2.5) ( 3.0, 4.0) ( 7.0, 7.0)
pass and nopass
By default the passed object is the first dummy argument, whatever its name.
The specifier pass(self) states that name explicitly, which is
useful documentation when a procedure has several arguments, and
nopass removes the passed object altogether. A nopass
binding can therefore be a function with no arguments at all, reached through
any value of the type; it behaves like a constructor or a constant attached to
the type.
module players
implicit none
private
public :: player
type :: player
character(len=10) :: name = ''
integer :: score = 0
contains
procedure, pass(self) :: add => player_add
procedure, nopass :: best => player_best
procedure :: beats => player_beats
end type player
contains
subroutine player_add(self, points)
class(player), intent(inout) :: self
integer, intent(in) :: points
self%score = self%score + points
end subroutine player_add
function player_best() result(p)
type(player) :: p
p = player('nobody', 0)
end function player_best
pure logical function player_beats(self, other) result(flag)
class(player), intent(in) :: self
type(player), intent(in) :: other
flag = self%score > other%score
end function player_beats
end module players
program t5
use players
implicit none
type(player) :: a, b, leader
a = player('alice', 0)
b = player('bob', 0)
call a%add(12)
call a%add(8)
call b%add(15)
print '(a, a, i0, a, a, a, l1)', trim(a%name), ' with ', a%score, &
' beats ', trim(b%name), ': ', a%beats(b)
print '(a, a, i0, a, a, a, l1)', trim(b%name), ' with ', b%score, &
' beats ', trim(a%name), ': ', b%beats(a)
leader = a%best()
print '(a, a, a, i0)', 'best starts as ', trim(leader%name), ' with ', &
leader%score
end program t5
Alice collects 20 points and Bob 15, so the first comparison is true and the
second false. The passed object of beats is the player before the
percent sign, while the other player travels as an ordinary argument.
a%best() passes nothing at all and returns the empty player used
as a starting point for a search; the same function could also be written as a
plain module function, and the binding only gives it a home in the type.
$ gfortran t5.f90 -o t5 $ ./t5 alice with 20 beats bob: T bob with 15 beats alice: F best starts as nobody with 0
Keeping the object valid
Because the procedures of a type are the only code that may touch its private
components, they are the natural place to enforce the rules that keep a value
meaningful. The date type below validates its own fields, advances by one day
with the correct month and leap-year handling, and prints itself in ISO form.
The helpers is_leap and month_length are ordinary
module functions; they are not bound to the type because they have no object of
their own.
module dates
implicit none
private
public :: date
type :: date
integer :: year = 2000
integer :: month = 1
integer :: day = 1
contains
procedure :: is_valid => date_valid
procedure :: next => date_next
procedure :: show => date_show
end type date
contains
pure logical function date_valid(self) result(flag)
class(date), intent(in) :: self
flag = .false.
if (self%month < 1 .or. self%month > 12) return
if (self%day < 1 .or. self%day > month_length(self%year, self%month)) return
flag = .true.
end function date_valid
subroutine date_next(self)
class(date), intent(inout) :: self
self%day = self%day + 1
if (self%day > month_length(self%year, self%month)) then
self%day = 1
self%month = self%month + 1
if (self%month > 12) then
self%month = 1
self%year = self%year + 1
end if
end if
end subroutine date_next
subroutine date_show(self)
class(date), intent(in) :: self
print '(i4.4, a, i2.2, a, i2.2)', self%year, '-', self%month, '-', self%day
end subroutine date_show
pure logical function is_leap(y) result(flag)
integer, intent(in) :: y
flag = (mod(y, 4) == 0 .and. mod(y, 100) /= 0) .or. mod(y, 400) == 0
end function is_leap
pure integer function month_length(y, m) result(n)
integer, intent(in) :: y, m
integer, parameter :: lengths(12) = [31, 28, 31, 30, 31, 30, &
31, 31, 30, 31, 30, 31]
n = lengths(m)
if (m == 2 .and. is_leap(y)) n = 29
end function month_length
end module dates
program t6
use dates
implicit none
type(date) :: d
integer :: i
d = date(2024, 2, 28)
print *, 'valid:', d%is_valid()
call d%show()
do i = 1, 3
call d%next()
call d%show()
end do
d = date(2023, 2, 29)
print *, 'valid:', d%is_valid()
d = date(1900, 2, 29)
print *, 'valid:', d%is_valid()
end program t6
Starting at 28 February 2024, three steps cross the leap day and the end of the
month, so the dates run through 29 February and 1 and 2 March. The year 2023 is
not a leap year, so 29 February 2023 is invalid, and 1900 is a century that is
not divisible by 400, so its 29 February is invalid as well. The constructor
date(...) accepts such a value without complaint, because a
structure constructor only copies components; a stricter program would provide
an init binding that rejects bad input before the value exists.
$ gfortran t6.f90 -o t6 $ ./t6 valid: T 2024-02-28 2024-02-29 2024-03-01 2024-03-02 valid: F valid: F
Bound functions that return the type
A binding may return a value of its own type, which allows a type to produce new values from old ones without the caller allocating anything. The polynomial below stores its coefficients in an allocatable component, evaluates itself by Horner's rule, and returns its derivative as a new polynomial. The derivative allocates the coefficient array of the result, and the assignment at the call site takes ownership of that array.
module polys
implicit none
private
public :: poly
type :: poly
real, allocatable :: c(:)
contains
procedure :: eval => poly_eval
procedure :: derivative => poly_derivative
procedure :: show => poly_show
end type poly
contains
pure real function poly_eval(self, x) result(y)
class(poly), intent(in) :: self
real, intent(in) :: x
integer :: i
y = 0.0
do i = size(self%c), 1, -1
y = y * x + self%c(i)
end do
end function poly_eval
function poly_derivative(self) result(d)
class(poly), intent(in) :: self
type(poly) :: d
integer :: i, n
n = size(self%c)
if (n <= 1) then
allocate (d%c(1))
d%c = 0.0
return
end if
allocate (d%c(n - 1))
do i = 2, n
d%c(i - 1) = self%c(i) * real(i - 1)
end do
end function poly_derivative
subroutine poly_show(self)
class(poly), intent(in) :: self
integer :: i
do i = size(self%c), 1, -1
if (i == 1) then
print '(a, f0.1)', ' c: ', self%c(i)
else
print '(a, f0.1, a, i0)', ' c: ', self%c(i), ' x^', i - 1
end if
end do
end subroutine poly_show
end module polys
program t7
use polys
implicit none
type(poly) :: p, d
p%c = [1.0, 0.0, 3.0, 2.0]
print *, 'p(2) =', p%eval(2.0)
print *, 'p(-1) =', p%eval(-1.0)
call p%show()
d = p%derivative()
print *, 'p''(2) =', d%eval(2.0)
call d%show()
end program t7
The coefficients [1, 0, 3, 2] describe
2x^3 + 3x^2 + 1, which is 29 at x = 2 and 2 at
x = -1. The derivative 6x^2 + 6x is 36 at
x = 2, and the loop copies the coefficients from lowest to
highest, so the display starts at the highest power. The call
d = p%derivative() assigns a value that owns an allocatable array;
Fortran copies the array into d and releases the temporary when
the statement ends.
$ gfortran t7.f90 -o t7 $ ./t7 p(2) = 29.0000000 p(-1) = 2.00000000 c: 2.0 x^3 c: 3.0 x^2 c: .0 x^1 c: 1.0 p'(2) = 36.0000000 c: 6.0 x^2 c: 6.0 x^1 c: .0
Next steps
The inheritance and polymorphism article
shows why the passed object is declared with class: an extended
type can override a binding, and the call through a polymorphic variable reaches
the version that belongs to the runtime type. The
operator overloading article adds
the operator and assignment interfaces, and the
derived types article covers the
components and constructors used throughout this one. The
modules article explains the
private and public rules that hide the components.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have declared procedures inside a type and called them with
the percent sign, hidden components with private, built a stack and
an account whose state only their own procedures can change, grouped bindings
under a generic name, separated pass from nopass, and
returned a new polynomial from a binding. The previous article,
Fortran derived types, introduces the
components that these procedures work on.
Author
List all Fortran tutorials.