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

BASH

login.archer2.ac.uk

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.

Key Points
  • 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.:

FORTRAN

  integer, dimension(10, 2) :: a
  integer, dimension(10, 2) :: b, c, d, e(10, 3)

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:

FORTRAN

  integer, dimension(10, 2) :: a
  integer                   :: b(10, 2)

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:

FORTRAN

  integer :: a(5)
  real    :: b(10, 2)

  a(1:5:2)          ! an array section   referencing elements 1, 3, and 5
  a( [1, 3, 5] )    ! a vector subscript referencing elements 1, 3, and 5
  b(1:5, 1)         ! a rank one section of size 5
  b(1:2, 1:2)       ! a rank two section of shape (2, 2)

Limit on number of ranks

Arrays of rank up to 7 were supported (pre-F2008); this was increased to a limit of 15 at F2008. F2018 introduced an intrinsic rank() inquiry function which returns the scalar integer rank of the array argument.

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:

FORTRAN

data data-statement-set [[,] data-statement-set] ...

where the data-statement-set consists of pairs of

FORTRAN

data-statement-object-list / data-statement-value-list /

That is, one associates a list of values with a list of variables, e.g.:

FORTRAN

real a, b, c
data a, b, c / 1.0, 2.0, 3.0 /

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.:

FORTRAN

   real, allocatable :: a(:)
   ! ...
   allocate(a(1:nlen))
   ! ...
   deallocate(a)

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:

FORTRAN

  a = [ a(:), 4.0, 5.0, 6.0 ]    ! Append to the existing elements

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:

FORTRAN

  integer :: a(0)      ! a zero-sized array
  integer :: b(0:0)    ! an array with one element b(0)

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)


Challenge

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.,

BASH

$ ftn problem1.f90

The following print statements will perform the actions the comments ask for:

FORTRAN

print *, a([1, 2, 7])    ! => 1 2 7
print *, a(::2)          ! => 1 3 5 7 9
print *, b(:, [1, 3])    ! => 1 2 5 6

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)


Discussion

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:

BASH

$ ftn -hbounds bounds-compile-time.f90

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.

BASH

$ ftn -hbounds bounds-run-time.f90

Check what happens if the tests are repeated using programs compiled without bounds checking. What are the possible dangers?

Key Points
  • 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 allocatable and intent(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:

FORTRAN

  subroutine array_operation(nmax, a)

    integer, intent(in) :: nmax
    real,    intent(in) :: a(nmax)

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:

FORTRAN

  subroutine array_operation(a)

    real, intent(in) :: a(:)

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:

FORTRAN

   real :: a(0:nmax)

   call array_operation(a)

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)

Challenge

Array arguments bounds

As a simple illustration of this problem, the following example should print out the values 0.0, 1.0, ... (not 1.0, 2.0, ...):

BASH

$ ftn example1.f90

What needs to be done to correct the situation (and we want to keep the declaration a(0:3))? Correct the program.

Replacing the dummy argument declaration with

FORTRAN

real, intent(inout) :: array(0:)

will set the lbound of the array variable to 0 with the ubound set automatically to preserve the shape.

Challenge

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:

FORTRAN

  subroutine array_operation(a)

    real :: a(*)

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.

FORTRAN

  subroutine array_example(a)

     real, allocatable, intent(inout) :: a(:)

The procedure will receive the allocation status of the object on entry. A number of conditions apply:

  1. The corresponding actual argument must also be allocatable.
  2. The rank must be specified.
  3. 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:

  1. An argument with intent(in) may not be allocated or deallocated (and the values may not be altered as for a normal intent(in) argument).
  2. 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.

Functions returning allocatable arrays

One can write a function having an allocatable result, e.g.,

FORTRAN

  function my_allocation(nlen) result(a)

     integer, intent(in)  :: nlen
     integer, allocatable :: a(:)

     allocate(a(nlen))

  end my_allocation

The result must be allocated at the point of return.

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

FORTRAN

   real :: a(5) = [ 1.0, 2.0, 3.0, 4.0, 5.0 ]

   call my_array_operation(a(1:5:2))

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”.

Example (2 minutes)

Discussion

Passing array sections

If you wish to convince yourself of this, a version of this code has been provided:

BASH

$ ftn example2.f90

A print statement has been added to the subroutine to confirm that the dummy argument is an entity of size 3.

Exercise (10 minutes)


Challenge

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 
Challenge

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.

Challenge

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.

Key Points
  • 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)

Challenge

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.

  1. 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_update

    If 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.

  2. 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_module

    We 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:

  1. Pointers associated with an actual argument may not become associated with relevant dummy arguments (copy-in, copy-out may occur);
  2. If a dummy argument has the target attribute, 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)

Challenge

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
Challenge

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
Key Points
  • 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.

Callout

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)

Challenge

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.

Callout

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)

Challenge

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
Challenge

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)

Challenge

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.

Key Points
  • Derived types enable creating custom data structures in Fortran.
  • Access to components of derived types can be controlled via the private attribute.

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.

Callout

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.

Callout

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)

Challenge

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.

Challenge

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)

Challenge

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)
Callout

Caution

This is a (deliberately) somewhat contentious example. It raises a number of general concerns about the approach:

  1. If operations involves single type, the operands are interchangeable, and a limited number of functions is required.
  2. 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)

Challenge

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

![\mathbf{u}\times \mathbf{v} = (u_2 v_3 - u_3 v_2, u_3 v_1 - u_1 v_3, u_1 v_2 - u_2 v_1)](https://latex.codecogs.com/svg.latex?\\mathbf{u}\\times \mathbf{v} = (u_2 v_3 - u_3 v_2, u_3 v_1 - u_1 v_3, u_1 v_2 - u_2 v_1).)

This should also allow a vector triple product

\mathbf{u}\times(\mathbf{v}\times\mathbf{w})

where one must use parentheses to obtain the desired result.

One could also define a scalar product .dot., being

![\mathbf{u}\cdot\mathbf{v} = u_1 v_1 + u_2 v_2 + u_3 v_3](https://latex.codecogs.com/svg.latex?\\mathbf{u}\\cdot\\mathbf{v} = u_1 v_1 + u_2 v_2 + u_3 v_3)

Does a scalar triple product

\mathbf{u}\cdot\mathbf{v}\times\mathbf{w}

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

Key Points
  • 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.

Callout

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)

Challenge

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
Challenge

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
Callout

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)

Challenge

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
Challenge

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:

  1. if the dynamic type of the selector exactly matches a type is block, then that block is executed;
  2. otherwise, if the dynamic type matches a class is block (i.e., it matches that type or a descendant) the class is block is executed;
  3. otherwise, the default block 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)

Challenge

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
Discussion

Type selection (continued)

Check your subroutine works by passing each different type in turn from the example2.f90 program.

Challenge

(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
Key Points
  • Fortran supports polymorphism through the class pointers 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.

Callout

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
Callout

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)

Challenge

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
Challenge

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)

Challenge

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
Challenge

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

  1. One of public or private indicating scope;
  2. non_overridable indicating this type-bound-name may not be re-defined in types which extend the current type;
  3. pass or nopass.
Callout

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
Callout

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)


Challenge

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.

Key Points
  • 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

  1. a formatted interface for formatted i/o;
  2. an unformatted interface 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)


Challenge

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.

Challenge

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.

Key Points
  • 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
Callout

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.

Callout

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)

Challenge

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:

  1. open the file with an appropriate file name;
  2. write the data to the file;
  3. 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:

  1. function f_open(self, filename) result(ierr) where filename is a string and the return value is an integer error code;
  2. function f_write(self, data) result(ierr) where the data should be, for simplicity, a rank 1 array of integers;
  3. 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
Callout

Implementation follows specification

This implementation must follow exactly the specification in the interface block, including the names of the dummy arguments.

Exercise (15 minutes)

Challenge

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)

Challenge

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
Challenge

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
Discussion

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.

Key Points
  • 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 host and use association

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

Callout

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
Callout

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
Callout

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)

Discussion

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.

Callout

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.

Callout

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.

Callout

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.

Exercise (15 minutes)


Discussion

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:

  1. Keep the abstract type file_writer_t definition and its related interface block in file_module.f90. Here we will also need an interface for the file_writer_from_string() function. Check the new file_module.f90 compiles on its own.
  2. Make a new (ordinary) module file_unformatted for the unformatted implementation in a separate file, and a new module file_formatted for the formatted implementation.
  3. Add a submodule to file_module to hold the implementation of the file_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?

Key Points
  • 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)

Challenge

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)


Discussion

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.

Discussion

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?

Discussion

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.

Key Points
  • 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.

Discussion

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.

Key Points
  • The kind of 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:

  1. error_unit, input_unit and output_unit: the unit numbers associated with standard error, standard input, and standard output.
  2. iostat_end and iostat_eor: error codes for end-fo-file and end-of-record.
  3. character_storage_size, file_storage_size, and numeric_storage_size related to i/o

F2008 included kind type parameters for frequently used intrinsic data types:

  1. int8, int16, int32, int64: integer kind type parameter for intrinsic integers (in bits);
  2. 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)

Discussion

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.

  1. ieee_exceptions provides handling for IEEE exception types enumerated by the values: ieee_overflow, ieee_divide_by_zero, ieee_invalid, ieee_underflow and ieee_inexact.

  2. ieee_arithmetic contains functionality for identifying different classes of floating point values (e.g., ieee_negative_inf), rounding modes, and so on. The ieee_arithmetic module include ieee_exceptions.

  3. ieee_features does not provide names a such by may affect compilation if present. E.g., if

      use, intrinsic :: ieee_features, only : ieee_subnormal

    is 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.

Key Points
  • The iso_fortran_env intrinsic 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_binding intrinsic 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)

Discussion

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)

Challenge

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.

Discussion

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)

Challenge

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 the c_ptr type 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 the c_funptr address of an interoperable procedure.

  • c_associated(c_ptr1 [, c_ptr2]) is an analogue of the associated() intrinsic which returns .true. if the first argument is not c_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 a c_ptr type into a Fortran pointer. A rank 1 integer array shape is required if fptr is 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)

Challenge

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
Key Points
  • The iso_c_binding module 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.