All in One View
Content from Connecting to ARCHER2 and transferring data
Last updated on 2026-07-21 | Edit this page
Overview
Questions
- What can I expect from this course?
- How will the course work and how will I get help?
- How can I give feedback to improve the course?
- How can I access ARCHER2 interactively?
Objectives
- Understand how this course works, how I can get help and how I can give feedback.
- Understand how to connect to ARCHER2.
Purpose
Attendees of this course will get access to the ARCHER2 HPC facility. You will have the ability to request an account and to login to ARCHER2 before the course begins. If you are not able to login, you can come to this pre-session where the instructors will help make sure you can login to ARCHER2.
Note that if you are not able to login to ARCHER2, and do not attend this session, you may struggle to run the course exercises as these were designed to run on ARCHER2 specifically.
Connecting using SSH
The ARCHER2 login address is
Access to ARCHER2 is via SSH using both a time-based one time password (TOTP) and a passphrase-protected SSH key pair.
Passwords and password policy
When you first get an ARCHER2 account, you will get a single-use password from the SAFE which you will be asked to change to a password of your choice. Your chosen password must have the required complexity as specified in the ARCHER2 Password Policy.
The password policy has been chosen to allow users to use both
complex, shorter passwords and long, but comparatively simple passwords.
For example, passwords in the style of both LA10!£lsty and
horsebatterystaple would be supported.
SSH keys
As well as password access, users are required to add the public part of an SSH key pair to access ARCHER2. The public part of the key pair is associated with your account using the SAFE web interface. See the ARCHER2 User and Best Practice Guide for information on how to create SSH key pairs and associate them with your account:
TOTP/MFA
ARCHER2 accounts are now required to use timed one-time passwords (TOTP), as part of a multi-factor authorisation (MFA) system. Instructions on how to add MFA authentication to a machine account on SAFE can be found here.
Data transfer services: scp, rsync, Globus Online
ARCHER2 supports a number of different data transfer mechanisms. The one you choose depends on the amount and structure of the data you want to transfer and where you want to transfer the data to. The three main options are:
-
scp: The standard way to transfer small to medium amounts of data off ARCHER2 to any other location. -
rsync: Used if you need to keep small to medium datasets synchronised between two different locations
More information on data transfer mechanisms can be found in the ARCHER2 User and Best Practice Guide:
Installation
For details of how to log into an ARCHER2 account, see https://docs.archer2.ac.uk/quick-start/quickstart-users/
Check out the git repository to your laptop or ARCHER2 account.
$ git clone https://github.com/EPCCed/2026-07-27-Fortran-inter.git
$ cd 2026-07-27-Fortran-inter
The default Fortran compiler on ARCHER2 is the Cray Fortran compiler
invoked using ftn. For example,
$ cd examples/01-arrays
$ ftn problem1.f90
should generate an executable with the default name
a.out.
Each section of the course is associated with a different directory, each of which contains a number of example programs and exercise templates. Answers to exercises generally re-appear as templates to later exercises. Miscellaneous solutions also appear in solutions subdirectories.
Not all the examples compile. Some have deliberate errors which will be discussed as part of the course.
Course structure and method
Rather than having separate lectures and practical sessions, this course is taught following The Carpentries methodology, where we all work together through material learning key skills and information throughout the course. Typically, this follows the method of the instructor demonstrating and then the attendees doing along with the instructor. You should also feel free to ask questions of the instructor whenever you like. The instructor will also provide many opportunities to pause and ask questions.
- We should all understand and follow the ARCHER2 Code of Conduct to ensure this course is conducted in the best teaching environment.
- ARCHER2’s login address is
login.archer2.ac.uk. - You have to change the default text password the first time you log in
- MFA is mandatory in ARCHER2
Content from Arrays
Last updated on 2026-07-21 | Edit this page
Overview
Questions
- How are arrays declared?
- How can array sizes be determined at runtime?
Objectives
- Understand how to define static and dynamic arrays
- Diagnose and debug array definitions in simple programs
Declarations
We may declare arrays of intrinsic type with a fairly elastic syntax, e.g.:
Note that the declaration e(10, 3) will override the
dimension(10, 2) statement, however this mix and match
approach is generally discouraged.
One may also omit the dimension attribute:
The two declarations of b(10,2) above are equivalent. If
one restricts oneself to one variable declaration per line, then the
second form is more concise, and will be preferred in the rest of this
course.
Arrays have a rank, a size (the number of elements). The sequence of
extents in each dimension is the shape. The array in Fortran is a
self-describing object and its properties may be interrogated via
intrinsic functions: size(), shape(),
lbound(), and ubound().
There is an array element order which has the left-most index counting fastest; we expect this to correspond to contiguous locations in memory.
There are a number of ways one may obtain array sections or array-valued objects which may not be contiguous:
Array constructors
There are a number of ways to provide initial values for array elements:
FORTRAN
integer, parameter :: j(3) = (/ -1, 0, +1 /) ! F2003
integer, parameter :: k(3) = [ -1, 0, +1 ] ! F2008
One may also use an implied do construction:
FORTRAN
integer :: i
real, parameter :: s(300) = [ (i, i = 1,300) ]
real :: t(3) = [ (2.0*(i*i + 1), i = 1,3) ]
Older code may also see use of the data statement to
initialise tables of values. This has the form:
where the data-statement-set consists of pairs of
That is, one associates a list of values with a list of variables, e.g.:
The data statement is quite flexible in syntax, but
generally can be omitted in favour of array constructors or other “more
modern” facilities. The data statement cannot be used to
initialise allocatable or pointer variables.
Heap storage
Storage for arrays may be established at run time via the
allocatable attribute, e.g.:
The allocation status of the array may be interrogated via
the intrinsic function allocated().
Assignment as allocation
One may combine allocation with initialisation in a number of ways including:
FORTRAN
real, allocatable :: a(:)
real, allocatable :: b(:)
real, allocatable :: c(:)
a = [ 1.0, 2.0, 3.0 ] ! status now allocated
allocate(b, source = a) ! "sourced allocation"
c = b(:)
Automatic reallocation is also possible for intrinsic assignments, e.g., following on from the above:
Allocatable scalars are allowed and may be useful in some circumstances.
Zero-sized arrays
Formally, a zero-sized allocation is not well defined by
malloc() in C. However, as Fortran arrays are objects,
zero-sized arrays are possible:
This can make it easier to write generic code which does not have to include conditionals to handle edge-cases where the array size might go to zero.
Two zero-sized arrays of the same rank may have different shapes, and so do not necessarilty conform (although a zero-sized array always conforms with a scalar, as usual). As a zero-sized array has no elements, it is always considered to be defined.
Exercise (5 minutes)
Correcting array programs
Look at the accompanying programs to be found in the directory
episodes/files/exercises/01-arrays within this
repository:
problem1.f90 ! needs completing
problem2.f90 ! will fail to compile; correct the code
problem3.f90 ! will fail at run time; what is the problem?
These may be compiled with, e.g.,
The compiler should highlight the source of the issue: i
is undefined. The values in the array will still not be as requested.
After fixing you should obtain the expected output
OUTPUT
Initial values 10.0000000 20.0000000 40.0000000
using the array constructor
real :: t(3) = [ (10.0*(2**(i-1)), i = 1, 3) ]
The program will SEGFAULT at runtime as a
has not been allocated. Confirm this by copying the line to print
Status to the point just before the array accesses are
performed:
OUTPUT
Status F
Allocating the array before accessing will allow the program to run correctly
OUTPUT
Status F # Before allocation
Status T # After allocation
Values 1.00000000 2.00000000 3.00000000
Alternatively, to perform assignment and allocation simultaneously,
as described above, turn a(:) = ... into
a = ....
Exercise (5 minutes)
Array safety
As arrays are self-describing in Fortran, it is relatively easy for
the compiler to analyse whether array accesses are valid, or within
bounds. This can help debugging. Most compilers will have an option that
instructs the compiler to inject additional code which checks bounds at
run time. For the Cray Fortran compiler, this is -hbounds;
for the GNU gfortran compiler, this is
-fbounds-check.
The first example contains a fixed array element reference which is incorrect. This should be visible to the compiler at compile time:
The second example prompts for an array index at run time. This may
or may not be out of bounds. Check what happens at run time if the value
of 4 is entered.
Check what happens if the tests are repeated using programs compiled without bounds checking. What are the possible dangers?
- Fortran supports both static- and dynamic-sized arrays
- Fortran arrays are objects, allowing out of bounds accesses to be checked
- The bounds of an array can be set by the programmer
Content from Break
Last updated on 2026-07-10 | Edit this page
Comfort break.
Content from Arrays as arguments
Last updated on 2026-07-21 | Edit this page
Overview
Questions
- How are arrays passed as arguments?
- How are array slices passed as arguments?
- How do allocatable array arguments interact with argument intent attributes?
Objectives
- Understand array argument behaviour
- Make use of non-standard array bounds in a simple program
- Write a program to illustrate the interactions between
allocatableandintent(out)dummy arguments.
This section describes use of arrays as actual and dummy arguments, and some associated conditions on their use.
Functions and subroutine arguments
A number of the following features require that procedure interfaces be explicit. As we recommend procedures are always placed in modules, we will assume this is always the case unless otherwise stated.
Explicit shape
An array may be passed explicitly with information on its shape as part of the dummy argument list:
This is ok; for large rank objects this can become a little verbose. The shapes of actual and dummy arguments must agree.
Assumed shape arguments
As an array in Fortran is a self-describing object, there is the option to use assumed shape:
The size of the dummy argument can be recovered via
size() etc. Note that it is the shape that is
passed, not the bounds; this can lead to errors without some care.
Consider:
The dummy argument in this case would have a size
nmax + 1 and a default lower bound of 1. This
may not be what is expected.
Example (5 minutes)
Array arguments bounds (continued)
Bonus question: why do we need the optional argument dim
in the lbound() and ubound() invocations in
the subroutine?
The intrinsic functions lbound and ubound
by default return a rank-1 array containing the lower or upper bounds of
all dimensions. Passing the optional argument dim instead
returns the scalar bound corresponding to that dimension of the
array.
Automatic objects
A procedure may allocate space for temporary objects which come into existence when the procedure is invoked, e.g.:
FORTRAN
subroutine array_operation(a)
real, intent(in) :: a
real :: tmp(1:size(a)) ! temporary storage
Such a temporary array, for which the size may not be known at compile time, is referred to as an automatic data object and is likely to be allocated on the stack. It will have a lifetime of the execution of the procedure. Automatic data objects cannot have an initial value as part of their declaration.
However, it is generally considered poor software engineering practice to put large objects on the stack. Heap storage may be preferred.
Assumed size arrays
Older code may contain the following syntax:
The * denotes an assumed size array (distinct
from assumed shape). This is something more akin to the plain C
pass-by-reference: a pointer. However, it is potentially error-prone as
it does not provide access to shape information of the associated actual
argument.
One should prefer the explicit shape, assumed shape, or allocatable options of Modern Fortran.
Allocatable arguments
Arguments to procedures may have the allocatable
attribute.
The procedure will receive the allocation status of the object on entry. A number of conditions apply:
- The corresponding actual argument must also be allocatable.
- The rank must be specified.
- Actions changing the allocation status of the actual argument must occur via the dummy argument.
Well-written procedures may need to check the allocation status of
any allocatable arguments with allocated() before action is
taken.
Intent
The intent concerns both the allocation status of the dummy argument
as well as the data. Dummy arguments with the allocatable
attribute should follow these rules:
- An argument with
intent(in)may not be allocated or deallocated (and the values may not be altered as for a normalintent(in)argument). - An argument with
intent(out)is automatically deallocated on entry if already allocated.
The second point may cause some surprises. An intent of
inout must be used if any information from the actual
argument is to be visible in the procedure.
Array sections as arguments
We may pass an array section as an actual argument. Consider:
FORTRAN
subroutine my_array_operation(a)
real, intent(inout) :: a(:)
! ... some operation on all elements...
end subroutine my_array_operation
We may invoke this as
to operate on the odd-indexed elements. How has this worked? In
practice, the compiler has probably made a temporary copy of the section
as a contiguous array (with three elements). The copy is passed to the
subroutine, and the result copied out again. The relevant values are
then set in the original array a(1:5). The temporary array
is destroyed.
This process is usually referred to as “copy-in, copy-out”. In Fortran, the compiler is given the freedom to do this if it is deemed appropriate. So Fortran cannot be described as “call by reference” or “call by value”. The standard only says the mechanism “is usually similar to call by reference”.
Exercise (10 minutes)
Allocatable array arguments
Write a short example along the lines of example2.f90 to
check that an allocatable dummy argument with intent out is
indeed deallocated on entry to the subroutine.
Example output might look like
OUTPUT
Initial values 1.00000000 2.00000000 3.00000000 4.00000000 5.00000000
The argument b is unallocated
Final values
Allocatable array arguments (continued)
Can an array section be passed to a subroutine where the corresponding dummy argument has been declared alloctable?
No, as the actual argument and intermediate array slice’s allocation status may not be the same after the call.
Allocatable array arguments (continued)
What happens if a function with an allocatable result returns with the result in an unallocated state?
The variable assignment will be unallocated.
- Arrays passed as arguments retain their size, not their bounds
- Array arguments behave as though passed by reference, however array sections may require copy-in, copy-out
Content from More on pointers
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How are pointers associated with data?
- How can pointers be used to access arrays?
- How can pointers be associated with procedures?
Objectives
- Understand pointer association
- Understand how pointers allow us to create ‘views’ of arrays
- Understand the use of procedure pointers
In C, a (bare) pointer is simply a variable that holds an address.
(C++ has more sophisticated pointer types such as
unique_ptr etc.). In this section we look more closely at
pointers in Fortran.
Pointer assignment
We recall that a pointer variable may be undefined, unassociated (sometimes “disassociated”), or associated:
integer, pointer :: p1 ! undefined
integer, pointer :: p2 => null() ! unassociated
integer, pointer :: p3 => t ! associated with target t
The difference between undefined and unassociated is that an
undefined pointer can not have its association status queried by the
intrinsic function associated().
Pointer assignment is of the form:
pointer => target
where the target may be a variable with the target attribute, another pointer, or a function returning a pointer result. The type, type parameters and rank of both sides of the assignment must match.
Target is an array
For example, if the target is an array, we might have:
integer, target :: a(10)
integer, pointer :: p(:)
p => a(1:10:2)
then the shape and bounds are those the right-hand side. We can see
here that the pointer is not a simple object. It should be viewed as a
descriptor which holds information about what it is pointing to. Here
one could use intrinsic functions shape(),
lbound(), and ubound() to interrogate the
pointer as one would for an array. The pointer must be associated to do
so.
It is possible to specify the lower bound of a pointer array:
p(2:) => a(2:6)
in which case elements are indexed from the lower bound when using the pointer.
A multi-dimensional pointer may be “reshaped” to a lower-dimensional target. For example:
integer, parameter :: n = 3
integer, target :: storage(n*n)
integer, pointer :: matrix(:, :)
matrix(1:n, 1:n) => storage(:)
Note that both the lower and upper bounds of
matrix(1:n, 1:n) are included on the left-hand side of the
pointer assignment.
Target is a pointer
If the target of an assignment is a pointer, then subsequent changes in status of the target are not reflected in the later assignment, e.g.,
b => a
c => b
nullify(b)
leaves c associated with a.
The nullify() statement has the same effect as
assignment to null(). However, nullify() can
take a comma-separated list of pointer arguments if desired. If the
argument has been allocated via allocate(), then
nullify() will not perform deallocation: use
deallocate().
Exercise: (5 minutes)
Pointer shapes and bounds
Write a program which initialises an integer array a(10)
and assigns the elements the values 1-10. Associate a pointer with the
even-numbered elements of a(2:10) (as above). Print out the
values returned by the functions lbound(),
ubound() and size() when applied to the
pointer. Check the value associated with the fourth element of the
pointer is eight.
A bare outline is provided in example1.f90.
program example1
implicit none
integer, target :: a(10)
integer, pointer :: p(:)
integer :: i
p => a(2:10:2)
a = [ (i, i = 1, 10) ]
! print pointer lower bound lbound()
print *, "Lower bound: ", lbound(p) ! => 1
! print pointer upper bound ubound()
print *, "Upper bound: ", ubound(p) ! => 5
! check size and elements
print *, "Size: ", size(p) ! => 5
print *, "p(4) = ", p(4) ! => 8
end program example1
Pointers as arguments
A dummy argument may have the pointer attribute. If the intent of the
dummy argument is intent(inout) or
intent(out), the relevant actual argument must also be a
pointer.
A pointer actual argument can correspond to a non-pointer dummy argument, in which case the pointer actual argument must be associated with a suitable target.
Arguments must be distinct
C programming has the idea of restrict for pointer
arguments to functions. The restrict qualifier is a
guarantee to the compiler that there will be no overlap in the memory
accessed via different pointers: all the relevant memory locations are
distinct. (If this is not the case then the situation is often referred
to as aliasing.) This information can be important, e.g., to
allow the compiler to include, omit, or re-order operations to perform
optimisations.
There is a similar consideration in Fortran, where the mechanism can be copy-in, copy-out. There are some moderately complex rules on what is and what is not allowed to ensure that dummy arguments are independent, both from each other and from entities available via host association, or any other mechanism.
Broadly: any operation that affects the value of an argument must be taken via the associated dummy argument alone. This includes allocation status, and association status for pointers.
-
Consider a case where we have a subroutine of the form
subroutine my_array_update(ia, ib) integer, intent(inout) :: ia(:) integer, intent(inout) :: ib(:) ia(:) = ia(:) + 1 ib(:) = ib(:) / 2 end subroutine my_array_updateIf we were to make a
call my_array_update(a(1:10), a(6:15))we have a situation where both dummy arguments are referring to the same section of the single actual argument.Procedures which have only one
intent(inout)argument can reduce the scope for this potential problem. -
Consider a case where we have a module procedure, schematically:
module my_module ! ... integer, allocatable, public :: ihost(:) ! ... contains subroutine my_subroutine(iarg) integer, allocatable, intent(inout) :: iarg(:) ! ... change to the status of iarg(:) or ihost(:) ... end subroutine my_subroutine end module my_moduleWe now have a situation where ihost(:) may appear as the actual argument to
my_subroutine(). This is best avoided by avoiding module scope data.
These restrictions are not enforced by the compiler (it may not even be possible): violations by the programmer may just be manifest as undefined behaviour.
Actual and dummy arguments with target attribute
Likewise, there is a set of conditions on the use of
target attribute in the context of procedure arguments.
These may be summarised:
- Pointers associated with an actual argument may not become associated with relevant dummy arguments (copy-in, copy-out may occur);
- If a dummy argument has the
targetattribute, any pointer associated with the dummy argument may not be associated on return.
Procedure declarations
There is a procedure statement which declares a name to
be a procedure. In its simplest form, it is equivalent to an external
declaration:
procedure () :: f_external
external :: f_external
Here, the () indicates there is no interface information
available. For functions, one may include information on a return
type
procedure (integer) :: f_external
integer, external :: f_external
These are again equivalent.
The general form is
procedure [(interface-spec)] [, attribute-list ::] declaration-list
The parentheses accommodate an interface specification, which may be
an interface name, or a declaration type specification (such as
integer above). There are a number of possible attributes,
including pointer, which declares a pointer to a
procedure.
Pointers to functions or subroutines: procedure pointers
A procedure having an explicit interface may be the target of a procedure pointer. The declaration might be as follows:
interface
function my_external_function(x) result(y)
real, intent(in) :: x
real :: y
end function my_external_function
end interface
procedure (my_external_function), pointer :: f => my_external_function
! ...
y = f(x)
Note the name appearing in the interface needs to match that of the external function. This is sometimes referred to as a specific interface.
We may also define an abstract procedure, which may only appear in
the interface-name specification of a procedure
declaration.
abstract interface
function if_function(x) result(y)
real, intent(in) :: x
real :: y
end function if_function
end interface
An associated procedure definition might be
procedure (if_function) :: my_external_function
This is an alternative to the specific interface declaration above.
A procedure pointer must be associated in order to reference the procedure.
Exercise (10 minutes)
Procedure interfaces and pointers
An example of an external function is provided in
external.f90. This is a function which has a single
argument which is an integer rank 1 array, and returns an integer which
is the size of the array.
The accompanying program example2.f90 makes a simple
procedure declaration to allow the external function to be
referenced (similar to an external declaration).
$ ftn external.f90 example2.f90
There are a number of possible problems with this example (e.g., what happens if you provide an actual argument which is a rank two array?).
Adjust the example to provide a specific interface block which describes the external function. Make an appropriate procedure declaration, and also try declaring a pointer to the procedure.
Check this works and that the compiler now traps errors associated with incompatible actual arguments.
program example2
implicit none
interface
function array_size(a) result(isize)
real, dimension(:), intent(in) :: a
integer :: isize
end function array_size
end interface
procedure (array_size), pointer :: f => array_size
real :: a(13)
print *, "size of a is: ", f(a)
end program example2
Procedure interfaces and pointers (continued)
Try replacing the specific interface block with an equivalent abstract interface. Again, call the external function via a name declared in a procedure statement, and also try a procedure pointer.
program example2
implicit none
abstract interface
function my_array_size(a) result(isize)
real, dimension(:), intent(in) :: a
integer :: isize
end function my_array_size
end interface
procedure (my_array_size) :: array_size
real :: a(13)
print *, "size of a is: ", array_size(a)
end program example2
- Fortran pointers describe what they are pointing to, not only its address.
- We must take care when programming with pointers to avoid aliasing.
- Procedure pointers create a binding between a variable and a procedure.
Content from Lunch
Last updated on 2026-07-10 | Edit this page
Lunch break.
Content from Derived types
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How are data structures (derived types) defined in Fortran?
- How can we control access to components of data structures in Fortran?
- How does assignment between derived types work?
Objectives
- Be able to declare your own data structures.
- Understand assignment operations in derived types and when shallow/deep copying occurs.
Derived types provide the fundamental basis for data structures
(cf. struct in C++). Derived types are sometimes referred
to as user-defined types. The advice given in the introductory course
was always to place type definitions in a module.
Definition
We recall that the general form of the declaration is:
type [ [, attribute-list] :: ] type-name
[private]
component-part
[ contains
procedure-part ]
end type [ type-name ]
Components of types may be intrinsic, allocatable, pointer, or other
derived types. The procedure part is used (optionally) to define
so-called type-bound procedures. These are the equivalent of
class methods in other languages (and will be covered in detail in later
episodes). Scope of components may be controlled via public
or private statements.
Encapsulation may be achieved by declaring a public type
with private components, e.g.,
type, public :: my_opaque_t
private
integer :: idata
end type my_opaque_t
It is also possible to have a mixture, e.g.:
type, public :: my_semi_opaque_t
private
integer :: idata ! default private
integer, public :: ndata ! explicitly public
end type my_semi_opaque_t
Components are referenced using the component selector
%. Types with components which are themselves derived types
give rise to the idea of an ultimate component, which is an
intrinsic type for which no further application of % is
relevant.
protected attribute
Note that there is a protected attribute in Fortran,
although it has a slightly different usage than in C++. It will be
discussed later as part of the section on submodules. Some authors argue
that protected should be avoided as it represents a
breakdown of encapsulation.
Assignments and copying
Intrinsic assignments
Intrinsic assignment is available for types, and involves an intrinsic assignment of each component in turn. Schematically:
type (my_semi_opaque_t) :: a
type (my_semi_opaque_t) :: b
b = my_semi_opaque(2, 3)
a = b
Here, the components of a will take on the values of the
components of b.
If a type component is itself a derived type, then intrinsic assignment takes place in the same way for that component.
Example (5 minutes)
Intrinsic assignment
The accompanying module my_semi_opaque_type.f90 provides
a public definition of the type as defined above, and includes a
function to initialise the two components. The accompanying program
exercise1.f90 will make the intrinsic assignment. You can
compile with, e.g.,
$ ftn my_semi_opaque_type.f90 example1.f90
How are you going to check that the result of the assignment is correct for both components (by printing the result to the screen)? Write some code to perform this check.
subroutine my_semi_opaque_print(name, val)
character(len=*), intent(in) :: name
type(my_semi_opaque_t), intent(in) :: val
print *, name, "%idata=", val%idata
print *, name, "%ndata=", val%ndata
end subroutine my_semi_opaque_print
This code should be added to the module. You should obtain the following output:
gfortran my_semi_opaque_type.f90 example1.f90 && ./a.out
a%idata= 2
a%ndata= 3
Allocatable components
Suppose our derived type had an allocatable component. For example:
type, public :: my_array_t
integer :: nlen
real, allocatable :: values(:)
end type my_array_t
Intrinsic assignment takes place for such an object in much the same way, e.g.,
type (my_array_t) :: a
type (my_array_t) :: b
! ... establish some data for b ...
a = b
However, there are a number of extra steps involved: 1) if the
values component of a on the left hand side is
already allocated, it is first deallocated; 2) an appropriate allocation
is then made for a depending on the size and bounds of
b%values(:); 3) all the relevant values of the right-hand
side are copied to the left-hand side.
If b%values is unallocated, then step (2) is omitted,
and a%values is also unallocated after assignment.
This is, in the jargon, a deep copy. You have a copy of the actual data.
Again, this process is repeated until any ultimate allocatable component is reached.
Derived types as procedure arguments
Recall the behaviour of allocatable arrays as arguments to
procedures. When the argument is intent(out) the array is
deallocated on entry. The same applies to allocatable components of
derived types when a derived-type argument is
intent(out).
Pointer components
For types with pointer components, the situation is different. Consider:
type, public :: my_array_pointer_t
integer :: nlen
real, pointer :: values(:)
end type my_array_pointer_t
Assignment here means that the pointer becomes associated with the target on the right-hand side.
type (my_array_pointer_t) :: a
type (my_array_pointer_t) :: b
b%nlen = 10
allocate(b%values(b%nlen))
a = b
This implies that if, in a subsequent step, the target becomes
unassociated (or deallocated), then the reference retained in
a is in a bad state.
This is a shallow copy. No data have been duplicated; only the pointer description itself.
Example (5 minutes)
Shallow and deep copies
In the accompanying module my_array_type.f90 both the
types above have been declared, along with a function to initialise some
array values. Compile the example program:
$ ftn my_array_type.f90 example2.f90
and check the values printed out. What happens if you insert a call
to my_array_destroy(a) (which deallocates the values
associated with the my_array_t argument) at the end of the
program and try to print the values of the pointer type c
again?
The output of the initial program
State of a 3 T
State of c 3 T 1.00000000 2.00000000 3.00000000
The output of the program after destroying a shows
c%values now points to uninitialised data, however
c%nlen is a deep copy and is still “valid” (in some
sense).
State of a 3 T
State of c 3 T 1.00000000 2.00000000 3.00000000
State of c 3 T -1.40913533E-36 1.56146688E-41 1.12103877E-44
Shallow and deep copies (continued)
What happens if you try to make a direct assignment between a
my_array_t object on the right-hand side, and a
my_array_pointer_t on the left-hand side?
The compiler rejects the code as invalid due to different types.
gfortran my_array_type.f90 example2.f90 && ./a.out
example2.f90:24:6:
24 | c = b
| 1
Error: Cannot convert TYPE(my_array_t) to TYPE(my_array_pointer_t) at (1)
A defined assignment
If something other than intrinsic assignment for types is required,
it is possible to overload the meaning of the assignment operation
=.
For example, if an assignment between two objects of
my_array_t were required, one could add a new assignment
interface in the module specification:
interface assignment (=)
module procedure my_assignment
end interface assignment (=)
and then provide the following module subprogram:
subroutine my_assignment(a, b)
type (my_array_t), intent(out) :: a
type (my_array_t), intent(in) :: b
a%nlen = b%nlen
a%values = b%values
end subroutine my_assignment
This must be a subroutine with two arguments, the first with
intent(out) (or inout) to represent the
left-hand side of the assignment, and the second with
intent(in) to represent the right-hand side.
Exercise (10 minutes)
Constructing a derived type pointer
In example2.f90 we explcitly assigned both components of
the my_array_pointer_t in the code and found we could not
make an assignment between my_array_pointer_t and
my_array_t. Add a subroutine in
my_array_type.f90 to make this possible.
First we define the (=) interface in the module
interface assignment (=)
module procedure my_array_ptr_assignment
end interface assignment (=)
and the assignment subroutine itself
subroutine my_array_ptr_assignment(a, b)
type(my_array_pointer_t), intent(out) :: a
type(my_array_t), target, intent(in) :: b
a%nlen = b%nlen
a%values => b%values
end subroutine my_array_ptr_assignment
noting that the assignment target must be a pointer so
that we can use it as the target of the my_array_pointer_t.
Then we can use c = a in the example program in place of
the member-wise assignment.
- Derived types enable creating custom data structures in Fortran.
- Access to components of derived types can be controlled via the
privateattribute.
Content from Interfaces and overloading
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How are interfaces used for generic programming?
Objectives
- Understand how to define generic interfaces, overload operators and define new names.
- Understand generic interface resolution and potential pitfalls.
The interface block has three different forms in
Fortran: it may define an abstract interface, a
generic interface, or a specific interface.
In this section, we will look at generic interfaces. Generic is the term which describes overloading in Fortran.
Generic interfaces
The formal description of an interface block in the generic context is
interface generic-spec
[ interface-specification ] ...
end interface generic-spec
The generic-spec has a number of different possible
forms:
-
assignment (=)for assignment - a generic procedure name
-
operator (defined-operator)for an operator - a derived type i/o operation
We have seen an example of this type of interface in the context of overloading assignment, e.g.,
interface assignment (=)
module procedure :: my_assignment
end interface assignment (=)
There are one or more interface-specification entries
which can take different forms. As we will usually expect to place
interface definitions in a module along with the relevant procedure
definitions, the interface-specification will often involve
one or more module procedure statements of the form
[ module ] procedure [ :: ] specific-procedure-list
The module in module procedure in the
interface block is optional: we could use external procedures. However,
it may be most natural to place the interface in the relevant module.
The procedure interface must be available in either form.
Generic assignment
We have seen an example of overloading assignment in the previous section:
interface assignment (=)
module procedure :: my_assignment
end interface assignment (=)
In this context, the procedure must be a subroutine (not a function)
with exactly two non-optional arguments. To prevent ambiguity, the first
argument must correspond to the left-hand side of the assignment and
have intent(inout) or intent(out), while the
second argument must represent the right-hand side of the assignment and
have intent(in).
Redefining assignment between intrinsic types is not allowed,
including redefining conforming array assignments. It is possible to
define assignment between an intrinsic type and a derived type. For
example, using the my_array_t again from the previous
section:
subroutine my_assignment_from_int(a, ival)
type (my_array_t), intent(inout) :: a
integer, intent(in) :: ival
a%values(:) = ival
end subroutine my_assignment_from_int
In this way, one can have a number of different procedures which overload the assignment: they are generic.
If the right-hand side is an expression, one can consider the assignment as being replaced by a call to the subroutine, with the second argument being the expression enclosed in parentheses (the right-hand side).
If an elemental subroutine is provided as a generic interface, both scalar and array assignments can be performed. Particular combinations of ranks may be specified (in which case intrinsic assignment remains available for other combinations).
Generic procedures
A generic procedure allows one to associate a generic name with one or more specific procedures.
interface generic-name
module procedure :: specific-prcedure-list
end interface generic-name
The specific-procedure-list can be a comma-separated
list of different implementations, or one may add new implementation on
different lines. For example,
interface my_generic_name
module procedure :: my_real32_implementation
module procedure :: my_real64_implementation
end interface my_generic_name
We are required to implement subroutines which are distinguishable by their arguments.
Generic procedure kinds
A procedure-list must consist of either all subroutines
or all functions: there cannot be a mix of subroutines and
functions.
Distinguishable procedures
In order that the compiler can identify unambiguously the appropriate specific implementation based on the actual arguments at the point of invocation, the signatures of a generic procedure must be distinguishable.
Distinguishing dummy arguments
Individual pairs of dummy arguments are distinguishable:
- if both are data objects and have different types, kind type parameters or ranks;
- if one is allocatable and the other is a pointer;
- if one is a procedure and the other is a data object.
Together, the set of non-optional arguments in each case must be distinguishable. Note that the order may not be counted upon if the dummy arguments have the same name, as one can always call using keywords at the point of invocation, e.g.,
call my_subroutine(arg2 = y, arg1 = x)
The presence or absence of a pointer attribute in the dummy argument is not enough to disambiguate a call; a pointer actual argument can be associated with dummy argument which is a non-pointer data object.
Example (10 minutes)
Constructor overloading
Looking again at our my_array_t example, check that you
can overload the default structure constructor my_array_t()
using the existing function my_array_allocate(). A new
version of the module and program are available in the
exercises/05-interfaces-overloading directory (or you can
keep your existing one).
$ ftn my_array_type.f90 example1.f90
Define a generic interface my_array_t that resolves to
my_array_allocate
interface my_array_t
module procedure my_array_allocate
end interface my_array_t
The object can now be initialised as a = my_array_t(3)
and the output should match the original code.
Constructor overloading (continued)
Add a new overloaded version to the same generic name which allows us to allocate a new array of a given size, but with the values initialised uniformly by a scalar integer supplied as a second argument to the new implementation. Check this works as expected.
Extend the generic interface to also resolve to
my_array_allocate_set when passed two arguments.
interface my_array_t
module procedure my_array_allocate
module procedure my_array_allocate_set
end interface my_array_t
contains
function my_array_allocate_set(nlen, val) result(a)
integer, intent(in) :: nlen
real, intent(in) :: val
type(my_array_t) :: a
a%nlen = nlen
allocate(a%values(nlen))
a%values(:) = val
end function my_array_allocate_set
calling a = my_array_t(4, 5.0) should now (re)allocate
a with four entries, each set to 5.0.
Example (5 minutes)
Different order, same arguments
The following is an edge-case which will not compile, as the dummy arguments of the two subroutines in the generic interface are ambiguous.
$ ftn -c example2.f90
There’s actually a simple solution to the problem. Can you see what it is (think about the keyword argument case)?
Because the arguments are named the same, using keyword arguments the two implementations cannot be distinguished by argument order. To resolve this we can simply rename one of the arguments in one of the implementations, e.g.
subroutine my_print_b(i, r)
integer, intent(in) :: i
real, intent(in) :: r
print *, "integer real32 ", i, r
end subroutine my_print_b
Overloading operators
It is possible to overload an intrinsic operator (such as
+), or define a new name for an operation. The intrinsic
operators include arithmetic operators +, -, *, / etc,
logical operators including .not., .or., and relational
operators including ==, <, <=. The choice of name or
symbol may depend on how meaning is ascribed to the operation.
To do so, one must provide a function (not a subroutine) taking
exactly one argument for unary operations such as negation, or exactly
two arguments for binary operations such as multiplication. The
arguments must be intent(in) in both cases. The arguments
cannot be optional.
One cannot overload operations for intrinsic types. They are only relevant for combinations of derived types, or derived types and intrinsic types.
For example, if we had two types
type (date_time_t) :: now ! a date and time
type (time_interval_t) :: dt ! interval in seconds
one might wish to overload the meaning of + to allow an
interval to be added to a date. This could be done as follows, assume we
have a module which makes available both types:
interface operator (+)
module procedure :: add_interval_to_date_time
end interface operator (+)
We would then have to supply the function
function add_interval_to_date_time(date, dt) result(newdate)
type (date_time_t), intent(in) :: date
type (time_interval_t), intent(in) :: dt
type (date_time_t). :: newdate
! ... details of implementation ...
end function add_interval_to_date_time
An expression of the form
now + dt
would then be replaced by the result of the function, which in this
case is of type date_time_t.
The order is significant here. The left-hand operand of the operation
corresponds to the first dummy argument, and the right-hand operand
corresponds to the second dummy argument. So one could not have
dt + now.
One could, equivalently, invoke the function directly:
type (date_time_t) :: newdate
...
newdate = add_interval_to_date(now, dt)
Caution
This is a (deliberately) somewhat contentious example. It raises a number of general concerns about the approach:
- If operations involves single type, the operands are interchangeable, and a limited number of functions is required.
- If operations involving even one defined type, and one intrinsic type are required, then the number of possible operations can quickly become quite large (without asking the question whether specific operations have meaning).
So it can be quite difficult to achieve a complete, consistent, and transparent set of operations for any new type.
Using a non-intrinsic name
We could equally define a new name
interface operator(.add.)
module procedure add_interval_to_date
end interface operator (.add.)
in which case we would write
newtime = now .add. dt
A new name must be of this form (having . at start and
finish) and must not be either .true. or
.false..
If one limits the choice to the intrinsic operator names, the
precedence of the different operations is the same, e.g., the precedence
of * is always greater than +. If using new
names, precedence in complex expressions may need to be established via
parentheses.
Exercise (20 minutes)
Dot and cross products
Write a module which defines a type to hold a 3-vector
(u_1, u_2, u_3) where the components are integers. Define
an operator .x. which computes the cross product of two
vectors, being
.)
This should also allow a vector triple product
where one must use parentheses to obtain the desired result.
One could also define a scalar product .dot., being

Does a scalar triple product
work correctly without parentheses?
A template is provided
$ ftn example3.f90 my_vector_type.f90
where the example program has a number of suggestions to check the results.
interface operator(.dot.)
module procedure dot_prod
end interface operator(.dot.)
interface operator (.x.)
module procedure cross_prod
end interface operator (.x.)
contains
module function dot_prod(a, b) result(d)
type(my_vector_t), intent(in) :: a, b
integer :: d
d = a%x * b%x + a%y * b%y + a%z * b%z
end function dot_prod
module function cross_prod(a, b) result(z)
type(my_vector_t), intent(in) :: a, b
type(my_vector_t) :: z
z%x = a%y * b%z - b%y * a%z
z%y = -(a%x * b%z - b%x * a%z)
z%z = a%x * b%y - b%x * a%y
end function cross_prod
The scalar triple product requires parentheses as evaluating the dot product would return a scalar as the first argument to the cross product (incorrect).
- Interfaces allow us to write generic, high-level code.
- Designing a generic interface requires care, particularly for generic operators.
Content from Break
Last updated on 2026-07-10 | Edit this page
Comfort break.
Content from Type extension and polymorphism
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How can we reuse type definitions in related types?
- How can we write generic procedures for related types?
- How can we apply type-specific behaviour for related types?
Objectives
- Understand type extension
- Understand the use of polymorphism
- Understand declared and dynamic types
Type extension and polymorphism provide the ability to define a uniform interface which can be used to interact with different implementations, and also handle references to such objects in a uniform way. This is the basis for abstraction.
Extending an existing type
Suppose we had a type to represent a general category of entity or object, e.g.,
type, public :: object_t
real :: rho ! density
real :: x(3) ! position of centre of mass
end type base_t
We can now define a more specific entity by extending the
object_t, e.g.,
type, extends(object_t), public :: sphere_t
real :: a ! radius
end type sphere_t
This new type is said to inherit the components of the original type which may be accessed via the component selector in one of two ways:
type (sphere_t) :: s
real :: density
density = s%object_t%rho
density = s%rho
The two assignments are equivalent. The new type has a parent component which has the name of the original, or base, type.
For operations which treat the base type as a whole, the longer form may be useful. However, if just component access is required, the second, shorter, form is more concise and to be preferred.
Single inheritance
A new type may extend only one existing type in Fortran: it has a so-called single inheritance model.
Structure constructors
There is a default structure constructor associated with the new type
type (sphere_t) :: s
real :: rho = 1.0
real :: x(3) = [ 2.0, 3.0, 4.0 ]
real :: radius = 1.5
s = sphere_t(rho, x, radius)
The order for the components in the structure constructor is the base type components first, and then the extended type components second. The components of each type also appear in the order that they have been declared.
The order can be adjusted with the use of keywords, e.g.:
s = sphere_t(a = radius, rho = rho, x = x)
We may also use the base type as a component of the structure constructor
type (object_t) :: obj
type (sphere_t) :: s
real :: a = 2.5
obj = object_t(1.5, [1.0, 1.0, 1.0])
s = sphere_t(object_t = obj, a)
Keywords are not necessary unless the order of the arguments is other than the defined order. Recall that components with default initialisations may be omitted in the structure constructor.
New types can be further extended by following the same procedure.
Example (5 minutes)
Extending types
Further extend the sphere_t to give a
charged_sphere_t by adding a (real) component
q in the new type. The first two type definitions are
provided in the file object_type.f90.
type, extends(sphere_t), public :: charged_sphere_t
real :: q = -1.0 ! charge
end type charged_sphere_t
Confirm you can compile the code with
ftn example1.f90 object_type.f90
Extending types (continued)
In the example main program, check you can provide some value for the
new charge component via a constructor (e.g., q = -1.0),
and access the ancestor components of the new type in both long and
short forms.
! ... Earlier declarations ...
cs = charged_sphere_t(s, 5.0)
print *, "Charged sphere q = ", cs%q
print *, "Charged sphere density (short) = ", cs%rho
print *, "Charged sphere density (long) = ", cs%sphere_t%object_t%rho
Compiling and running the code you should obtain output similar to
$ ftn example1.f90 object_type.f90 && ./a.out
Sphere density 1.00000000
Sphere density 1.00000000
Sphere radius 1.50000000
Sphere density 1.00000000
Sphere density 1.00000000
Sphere radius 1.50000000
Sphere density 2.50000000
Sphere position: 1.00000000 1.00000000 1.00000000
Sphere radius 1.50000000
Charged sphere q = 5.00000000
Charged sphere density (short) = 2.50000000
Charged sphere density (long) = 2.50000000
Polymorphism
In order to be able to handle types and extended types in a flexible way, we need a mechanism that allows a given variable to reference objects of different type. Such a variable is typically a pointer in many languages.
Fortran provides the pointer mechanism using
class (object_t), pointer :: obj => null()
One may also have an allocatable polymorphic object:
class (object_t), allocatable :: obj
A class pointer cannot be declared as being of an intrinsic type: the object must be an extensible derived type.
The class declaration is a signal that we intend to use
this variable in a polymorphic context. We will consider just the
pointer alternative for the time being.
Declared and dynamic type
The declaration
class (object_t), pointer :: obj => null()
allows the pointer obj to be associated with a target
which has a type object_t or any other type which extends
object_t. The pointer is said to be
type-compatible with this class of objects.
In the above example, the declared type of the polymorphic
pointer is object_t. The declared type does not change.
The dynamic type may be changed by associating the pointer with a target of an extended type, e.g. with:
class (object_t), pointer :: obj => null()
type (sphere_t), target :: s
obj => s
the dynamic type would become sphere_t. The dynamic type
of an unassociated pointer is its declared type.
The polymorphic variable has access to components of only of the declared type, but not of its descendents. So
class (object_t), pointer :: obj => null()
type (sphere_t), target :: s
obj => s
obj%rho ! ok: rho is a component of the declared type
obj%a ! erroneous: radius `a` is component of the extended type
Polymorphic arguments
Dynamic type will also be relevant when procedures are considered: polymorphic dummy arguments take on the dynamic type of the associated actual argument.
Exercise (5 minutes)
Declared vs dynamic type
Compile the second example together with your updated
object_type.f90 which includes a
charged_sphere_t:
$ ftn example2.f90 object_type.f90
(or use the canned solution object_types.f90).
Attempting to compile this code should fail with errors
$example2.f90:20:34:
20 | print *, "object radius ", p%a
| 1
Error: 'a' at (1) is not a member of the 'object_t' structure
example2.f90:26:34:
26 | print *, "sphere radius ", p%a
| 1
Error: 'a' at (1) is not a member of the 'object_t' structure
example2.f90:29:34:
29 | print *, "sphere charge ", p%q
| 1
Error: 'q' at (1) is not a member of the 'object_t' structure gfortran exercise2.f90 object_type.f90
Declared vs dynamic type (continued)
Comment out (don’t remove for the time being) the erroneous statements from the example so it will compile.
Why can’t we just declare the pointer to be:
class (charged_sphere_t), pointer :: p
in this example? What is the compiler error if you try?
The charged_sphere_t is higher in the inheritance chain
than object_t, it therefore cannot be associated with the
base derived type.
example2.f90:16:2:
16 | p => obj
| 1
Error: Different types in pointer assignment at (1); attempted assignment of TYPE(object_t) to
CLASS(sphere_t)
Type selection
Code may detect the dynamic type of a polymorphic variable via use of
the select type construct, which allows appropriate action
to to taken depending on the dynamic type. This is similar to the simple
select case construct, where the behaviour is controlled by
the dynamic type of the selector, here p:
select type (p)
type is (charged_sphere_t)
print *, "Charge is ", p%q
class is (sphere_t)
print *, "Radius is ", p%a
class default
print *, "bare object_t"
end select
There are two forms of the so-called type guard statement:
type is and class is. They may be combined in
a single construct. The result depends on the following logic:
- if the dynamic type of the selector exactly matches a
type isblock, then that block is executed; - otherwise, if the dynamic type matches a
class isblock (i.e., it matches that type or a descendant) theclass isblock is executed; - otherwise, the
defaultblock is executed (if present).
So at most one block is executed for any given selector.
Type inquiry functions
There are a number of intrinsic type inquiry functions which take polymorphic arguments, and return a logical result depending on dynamic type.
extends_type_of(a, b)
returns .true. if the dynamic type of a is
an extension of b; and
same_type_as(a, b)
returns .true. if the dynamic types of both arguments
are equal.
Exercise (10 minutes)
Type selection
Write a subroutine in object_type.f90 which takes a
single polymorphic argument of object_t, and prints out all
the relevant components depending on the dynamic type of the actual
argument.
The body of the subroutine will look like
print *, "Select type: "
select type (p)
class is (object_t)
print *, "density ", p%rho
print *, "position ", p%x(:)
class is (sphere_t)
print *, "sphere radius ", p%a
class is (charged_sphere_t)
print *, "sphere charge ", p%q
class default
print *, "unknown"
end select
Type selection (continued)
Check your subroutine works by passing each different type in turn
from the example2.f90 program.
(Optional) Type constructors again
Write some generic constructors for object types which take different data types as arguments. For example, it might be a convenience to be able to specify the position as a three-vector of integers.
A possible implementation might be of the form
subroutine object_info(p)
class(object_t), pointer, intent(in) :: p
select type (p)
type is (charged_sphere_t)
print *, "Charge = ", p%q
class is (sphere_t)
print *, "Radius = ", p%a
class is (object_t)
print *, "Density = ", p%rho
print *, "Position = ", p%x
class default
print *, "Unknown object type!"
end select
end subroutine object_info
- Fortran supports polymorphism through the
classpointers and allocatable objects. - Types can be selected dynamically to support type-specific behaviours
Content from Type-bound procedures
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How can we associate behaviour with types?
- How can we specialise behaviour for extended types?
Objectives
- Define a type-bound procedure for a derived type
- Override a type-bound procedure for an extended type
- Add additional functionaly to extended types not present in the parent type
Many languages use the term “methods” to refer to functions or operations that are associated with particular objects.
Methods are referred to as type-bound procedures in Fortran. They provide a mechanism to perform operations which are appropriate for a given (dynamic) type.
Type-bound procedures
A type-bound procedure is a fixed entity associated with the
declaration of the type, and appears after the contains
statement in the definition. For example, consider again the simple
object_t. Say we wished to add a function to compute the
volume of an object:
type, public ::object_t
! ... components ...
contains
! ... procedure bindings ...
procedure, pass :: volume => object_volume
end type object_t
The pass attribute specified in the procedure binding
indicates that the object to which the procedure is bound will be passed
as the first argument of the call of the type-bound procedure. This
argument is called the passed object dummy argument.
Type-bound procedures
Note there is no pointer attribute: this is a
type-bound procedure.
We must provide the corresponding function function
function object_volume(self) result(volume)
class (object_t), intent(in) :: self
real :: volume
! ...
end function object_volume
Type-bound procedures are polymorphic
Where we might expect to see a type declaration for the
dummy argument (self), this has been replaced by
class. This is to allow that the call may be made in a
context in which the type has been extended, i.e., class
permits polymorphism.
To invoke the new type-bound procedure, e.g.:
type (object_t) :: a = object_t()
print *, "Volume is ", a%volume()
Here, the declaration of a remains type
rather than class. There is no polymorphism involved at
this point. The object a in this context is associated with
the passed object dummy argument (self).
Exercise (5 minutes)
Type-bound procedures
Using the template object_type.f90 add the type-bound
procedure volume() in the object_t type
(only). Provide an implementation which sets the volume to zero. Using
the accompanying program example1.f90 check you can call
new volume() procedure for an object_t.
$ ftn object_type.f90 example1.f90
The object_t type declaration should now look like
type, public :: object_t
real :: rho = 1.0 ! density
real :: x(3) = 0.0 ! position of centre of mass
contains
procedure, pass :: volume => object_volume
end type object_t
and the module should now contain
function object_volume(self) result(volume)
class (object_t), intent(in) :: self
real :: volume
volume = 0.0
end function object_volume
Type-bound procedures (continued)
Can you also use the same type-bound procedure for the sub-type
sphere_t?
Yes, as a derived type of object_t, objects of type
sphere_t can call their methods. Does always returning
volume()=0 make sense for a sphere_t?
Overriding a specific procedure
Types which extend a base type will inherit type-bound procedures
from their parent. If we wish to relate a different volume computation
with a given object type, we may override the
object_volume() implementation with a new
implementation.
This can be done by merely re-declaring the procedure binding in the extending type with the same name, e.g.,:
type, extends(object_t), public :: sphere_t
! ...
contains
procedure, pass, non_overridable :: volume => sphere_volume
end type sphere_t
Here, the non_overridable attribute to the binding
specifies that the same name cannot be further overridden by types which
extend sphere_t.
An overriding procedure must have exactly the same interface as the relevant procedure in the parent type, bar the type of the object reference itself.
Exercise (5 minutes)
Overriding type-bound procedures
Check you can add this specific function in the sphere_t
type. Re-run the example1.f90 to check that objects of
different type obtain an appropriate result.
Define an appropriate function for computing the volume of a sphere
function sphere_volume(self) result(volume)
class (sphere_t), intent(in) :: self
real :: volume
volume = (4.0/3.0)*(4.0*atan(1.0))*self%a**3
end function sphere_volume
and bind this to the sphere_t type
type, extends(object_t), public :: sphere_t
real :: a = 1.0 ! radius
contains
procedure, pass, non_overridable :: volume => sphere_volume
end type sphere_t
Overriding type-bound procedures (continued)
What happens if you try to override the volume()
procedure in the charged_sphere_t having declared it
non_overridable?
The compiler prevents redefinition of the
non_overridable bound procedure.
$ gfortran -c object_type.f90
object_type.f90:23:14:
23 | procedure, pass :: volume => charged_sphere_volume
| 1
Error: 'volume' at (1) overrides a procedure binding declared NON_OVERRIDABLE
Binding attributes
The form of the type-bound procedure definition in this context is:
procedure [, binding-attr-list] :: type-bound-name [ => specific-name ]
where there is a comma-separated list of attributes which may include
- One of
publicorprivateindicating scope; -
non_overridableindicating thistype-bound-namemay not be re-defined in types which extend the current type; -
passornopass.
Passing self
The default is that type-bound-procedure receives the passed object dummy argument as the first dummy argument. There is an optional argument
pass [ (arg) ]
which can be used to specify which dummy argument is associated with the invoking object (required if it’s not the first dummy argument).
Generic type-bound procedures
If one wishes to overload type-bound procedures, an additional step is required:
type, public :: my_type
! ...
contains
procedure, pass :: add_real32
procedure, pass :: add_real64
generic, public :: add => add_real32, add_real64
end type my_type
Here, the single generic name add is intended to be
used, and must be associated with distinguishable alternatives. As
before, this may be used for assignment, operators, generic names, or
derived type i/o.
Procedure pointer as type component
It is possible to declare a type which has a procedure pointer as a component:
type, public :: my_pp_t
procedure (interface_pp), pointer :: p
end type my_pp_t
Instance data
Such a procedure pointer is part of the data associated with an instance of a type. Different instances of the object may contain pointers to different procedures as the value is determined at run time. Note that an interface definition may be required in this context.
Exercise (5 minutes)
Computing the mass of a sphere
Add an extra type-bound procedure to the sphere_t to
compute the sphere’s mass using the density and the type-bound
volume() function. Check this produces a reasonable
result.
Check that this works for the charged_sphere_t too.
As before, define an appropriate function to compute the sphere mass
function sphere_mass(self) result(mass)
class (sphere_t), intent(in) :: self
real :: mass
mass = self%rho*self%volume()
end function sphere_mass
and bind this to the sphere_t type
type, extends(object_t), public :: sphere_t
real :: a = 1.0 ! radius
contains
procedure, pass, non_overridable :: volume => sphere_volume
procedure, pass :: mass => sphere_mass
end type sphere_t
Note that the new procedure reuses the volume()
computation defined previously.
- Type-bound procedures allow Fortran objects to implement behaviour
- Redefining type-bound procedures allows behaviour to be specialised
- Types can prevent extended types from redefining type-bound procedures
- Generic type-bound procedures enable generic methods on types
Content from Generic input/output for derived types
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How can we customise the writing/reading of defined types?
Objectives
- Understand how types are written/read by default
- Be able to write a custom type-bound writer/reader for generic I/O
Default input/output
List-directed output for derived types can be used to provide a default output in which each component appears in order, schematically:
type (my_type) :: a
! ...
write (*, fmt = *) a
will write out all the components with some default format for the given components.
Alternatively, if we know the components, we could write out each component separately with an appropriate format
write (*, fmt = "(i4)") a%int1
write (*, fmt = "(f5.3)") a%real1
If we wish to control the output format, without needing to write each component individually, we can customise the I/O using type-bound procedures.
Non-default input/output
A facility for the user to specify the behaviour of input/output via an edit descriptor is provided by means of a type-bound procedure.
For formatted i/o, a special dt edit-descriptor exists,
of the form:
dt[iodesc-string][(v-list)]
where the iodesc-string is a string, and the v-list is a
series of integers. For example, we may have
dt" my-type: "(2,14)
The iodesc-string string and v-list array
of integers will re-appear as arguments to a type-bound procedure which
must be provided by the programmer.
The following generic procedures may be defined for read or write actions:
read (formatted)
read (unformatted)
write (formatted)
write (unformatted)
These provide a mechanism to define a procedure with the relevant interface
- a
formattedinterface for formatted i/o; - an
unformattedinterface to specify unformatted i/o.
The relevant procedure would then be called if an item of that type
appeared in the io list for a read() or a
write() statement.
Interfaces
Unformatted output
If we consider the type my_type, the unformatted output
implementation requires
subroutine my_type_unformatted_output(self, unit, iostat, iomsg)
class (my_type), intent(in) :: self ! object
integer, intent(in) :: unit ! Fortran unit number
integer, intent(out) :: iostat ! error condition
character (len = *), intent(inout) :: iomsg ! error message
! ... implementation ...
end subroutine my_type_unformatted_output
The iostat and iomsg variables take on
their usual meaning in the context of write(). The
iostat variable is zero on success, but should take a
positive value if an error occurs. If iostat is non-zero,
iomsg should contain a short message for the user on the
reason for the error.
For input the only difference is that the passed object dummy
argument should be of intent(inout).
Formatted output
The formatted case includes the addition of
iodesc-string and v-list arguments:
subroutine my_type_write_formatted(self, unit, iotype, vlist, iostat, iomsg)
class (my_type), intent(in) :: self
integer, intent(in) :: unit
character (len = *), intent(in) :: iotype ! "DT my-type: "
integer, intent(in) :: vlist(:) ! (2,14)
integer, intent(out) :: iostat
character (len = *), intent(inout) :: iomsg
! ... process arguments to give required output to unit number ...
! iotype is "LISTDIRECTED" for list directed io
! iotype is "DTdesc-string" for dt edit descriptor
! ...
! ... write (unit = unit, fmt = ...) components ...
! iostat and iomsg should be set if there is an error
end subroutine my_type_write_formatted
Both parts of the dt edit descriptor are optional in the
format specification. If the iodesc-string is missed, the
corresponding dummy argument iotype is of length zero; if
the v-list is omitted, then vlist has size
zero.
Again, the only difference for input is the intent of the passed
object dummy argument, which must be intent(inout).
Type-bound procedures
The two procedures above should be declared as generic type-bound procedures
type, public :: my_type
! ... components ...
contains
procedure :: my_type_write_formatted
procedure :: my_type_write_unformatted
generic :: write(formatted) => my_type_write_formatted
generic :: write(unformatted) => my_type_write_unformatted
end type my_type
Some care may be required to write a robust set of procedures handling the different formatted and unformatted cases.
No additional file operations involving the specified unit number are
allowed in the type-bound procedures. Procedures for reading must only
invoke read() and procedures for writing only invoke
write().
Exercise (20 minutes)
Implementing generic I/O
Try implementing the generic write(formatted) procedure
for the following type:
type, public :: my_date
integer :: day = 1 ! day 1-31
integer :: month = 1 ! month 1-12
integer :: year = 1900 ! year
end type my_date
First, try list directed I/O: the format we would like is
dd/mm/yyyy for 01/12/1999 for 1st December
1999.
A program date_program.f90 and module
date_module.f90 template are provided.
First we implement a type-bound procedure for the date object that
will print it in dd/mm/yyyy format
subroutine write_my_type(self, unit, iotype, vlist, iostat, iomsg)
class (my_type), intent(in) :: self
integer, intent(in) :: unit
character (len = *), intent(in) :: iotype
integer, intent(in) :: vlist(:)
integer, intent(out) :: iostat
character (len = *), intent(inout) :: iomsg
character (len = *), parameter :: dfmt = "(i2.2,'/',i2.2,'/',i4)"
iostat = 0 ! we will assume no errors occur (iomsg is unchanged)
write(unit, fmt = dfmt, iostat = iostat) self%day, self%month, self%year
end subroutine write_my_type
where the format string dfmt controls the formatting.
You should be able to confirm this works with the
date_program.
Implementing generic I/O (continued)
Then try adding the dt edit descriptor to allow some
more flexibility. For example, a vlist of 3 integers might
control the width of the fields for each part of the date. This requires
constructing an appropriate format string.
Extending our earlier solution we can handle both the list directed, and the format descriptor cases
if (iotype == "LISTDIRECTED") then
write(unit, fmt = dfmt, iostat = iostat) self%day, self%month, self%year
else
! A dt-style format
! We will only consider the case of vlist(3)
if (size(vlist) == 3) then
block
character (len = 20) :: userfmt
write (userfmt, "(a,i1,a,i2,a,i1,a)") &
"(i", vlist(1), ",a", vlist(2), ",i", vlist(3), ")"
write(unit, userfmt, iostat = iostat) &
self%day, months(self%month), self%year
end block
else
! Handle other conditions; we will just use the default format
write (unit, dfmt, iostat = iostat) self%day, self%month, self%year
end if
end if
using the default list directed format as a fallback. The
userfmt format string controls the display of the date, try
changing some of the widths passed.
- Defined types can control how they are printed using type-bound procedures
Content from Abstract types
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How can we write programs that abstract over implementations?
Objectives
- Understand the use of abstraction in Fortran
- Be able to write and extend an abstract type and a concrete implementation
The ability to have abstraction in our programs is really the feature that allows one to write flexible and extensible software.
Defining an abstract type
An abstract type is defined with the abstract attribute,
schematically:
type, abstract, public :: my_abstract_t
! ... usually has no components ...
contains
procedure (interface1), pass, deferred :: binding1
procedure (interface2), pass, deferred :: binding2
! ... and so on ...
end type my_abstract_t
abstract interface
! ... relevant for interface1 and so on ...
end interface
Abstract types define only interfaces
It is often the case that an abstract type defines only interface, or supported behaviours, and not components (a lack of concrete components can be considered a characteristic of an abstract entity).
The deferred attribute in the procedure
declarations indicates that the actual implementation is yet to be
specified (cf. virtual in C++). Only abstract types may have
deferred attribute for procedures. Interface blocks -
typically abstract - must be provided for each type-bound procedure.
This is done in the same way as for non-abstract types as we have seen
before.
An object of an abstract type is not permitted:
type (my_abstract_t) :: a ! erroneous
A polymorphic pointer must be used:
class (my_abstract_t), pointer :: a ! ok. pointer to abstract type
A concrete implementation
A concrete implementation would extend the abstract type
type, extends(my_abstract_t), public :: my_concrete_t
private
! ... implementation often private ...
contains
procedure :: binding1 => my_implementation1
procedute :: binding2 => my_implementation2
! ... and so on ...
end type my_concrete_t
and would have to provide appropriate implementations for the deferred procedures consistent with the relevant interface.
Concrete types cannot have
deferred procedures
A concrete type extending an abstract type must implement any remaining deferred procedures.
A type extending an abstract type may itself be abstract, and the
definitions of its type-bounds procedures can remain
deferred, but could also be implemented in the abstract
type.
Constructor
As usual, a concrete type would typically provide some way to instantiate itself. Schematically,
function my_concrete_type_create(...) result(p)
! ... arguments ...
type (my_concrete_t), pointer :: p
allocate(p)
...
end function my_concrete_type_create
And then, schematically,
class (my_abstract_t), pointer :: a => null()
a => my_concrete_type_create(...)
call a%binding1(...)
As a is type-compatible with extending types, and we are
able to write code in which we do not care about the details of the
implementation, but only access the public (abstract) interface. This
excepts the constructor itself.
An object type again
Suppose we wished to refactor our object_t from the
previous section to be an abstract type. We wish to provide a type-bound
procedure to compute the volume of different objects.
type, abstract, public :: object_t
! ... no components here
contains
procedure (if_volume), pass, deferred :: volume
end type object_t
We need to specify an interface for the volume procedure, which will use the passed object dummy argument, and return a scalar real number:
abstract interface
function if_volume(self) result(volume)
import object_t
class (object_t), intent(in) :: self
real :: volume
end function if_volume
end interface
Here, the function is declared with a name matching the interface
name in the type definition. The procedure will ultimately be called
using the bound name volume().
As the interface block does not have access to the definitions from
the outside scope, we have used the import statement to
make the name object_t available to allow us to declare the
dummy variable.
Exercise (15 minutes)
Implementing an abstract writer
Suppose we have some data which we would like to be able to store in files of different formats. Such formats might be native Fortran formats, or might use libraries such as NetCDF or HDF5. For simplicity, we will restrict ourselves to Fortran output.
We could think of representing the act of storing data to a file with three separate stages:
- open the file with an appropriate file name;
- write the data to the file;
- close the file when complete.
This is an opportunity for an abstract type. We do not wish to specify the details of the data format at this point, just the three oparations involved.
Write a new module which contains an abstract class with three
deferred procedures. The abstract type might be called
file_writer_t. The three procedures can be functions or
subroutines. If functions, the interface we want is:
-
function f_open(self, filename) result(ierr)where filename is a string and the return value is an integer error code; -
function f_write(self, data) result(ierr)where the data should be, for simplicity, a rank 1 array of integers; -
function f_close(self) result(ierr)which closes the file.
(Subroutines would be similar, but with an intent(out)
integer error code.)
In all cases the passed object dummy argument should be
intent(inout) to allow that the internal state associated
with the file write can be updated. The data argument for
the f_write() function can be intent(in).
At this point you can check only that the module compiles successfully:
$ ftn -c file_writer_module.f90
Hint: it may be useful to open the file with
status = "replace" to prevent the need to delete files each
time before running the program we are working towards.
The abstract type requires a corresponding abstract interface for the
deferred procedures.
type, abstract, public :: file_writer_t
contains
procedure (if_open), pass, deferred :: open
procedure (if_write), pass, deferred :: write
procedure (if_close), pass, deferred :: close
end type file_writer_t
abstract interface
function if_open(self, filename) result(ierr)
import file_writer_t
class (file_writer_t), intent(inout) :: self
character (len = *), intent(in) :: filename
integer :: ierr
end function if_open
function if_write(self, data) result(ierr)
import file_writer_t
class (file_writer_t), intent(inout) :: self
integer, intent(in) :: data(:)
integer :: ierr
end function if_write
function if_close(self) result(ierr)
import file_writer_t
class (file_writer_t), intent(inout) :: self
integer :: ierr
end function if_close
end interface
A concrete implementation
A concrete implementation of the abstract object_t might
look like:
type, extends(object_t), public :: sphere_t
real, private :: a ! radius
contains
procedure, pass :: volume => sphere_volume
end type sphere_t
Notice the type is no longer abstract, and the procedure
definition is no longer deferred. In addition, onlt deferred definitions
require an interface specification.
The function sphere_volume() would be:
function sphere_volume(self) result(volume)
class (sphere_t), intent(in) :: self
real :: volume
volume = ...
end function sphere_volume
Implementation follows specification
This implementation must follow exactly the specification in the interface block, including the names of the dummy arguments.
Exercise (15 minutes)
Writing formatted data to the file
When your abstract definition of the file_writer_t is
compiling successfully, add a concrete implementation which just uses
Fortran formatted i/o to write the data to a file. What is the minimum
state we must keep in the component part to remember the file between
open(), write(), and close()
operations.
Write a short program to check you can use an object of the new type to write some test data to a file.
We need a concrete type to implement the interface definted by
file_writer_t
type, extends(file_writer_t), public :: file_formatted_writer_t
private
integer :: myunit
contains
procedure, pass :: open => open_formatted
procedure, pass :: write => write_formatted
procedure, pass :: close => close_formatted
end type file_formatted_writer_t
! ...
function open_formatted(self, filename) result(ierr)
class (file_formatted_writer_t), intent(inout) :: self
character (len = *), intent(in) :: filename
integer :: ierr
open (newunit = self%myunit, file = filename, form = 'formatted', &
status = "replace", action = 'write', iostat = ierr)
end function open_formatted
function write_formatted(self, data) result(ierr)
class (file_formatted_writer_t), intent(inout) :: self
integer, intent(in) :: data(:)
integer :: ierr
write (unit = self%myunit, fmt = *, iostat = ierr) data(:)
end function write_formatted
function close_formatted(self) result(ierr)
class (file_formatted_writer_t), intent(inout) :: self
integer :: ierr
close (unit = self%myunit, status = 'keep', iostat = ierr)
end function close_formatted
An example program then might look like
program example1
! Write a file using the concrete class for formatted output.
! Compile with: ftn file_module.f90 example1.f90
use file_module
implicit none
type (file_formatted_writer_t) :: f
integer :: data(4) = [ 3.0, 5.0, 7.0, 9.0 ]
integer :: ierr
ierr = f%open("file_formatted.dat")
ierr = f%write(data)
ierr = f%close()
end program example1
Two implementations
Let us say we now have two implementations of the
object_t abstract type which are “sphere_t” and
“cube_t”.
It would be attractive to be able to choose between the two in a simple way without worrying about the details of the specific implementations. Here we can use a polymorphic pointer to the abstract type.
Schematically
class (object_t), pointer :: obj => null()
obj => object_from_string("cube")
...
print *, "Volume is ", obj%volume()
where the object_from_string() function returns an
appropriate pointer to the object requested.
A function to return a pointer to one particular type might be
function cube_create() result(p)
class (cube_t), pointer :: p
allocate(p)
end function cube_create
Two such functions could be combined to return the polymorphic result
of the abstract type file_writer_t. Memory has been
allocated against p to instantiate the object.
Exercise (15 minutes)
An abstract program
Add a function in file_module.f90 to return a pointer
based on a string. This should allow at least one working
file_write_t implementation.
function create_file_formatted_writer_t() result(fp)
class (file_formatted_writer_t), pointer :: fp
allocate(fp)
end function create_file_formatted_writer_t
function file_writer_from_string(str) result(fp)
character (len = *), intent(in) :: str
class (file_writer_t), pointer :: fp
! We haven't reached typed allocation yet, hence the extra
! routines to return a pointer of the right type.
fp => null()
select case (str)
case ("formatted")
fp => create_file_formatted_writer_t()
case default
print *, "Not recognised ", str
end select
end function file_writer_from_string
An abstract program (continued)
Adjust your main program to be completely abstract.
program example1
! Write two files via abstract mechanism.
! Compile: ftn file_module.f90 example2.f90
use file_module
implicit none
class (file_writer_t), pointer :: f => null()
integer :: data(4) = [ 2, 4, 6, 8 ]
integer :: ierr
f => file_writer_from_string("formatted")
ierr = f%open("data_formatted.dat")
ierr = f%write(data)
ierr = f%close()
deallocate(f)
end program example1
An abstract program (continued)
(Optional) Implement an additional concrete implementation of
file_write_t for unformatted output, extend the program to
use both concrete types.
- Abstract types allow Fortran programs to specify and use interfaces that are then provided by concrete implementations.
Content from Break
Last updated on 2026-07-10 | Edit this page
Comfort break.
Content from Modules again
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How to organise large Fortran programs?
- How to minimise recompilation in large Fortran programs?
Objectives
- Understand how to separate modules into modules and submodules
- Understand the difference between
hostanduseassociation
Modules
The introductory course encouraged the use of modules to provide structure, and to separate public interface from private implementation. Schematically, we have seen:
module schematic
! Broadly, public "interface"
contains
! Broadly, private "implementation"
end module schematic
However, …
We also have a situation in which the (public) interface can remain unchanged, while a change to the (private) implementation requires recompilation of the module. This may have knock-on effects requiring re-compilation of any dependencies. Is this really necessary if we have not changed the interface?
The answer should be no. However, a typical makefile dependency might
cause re-compilation of all units use-ing the module
schematic. Some (rather baroque) mechanisms have been used
to avoid this, e.g., making a copy of the .mod file, and
interrogating whether this changes on compilation.
The separation of large modules into parts may be desirable, but can
necessitate the proliferation of public entities which might not really
be wanted. Cross-dependencies can also be a problem (the situation where
module a wants to use module b and
vice-versa).
Separate interface and implementation
This is not quite satisfactory. A clearer separation between interface and implementation (cf. C header files) is required.
Submodules
Submodules may be organised in a tree-like hierarchy, with the ancestor module as the root.
module example_module
end module example_module
The module should define only the interface (there should probably be
no contains statement).
The implementation can then be placed in a submodule:
submodule (example_module) example_submodule
contains
end submodule example_submodule
Host association
The submodule automatically has access to the declarations in the
example module via host association. There is no need to
use the ancestor module.
Formally, we have:
submodule (parent-identifier) submodule-name
[ specification part ]
[ module-subprogram-part]
end [submodule [ submodule-name] ]
where the parent-identifier is the name of the parent
module.
Submodule procedures
Suppose in our module we define a new data type:
type, public :: example_t
...
end type example_t
We can define an interface block using this type (also in the module) to specify the contract:
interface example_t
module function example_int_t(ival) result(e)
integer, intent(in) :: ival
type (example_t) :: e
end function example_int_t
end interface example_t
Note the module at the start of the
function declaration, and that there is no
import statement required in the interface block.
This would then be implemented in the submodule
subprogram part:
module function example_int_t(ival) result(e)
integer, intent(in) :: ival
type (example_t) :: e
...
end function example_t
Matching text
The interface and the function declaration must match precisely (only the name of the result variable is not formally part of the interface and need not match).
Exercise (2 minutes)
Compiling modules and submodules
Compile (do not link) the module and submodule files
example.f90 and example_a.f90 in the current
directory. E.g.,
$ ftn -c example_module.f90
$ ftn -c example_submodule.f90
Check what additional files have been generated (the exact details
will depend on the compiler: some produce .smod files,
others just additional .mod files).
Abbreviated form
It’s possible to omit the interface details in the submodule. E.g.,
module procedure example_t
...
end procedure example_t
Note the use of procedure here. Again, the details must
match the interface declared in the module.
A matter of taste
While this provides some saving in verbosity, it may be preferable to keep the full form.
Host association and use association
A submodule has access to entities in the parent module automatically
by host association, but may also use other modules in the
standard way.
Breaking circular dependencies
A submodule of module a can access module b
by use association. In addition, a submodule of
module b can access module a. This breaks the
circular dependency problem in standard modules.
Submodule declarations
Note that submodules do not contain public or
private declarations. However, additional variables, types,
or named constants may be defined in a submodule specification part.
These are neither public nor private, but can only be accessed in the
submodule and any descendants by host association.
Exercise (15 minutes)
Splitting a module into submodules
We will take the single module developed for the
file_writer_t and split it into a module containing the
abstract definition, and one or two implementations. A client program
should then only depend on the top-level (abstract) interface.
It’s probably easiest to make a number of copies of a working solution module, and then delete unwanted parts from each. Suggested procedure:
- Keep the abstract type
file_writer_tdefinition and its related interface block infile_module.f90. Here we will also need an interface for thefile_writer_from_string()function. Check the newfile_module.f90compiles on its own. - Make a new (ordinary) module
file_unformattedfor the unformatted implementation in a separate file, and a new modulefile_formattedfor the formatted implementation. - Add a submodule to
file_moduleto hold the implementation of thefile_writer_from_string()function in a separate file.
An example program which only has use file_module will
now depend only on the public definition of the abstract type.
All the Fortran sources should be compiled together to produce an executable.
What further decomposition into submodules could we make at this point?
- Submodules provide the mechanism to separate interface and implementation in Fortran
Content from Unlimited polymorphic entities
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How does Fortran support unknown data (at compile time) types?
Objectives
- Understand the use of unlimited polymorphic entities
- Implement a data structure capable of holding arbitrary data based on polymorphic entities
It is sometimes useful in C to be able to use a void *
pointer, a pointer for which there is no type-checking at compile time.
The nearest analogue in Fortran is the unlimited polymorphic
pointer.
class (*) pointers
We have seen a polymorphic pointer of given class used in the context of type extension. A more general type of pointer, an unlimited polymorphic pointer can be declared as
class (*), pointer :: p => null()
This is particularly useful if a polymorphic reference of intrinsic types (which cannot be extended) is required. For example:
real (real32), target :: r32
real (real64), target :: r64
real (real32), pointer :: p32 => null()
real (real64), pointer :: p64 => null()
class (*), pointer :: p => null()
p32 => r32 ! ok
p64 => r32 ! compile-time error
p => r32 ! ok
p => r64 ! ok
While we cannot associate a pointer of a fixed type
(p64) with a target of incompatible type, we can with an
unlimited polymorphic pointer.
However, unlike the type-specific pointer, we cannot use the unlimited polymorphic pointer in any context, e.g.,:
p64 = 2.0d0 ! normal assignment ok
p = 2.0d0 ! compile-time error
Pointer assignments are valid
p => p32
p => p64
If an unlimited polymorphic pointer is on the right-hand side of an assignment, then the left-hand side must be a pointer to a non-extensible derived type. E.g.,
p32 => p
is not valid, as this allows an association to an incompatible type.
Exercise (2 minutes)
Compiling pointers
The accompanying code example1.f90 has three invalid
assignments which will not compile, and one additional error. Check the
compiler messages for each.
$ gfortran example1.f90
example1.f90:14:2:
14 | p32 => r64
| 1
Error: Different types in pointer assignment at (1); attempted assignment of REAL(8) to REAL(4)
example1.f90:19:2:
19 | p = 0.0
| 1
Error: Nonallocatable variable must not be polymorphic in intrinsic assignment at (1) - check that there is a matching specific subroutine for '=' operator
example1.f90:21:2:
21 | p32 => p
| 1
Error: Data-pointer-object at (1) must be unlimited polymorphic, or of a type with the BIND or SEQUENCE attribute, to be compatible with an unlimited polymorphic target
example1.f90:24:23:
24 | print *, "Result ", p
| 1
Error: Data transfer element at (1) cannot be polymorphic unless it is processed by a defined input/output procedure
Use of type guards
Recall that we can use a select type construct with
appropriate type gaurds to provide the compiler with concrete
information to deal with the dynamic type. This is appropriate for
unlimited polymorphic pointers, schematically:
class (*), pointer :: p
select type (p)
type is (real(real32))
! ... action for real32 ...
type is (real(real64))
! ... action for real64 ...
end select
Typed allocation and sourced allocation
Unlimited polymorphic pointers may be used as actual arguments to
procedures. One relevant intrinsic case is allocate().
Typed allocation has a form similar to a constructor which specifies the
type:
allocate(type-spec :: alloc-list)
This allows allocations against unlimmited polymorphic pointers, e.g.,
class (*), pointer :: p => null()
class (*), pointer :: r(:) => null()
allocate(character (len = 9) :: p)
allocate(real (real32) :: r(7))
A useful step in many circumstances is to use sourced allocation:
class (*), pointer :: p => null()
allocate(p, source = a)
which will produce a copy of a; p will take
on the dynamic type of a.
Exercise (20 minutes)
A key value pair
The accompanying template module key_value_module.f90
provides a derived type that is intended to store key value pairs, where
the key is a (deferred length) string, and the value is an unlimited
polymorphic pointer.
type, public :: key_value_t
character (len = :), allocatable :: key
class (*), pointer :: val
end type key_value_t
In principle, this can store values of any type.
Implement three specific constructors to establish key-value pairs
for integer and real intrinsic types (int32 and
real32 from iso_fortran_env), and for strings.
Each should allocate appropriate memory for the key and the value. These
should overload the default structure constructor
key_value_t().
Implement a subroutine key_value_print() which uses a
select type construct to display the current type, key and
value of the three different data types.
To be complete, implement a routine to release the resources
associated with a key_value_t. We could use type-bound
procedures for these last two operations, but it’s not really necessary
in this context.
The accompanying program has some examples to act as a test.
Assumed type (F2018)
It is possible to use a non-pointer unlimited polymorphic dummy argument in a procedure, e.g.:
subroutine example(upe)
class (*), intent(in) :: upe
! ...
end subroutine example
This does not have the pointer attribute, but has similar constraints. Such an entity would allow us to replace the three specific constructors with one which took on the dynamic type of the actual argument. Try it. Do you think it’s a good idea?
A dynamic list of key value pairs
It might be useful to have an expandable list of such key value
pairs. A possible implementation is also provided in the
key_value_module.f90
The list constructor and a procedure to add a
key_value_t are provided. There’s also a routine to print
out the list contents. What remains to be provided is a subroutine which
increases the storage as required. This should use
move_alloc(). Have a go at providing this subroutine.
- Unlimited polymorphic entities provide a
void *-like type in Fortran - Without determining the unlimite polymorphic entity’s dynamic type, it cannot be used
Content from Lunch
Last updated on 2026-07-10 | Edit this page
Lunch break.
Content from Type parameters
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How to implement user-defined types with
kinds, like intrinsic types?
Objectives
- Understand the use of type parameters in user-defined types.
Type parameters for intrinsic types
We have seen type parameters (kind type parameters) for intrinsic types:
use iso_fortran_env
...
integer (int32) :: i32
This provides a way to control, parametrically, the actual data type associated with the variable. Such parameters must be known at compile time.
There are all so-called deferred parameters, such as the length of a deferred length string
character (len = :), allocatable :: string
The colon indicates the length is deferred.
Parameterised derived types
These features may be combined in a parameterised type definition, e.g.:
type, public :: my_array_t(kr, nlen)
integer, kind :: kr ! kind parameter
integer, len :: nlen ! len parameter
real (kr) :: data(nlen)
end type my_array_t
The type parameters must be integers and take one of two roles: a
kind parameter, which is used in the place of kind type
parameters, and a len parameter, which can be used in array
bound declaration or a length specificaition. A kind
parameter must be a compile-time constant, but a length parameter may be
deferred until run time.
The extra components act as normal components in that they may have a
default value specified in the declaration, and may be accessed at run
time via the component selector % in the usual way.
For example declaration of a variable of such a type might look like:
! ... establish len_input ...
type (my_array_t(real32, len_input)) :: a
Such a parameterised type may have a dummy argument associated with an actual argument via a dummy argument declaration, e.g.,
type (my_array_t(real32, nlen = *)), intent(in) :: a
cf.
character (len = *), intent(in) :: str
A pointer declaration of this type would use the deferred notation with the colon:
type (my_array_t(real32, nlen = :)), pointer p => null()
Here, an (optional) keyword has been used in the parameter list.
Example
The example code example1 illustrates how a
parameterised type can be defined and used
program exmaple1
use iso_fortran_env
implicit none
type :: my_array_t(kr, nlen)
integer, kind :: kr
integer, len :: nlen
real (kr) :: data(nlen)
end type my_array_t
integer :: kr
integer :: nlen
type (my_array_t(kr = real64, nlen = 1000)) :: a
print *, "kind a", a%kr
print *, "nlen a", a%nlen
write (*, "(a)", advance = "no") "nlen: "
read (*, *) nlen
call defer_me(nlen)
contains
subroutine defer_me(nlen)
integer, intent(in) :: nlen
type (my_array_t(kr = real32, nlen=nlen)) :: b
print *, "Length ", b%nlen
print *, "Shape ", shape(b%data)
end subroutine defer_me
end program exmaple1
The kind of the data held by
my_array_t%data must be known at compile time, but the
len of the array can be set dynamically as shown by
defer_me(). You can convince yourself of this by compiling
and running example1.f90 which will allow you to pass the
array length at runtime.
- The
kindof a user-defined type must be known at compile time (as for intrinsic types)
Content from Intrinsic modules
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- What intrinsic modules does Fortran provide?
- How can intrinsic modules aid writing portable code?
Objectives
- Be aware of the intrinsic modules in Fortran and their uses
Intrisic and non-intrinsic modules
Five intrinsic modules are provided by the implementation: we have
seen iso_fortran_env.
Three are related to IEEE arithmetic: ieee_arithmetic,
ieee_exceptions and ieee_features. The last is
iso_c_binding which concerns C/Fortran
interoperability.
A use statement may include, optionally, an intrinsic or
non_intrinsic attribute, e.g.,
use, intrinsic :: iso_fortran_env
This true intrinsic iso_fortran_env will then be used in
preference to any other module with the same name.
Likewise a non-intrinsic module can be used:
use, non_intrinsic :: iso_fortran_env
This would generate an error if a user-provided
iso_fortran_env were unavailable to the compiler.
Best practice is to always use an only clause with
intrinsic modules, which enumerates the names required in the current
scope. This is a general rule which limits “namespace pollution”, and
can be applied to your own modules too.
Intrinsic module iso_fortran_env
This module provides a number of symbolic constants and functions to provide information about the local Fortran implementation.
These include:
-
error_unit,input_unitandoutput_unit: the unit numbers associated with standard error, standard input, and standard output. -
iostat_endandiostat_eor: error codes for end-fo-file and end-of-record. -
character_storage_size,file_storage_size, andnumeric_storage_sizerelated to i/o
F2008 included kind type parameters for frequently used intrinsic data types:
-
int8,int16,int32,int64: integer kind type parameter for intrinsic integers (in bits); -
real32,real64,real128: integer kind type parameters for intrinsic real data types.
Lists of the kind types available for each intrinsic data type are
also available: integer_kinds, logical_kinds,
real_kinds, character_kinds.
Functions
Information on the compiler and the options at compile time can be
accessed via the functions: compiler_version() and
compiler_options(). Both return a string fixed at compile
time.
E.g.,
character (len = *), parameter :: compiler = compiler_version()
Example (3 minutes)
iso_fortran_env values
An example program is provided which prints out the values of the
various symbols available from iso_fortran_env.
$ ftn print_iso_fortran_env.f90
To see the actual values in the kind arrays
(integer_kinds and so on) some extra statements would be
required.
IEEE arithmetic support
Fortran provides support for programmers to inquire about support for various aspects of the IEEE standard for floating point arithmetic. This information is separated into three modules.
ieee_exceptionsprovides handling for IEEE exception types enumerated by the values:ieee_overflow,ieee_divide_by_zero,ieee_invalid,ieee_underflowandieee_inexact.ieee_arithmeticcontains functionality for identifying different classes of floating point values (e.g.,ieee_negative_inf), rounding modes, and so on. Theieee_arithmeticmodule includeieee_exceptions.-
ieee_featuresdoes not provide names a such by may affect compilation if present. E.g., ifuse, intrinsic :: ieee_features, only : ieee_subnormalis present, then the program unit must provide support for subnormal floating point numbers (aka denormalised numbers, with a leading zero in the mantissa).
A full description of the IEEE features will not be attempted here.
- The
iso_fortran_envintrinsic module provides constants and functions that enable code portability - The IEEE intrinsic modules allow programs to check the conformance with IEEE features
Content from Break
Last updated on 2026-07-10 | Edit this page
Comfort break.
Content from Interoperability with C
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How can we write Fortran programs that interact with C in a portable way?
- How can we make use of Fortran procedures from C code?
Objectives
- Understand the use of the
iso_c_bindingintrinsic module. - Write C/Fortran programs that call Fortran/C procedures using
iso_c_binding.
Fortran 2003 introduced facilities to allow a Fortran program to interact with C in a standardised way.
The intrinsic module iso_c_binding
Fortran has introduced the idea of interoperable entities, which have declarations which have an analogue in C. (Strictly C, not C++. However, as there is a subset of C which is also valid C++, one can also communicate with C++.)
Facilities for ensuring that data objects and procedures are
interoperable are provided by the intrinsic module
iso_c_binding
Numeric data types
For integer, real and complex
types, iso_c_binding provides names for constants which are
the relevant kind parameters for Fortran. For example, integer
interoperable types include:
! declaration ... interoperable with ...
integer (c_int) :: i_int ! C "int"
integer (c_short) :: i_short_int ! C "short int"
integer (c_long) :: i_long ! C "long int"
integer (c_size_t) :: i_size_t ! C "size_t
The value can be -1 (no interoperable Fortran type) or -2 (no
corresponding C type). Recall size_t is usually an unsigned
type in C. There is still no direct analogue of unsigned types in
Fortran, although an interoperable type is almost certainly
available.
For real types, the full list is:
! declaration ... interoperable with ...
real (c_float) :: r_float ! C "float"
real (c_double) :: r_double ! C "double"
real (c_long_double) :: r_long_double ! C "long double"
If the value of the C name is -1 (e.g., c_long_double)
then Fortran does not provide a precision equal to that of C type
(long double). Other negative values indicate
unavailabillity for different reasons.
For complex types, the full list is:
! declaration ... interoperable with ...
complex (c_float_complex) :: z_float_complex ! C "float _Complex"
complex (c_double_complex) :: z_double_complex ! C "double _Complex"
complex (c_long_double_complex) :: z_ldc ! C "long double _Complex"
The precision for complex numbers relates to the precision for each of the real and imaginary parts.
Logical data types
There is one interoperable logical type:
! declaration
logical (c_bool) :: logical_c_bool ! C "_Bool"
As logical operations in C are often performed using integer types, an integer type may be appropriate in a given context.
Example (1 minute)
iso_c_binding symbols
A program is provided which prints out a full list of the symbols
from iso_c_binding illustrated above.
$ ftn print_iso_c_binding.f90
Characters and strings
An interoperable character type is expected to be available:
! declaration ... interoperable with ...
character (kind = c_char, len = 1) :: char ! C "char"
To pass strings to C, rather than single characters, it is usually
necessary to think about a string in Fortran as being an assumed size
array of characters of len = 1.
Named constants for the following C special characters are provided:
c_null_char (\0), c_alert
(\a), c_backspace (\b),
c_form_feed (\f),c_new_line
(\n), c_carriage_return (\r),
c_horizontal_tab (\t), and
c_vertical_tab (\v).
Fortran code receiving strings from C may wish to discard the
c_null_char at the end of the string.
Calling C from Fortran
Suppose we wish to call the standard C library function
int atoi(const char * str);
from Fortran. To do this, we will write a Fortran interface to reflect the need for interoperable arguments. This might be:
interface
function c_atoi(str) bind(c, name = "atoi") result(i)
use, intrinsic :: iso_c_binding, only : c_char, c_int
character (kind = c_char, len = 1), intent(in) :: str(*)
integer (kind = c_int) :: i
end function c_atoi
end interface
The bind(c) declaration indicates that this interface
relates to a C function. If the name is present, it can be
used to associate the C name with the Fortran interface declaration. If
the name is not present, the C name will be taken to be the
Fortran name (in lower case). Here, the function could be called from
Fortran via, e.g.,
integer (c_int) :: i
i = c_atoi("-23")
The arguments of the function, and the return type, should all be relevant interoperable types.
A C function with void return type should have an
interface defining a subroutine rather than a function.
Arguments passed by value
Many C functions have non-pointer dummy arguments, e.g.,
double hypot(double x, double y);
where actual arguments would be passed by value.
An appropriate Fortran interface can provide information on such
scalar arguments via the value attribute:
interface
function c_hypot(x, y) bind(c, name = "hypot") result(z)
use iso_c_binding, only : c_double
real (c_double), value :: x, y
real (c_double) :: z
end function c_hypot
end interface
The value attribute takes the place of
intent(in) (they cannot occur together). If a scalar
argument does not have the value attribute, it means that C
expects a pointer.
The value attribute can also be used in a normal Fortran
context, where it is a signal to the compiler that the value should be
copied in, whereon it may be changed, but not copied out again.
Example (10 minutes)
Calling C from Fortran
Suppose we have a C function with prototype
int c_snprintf_float(char * str, size_t nsz, const char * format, float x);
which we wish to call from Fortran. The function uses the standard C
library call snprintf() to write the value of the
float argument to a string str with C format
specification format. The maximum number of characters to
be written is nsz. The return value is the number of
characters actually written to the string (not including the
\0 terminating character).
The C function is supplied in the current directory; it needs to be compiled (not linked) with the relevant C compiler, e.g. on ARCHER2,
$ cc -c c_snprintf.c
which will produce an object c_snprintf.o.
Write a program which includes an interface which allows the C
function to be called with appropriate arguments. If the program is
called f_snprintf.f90, we should be able to compile this
with the C object via:
$ ftn c_snprintf.o f_snprintf.f90
Note this is the Fortran compiler.
What can you say about the length of the string returned in Fortran compared with the number of characters written indicated by the return value?
You will need to provide an interface like so:
interface
function f_snprintf_float(str, sz, cformat, x) &
bind(c, name = "c_snprintf_float") result(nchar)
use iso_c_binding, only : c_int, c_char, c_size_t, c_float
character (kind = c_char, len = 1), intent(out) :: str(*)
integer (kind = c_size_t), value, intent(in) :: sz
character (kind = c_char, len = 1), intent(in) :: cformat(*)
real (kind = c_float), value, intent(in) :: x
integer (kind = c_int) :: nchar
end function f_snprintf_float
function f_snprintf_double(str, sz, cformat, x) &
bind(c, name = "c_snprintf_double") result(nchar)
use iso_c_binding, only : c_int, c_char, c_size_t, c_double
character (kind = c_char, len = 1), intent(out) :: str(*)
integer (kind = c_size_t), value, intent(in) :: sz
character (kind = c_char, len = 1), intent(in) :: cformat(*)
real (kind = c_double), value, intent(in) :: x
integer (kind = c_int) :: nchar
end function f_snprintf_double
end interface
In order to make use of it, you should declare the variables that will be the actual arguments using the same interoperable types.
According to the Fortran, the returned string has length of 6,
whereas the string legnth reported by the returned value from
snprintf is 5. The extra is the null character used by C to
terminate strings.
An example program called f_snprintf.f90 is provided.
Calling C from Fortran (continued)
If you were to implement interfaces for both the
c_snprintf_float() and c_snprintf_double()
versions, you might wonder whether you could overload the specific names
with a generic name. It seems like this should be possible, but all
attempts currently fail with the compiler unable to resolve which
specific interface it should use from the generic.
Arrays
Fortran arrays of non-zero size are interoperable if the data type is
interoperable, and the array has an explicit shape or an assumed size
(the * notation).
As an example of an explicit shape consider the C function with prototype
void c_array1(int nlen, double * data);
As usual, one would expect the array to be indexed from zero in C.
An appropriate Fortran interface might be
interface
subroutine c_array1(nlen, data) bind(c)
use iso_c_binding, only : c_int, c_double
integer (c_int), value :: nlen
real (c_double), intent(inout) :: data(nlen)
end subroutine c_array1
end interface
A Fortran allocatable array should be declared as assumed size in the interface. The relevant current size of the allocation will typically need to be passed as well, as above.
For arrays of rank 2, we must remember that the array element order is reversed in C relative to Fortran. That is, the Fortran declaration
integer (c_int) :: array(m, n)
would need to correspond an array array[][m] or
array[n][m] to be interoperable. The Fortran interface
would specify dimension (m,*) or
dimension(m,n), respectively.
Exercise (10 minutes)
Passing arrays to C
The accompanying C file c_array.c holds a function with
prototype
void c_array(int mlen, int nlen, int [][mlen]);
intended to be interoperable with a Fortran array declared
(mlen,nlen). The C function simply prints out the values of
the elements.
Write a Fortran program that passes a small array of shape
(2,3) to the C function. Initialise the values consistent
with Fortran array element order (e.g., indicative of increasing
address). Does what you see make sense?
If you declare the array in C as described and in your Fortran interface to the C function declare it as assumed size
integer (kind = c_int), intent(in) :: idata(mlen, *)
then you should receive output like the following:
OUTPUT
Element [0][0] 0 1
Element [0][1] 1 2
Element [1][0] 2 3
Element [1][1] 3 4
Element [2][0] 4 5
Element [2][1] 5 6
If we insist on visualising the array as a matrix, then in C it appears to be transposed. This is not actually correct – the memory layout is unchanged, and we reverse the order of indices while in C code in order to use it as we did in Fortran.
Sample solution code is available in the solutions
directory in f_array.f90.
Pointers
Derived types c_ptr and c_funptr are
provided for interoperability with C pointer types. These shouldn’t be
assigned to directly, but instead a number of functions are provided to
manage the translation of Fortran entities to and from these new
types.
c_loc(x)returns thec_ptrtype which C can use as the address of the argument. The argument can be a scalar, a contiguous array of non-zero size (or allocated non-zero size), or an associated pointer. The argument must be of interoperable type. The argument must be a pointer or a data object with target attribute.c_funloc(p)can return thec_funptraddress of an interoperable procedure.c_associated(c_ptr1 [, c_ptr2])is an analogue of theassociated()intrinsic which returns.true.if the first argument is notc_null_ptr. If the second argument is present, the function will return.true.if both arguments are the same.c_f_pointer(c_ptr, fptr [, shape])provides functionality to translate ac_ptrtype into a Fortran pointer. A rank 1 integer array shape is required iffptris an array.c_f_procpointer(c_funptr, fptr)provides similar functionality for a procedure pointer.
Derived types
To be interoperable, a Fortran derived type must map to a plain C
struct with interoperable components. This means the Fortran type must
have no type-bound procedures, cannot be extended, and cannot have
components that have either the allocatable or
pointer attributes,
The type should be declared as bind(c), e.g.,
type, bind(c), public :: my_type
integer (c_int) :: icomponent
real (c_float) :: fcomponent
end type my_type
The presence of the bind(c) means the type cannot be
extended.
Calling Fortran from C
Let us suppose we have a C program which defines a
struct
typedef struct array_s {
int nlen;
float * data;
} array_t;
to aggregate the information on an array of float which
is to be allocated by the program. Further, we wish to call a subroutine
declared in C as
void f_subroutine(const array_t * a);
which we wish to write in Fortran.
A corresponding subroutine in Fortran requires the definition of the interoperable structure, i.e.,
type, bind(c) :: array_t
integer (c_int) :: nlen
type (c_ptr) :: data
end type array_t
where we have used a c_ptr type to represent the C
pointer component.
An interoperable subroutine declared bind(c) might
be
subroutine f_subroutine(a) bind(c)
type (array_t), intent(in) :: a
real (c_float), pointer :: data(:)
call c_f_pointer(a%data, data, [ a%nlen ])
! ... perform operations with data(:) ...
end subroutine f_subroutine
The information that the C data is of type float appears
in the declaration of the Fortran pointer used to access the array. This
must be initialised by a call to c_f_pointer() before it
can be used.
Exercise (10 minutes)
Calling Fortran from C
Check you can construct a working example based on the above outline. Initialise some sample values and check you can recover the values in the Fortran subroutine.
Try using either the C or the Fortran compiler to perform the link stage.
The Fortran type and subroutine can be placed in a module. Both need
to be interoperable with C, meaning they should use
bind(c). In the C code provide the struct, the
extern void declaration of the Fortran subroutine, then
write a brief program to set some values in the struct’s
array. In the Fortran subroutine, you can print the array (after
retrieving a usable pointer to it via c_f_pointer()) and
check it’s correct.
Example solutions are available in
exercises/14-interoperability-with-c/solutions as c_struct_to_fortran.c
and f_array_t.f90.
Using the GCC compilers, you should be able to compile the code as follows:
OUTPUT
ftn -c f_array_t.f90
cc -c c_struct_to_fortran.c
ftn f_array_t.o c_struct_to_fortran.o
You can also compile in two steps as long as you tell the C compiler
that it will need to use libgfortran.so in order to link
the symbols from f_array_t.o.
OUTPUT
ftn -c f_array_t.f90
cc -lgfortran f_array_t.o c_struct_to_fortran.c
- The
iso_c_bindingmodule allows a programmer to write Fortran that is interoperable with C. - Care must be taken with pointers and arrays, and with variables which are to be passed to C by value.
Comment on the use of submodules
Submodules may only really come into their own if you have an extremely large software project for which the overheads of re-compilation are high, or the organisation of extremely large single modules becomes a problem. For small exercises, such as those presented in this course, the additional files involved are probably an unnecessary distraction. So we will only use them on one occasion.