Derived types
Last updated on 2026-07-10 | Edit this page
Overview
Questions
- How are data structures (derived types) defined in Fortran?
- How can we control access to components of data structures in Fortran?
- How does assignment between derived types work?
Objectives
- Be able to declare your own data structures.
- Understand assignment operations in derived types and when shallow/deep copying occurs.
Derived types provide the fundamental basis for data structures
(cf. struct in C++). Derived types are sometimes referred
to as user-defined types. The advice given in the introductory course
was always to place type definitions in a module.
Definition
We recall that the general form of the declaration is:
type [ [, attribute-list] :: ] type-name
[private]
component-part
[ contains
procedure-part ]
end type [ type-name ]
Components of types may be intrinsic, allocatable, pointer, or other
derived types. The procedure part is used (optionally) to define
so-called type-bound procedures. These are the equivalent of
class methods in other languages (and will be covered in detail in later
episodes). Scope of components may be controlled via public
or private statements.
Encapsulation may be achieved by declaring a public type
with private components, e.g.,
type, public :: my_opaque_t
private
integer :: idata
end type my_opaque_t
It is also possible to have a mixture, e.g.:
type, public :: my_semi_opaque_t
private
integer :: idata ! default private
integer, public :: ndata ! explicitly public
end type my_semi_opaque_t
Components are referenced using the component selector
%. Types with components which are themselves derived types
give rise to the idea of an ultimate component, which is an
intrinsic type for which no further application of % is
relevant.
protected attribute
Note that there is a protected attribute in Fortran,
although it has a slightly different usage than in C++. It will be
discussed later as part of the section on submodules. Some authors argue
that protected should be avoided as it represents a
breakdown of encapsulation.
Assignments and copying
Intrinsic assignments
Intrinsic assignment is available for types, and involves an intrinsic assignment of each component in turn. Schematically:
type (my_semi_opaque_t) :: a
type (my_semi_opaque_t) :: b
b = my_semi_opaque(2, 3)
a = b
Here, the components of a will take on the values of the
components of b.
If a type component is itself a derived type, then intrinsic assignment takes place in the same way for that component.
Example (5 minutes)
Intrinsic assignment
The accompanying module my_semi_opaque_type.f90 provides
a public definition of the type as defined above, and includes a
function to initialise the two components. The accompanying program
exercise1.f90 will make the intrinsic assignment. You can
compile with, e.g.,
$ ftn my_semi_opaque_type.f90 example1.f90
How are you going to check that the result of the assignment is correct for both components (by printing the result to the screen)? Write some code to perform this check.
subroutine my_semi_opaque_print(name, val)
character(len=*), intent(in) :: name
type(my_semi_opaque_t), intent(in) :: val
print *, name, "%idata=", val%idata
print *, name, "%ndata=", val%ndata
end subroutine my_semi_opaque_print
This code should be added to the module. You should obtain the following output:
gfortran my_semi_opaque_type.f90 example1.f90 && ./a.out
a%idata= 2
a%ndata= 3
Allocatable components
Suppose our derived type had an allocatable component. For example:
type, public :: my_array_t
integer :: nlen
real, allocatable :: values(:)
end type my_array_t
Intrinsic assignment takes place for such an object in much the same way, e.g.,
type (my_array_t) :: a
type (my_array_t) :: b
! ... establish some data for b ...
a = b
However, there are a number of extra steps involved: 1) if the
values component of a on the left hand side is
already allocated, it is first deallocated; 2) an appropriate allocation
is then made for a depending on the size and bounds of
b%values(:); 3) all the relevant values of the right-hand
side are copied to the left-hand side.
If b%values is unallocated, then step (2) is omitted,
and a%values is also unallocated after assignment.
This is, in the jargon, a deep copy. You have a copy of the actual data.
Again, this process is repeated until any ultimate allocatable component is reached.
Derived types as procedure arguments
Recall the behaviour of allocatable arrays as arguments to
procedures. When the argument is intent(out) the array is
deallocated on entry. The same applies to allocatable components of
derived types when a derived-type argument is
intent(out).
Pointer components
For types with pointer components, the situation is different. Consider:
type, public :: my_array_pointer_t
integer :: nlen
real, pointer :: values(:)
end type my_array_pointer_t
Assignment here means that the pointer becomes associated with the target on the right-hand side.
type (my_array_pointer_t) :: a
type (my_array_pointer_t) :: b
b%nlen = 10
allocate(b%values(b%nlen))
a = b
This implies that if, in a subsequent step, the target becomes
unassociated (or deallocated), then the reference retained in
a is in a bad state.
This is a shallow copy. No data have been duplicated; only the pointer description itself.
Example (5 minutes)
Shallow and deep copies
In the accompanying module my_array_type.f90 both the
types above have been declared, along with a function to initialise some
array values. Compile the example program:
$ ftn my_array_type.f90 example2.f90
and check the values printed out. What happens if you insert a call
to my_array_destroy(a) (which deallocates the values
associated with the my_array_t argument) at the end of the
program and try to print the values of the pointer type c
again?
The output of the initial program
State of a 3 T
State of c 3 T 1.00000000 2.00000000 3.00000000
The output of the program after destroying a shows
c%values now points to uninitialised data, however
c%nlen is a deep copy and is still “valid” (in some
sense).
State of a 3 T
State of c 3 T 1.00000000 2.00000000 3.00000000
State of c 3 T -1.40913533E-36 1.56146688E-41 1.12103877E-44
Shallow and deep copies (continued)
What happens if you try to make a direct assignment between a
my_array_t object on the right-hand side, and a
my_array_pointer_t on the left-hand side?
The compiler rejects the code as invalid due to different types.
gfortran my_array_type.f90 example2.f90 && ./a.out
example2.f90:24:6:
24 | c = b
| 1
Error: Cannot convert TYPE(my_array_t) to TYPE(my_array_pointer_t) at (1)
A defined assignment
If something other than intrinsic assignment for types is required,
it is possible to overload the meaning of the assignment operation
=.
For example, if an assignment between two objects of
my_array_t were required, one could add a new assignment
interface in the module specification:
interface assignment (=)
module procedure my_assignment
end interface assignment (=)
and then provide the following module subprogram:
subroutine my_assignment(a, b)
type (my_array_t), intent(out) :: a
type (my_array_t), intent(in) :: b
a%nlen = b%nlen
a%values = b%values
end subroutine my_assignment
This must be a subroutine with two arguments, the first with
intent(out) (or inout) to represent the
left-hand side of the assignment, and the second with
intent(in) to represent the right-hand side.
Exercise (10 minutes)
Constructing a derived type pointer
In example2.f90 we explcitly assigned both components of
the my_array_pointer_t in the code and found we could not
make an assignment between my_array_pointer_t and
my_array_t. Add a subroutine in
my_array_type.f90 to make this possible.
First we define the (=) interface in the module
interface assignment (=)
module procedure my_array_ptr_assignment
end interface assignment (=)
and the assignment subroutine itself
subroutine my_array_ptr_assignment(a, b)
type(my_array_pointer_t), intent(out) :: a
type(my_array_t), target, intent(in) :: b
a%nlen = b%nlen
a%values => b%values
end subroutine my_array_ptr_assignment
noting that the assignment target must be a pointer so
that we can use it as the target of the my_array_pointer_t.
Then we can use c = a in the example program in place of
the member-wise assignment.
- Derived types enable creating custom data structures in Fortran.
- Access to components of derived types can be controlled via the
privateattribute.