Derived Types

A derived type bundles related data into one name — coordinates plus altitude, a solution plus its residual, a station plus its history. Fortran's model is value-based: a variable of a derived type is its components, assignment copies them, and passing to a procedure hands over a whole record at once. This lesson builds the type from scratch.

Defining a type

A type declaration lists components between type :: name and end type name. Components may be any intrinsic type, another derived type, arrays, or allocatable data. The type(...) :: var syntax then declares variables, and the % operator selects one component — the dot you know from C structs and Python classes, typed with a percent sign:

type :: weather_station
  character(len=32) :: name
  real(8) :: latitude, longitude
  integer :: altitude
  real(8), allocatable :: history(:)     ! one reading per day, grows
end type weather_station
Derived type with components and instances

Fig. 1 — One type description, many independent instances; assignment copies the value.

Instances and assignment

Each typed variable is a complete record. Assignment copies every component — deep enough that the two variables hold equal values, not shared storage. The structure constructor initializes whole instances positionally or by keyword, and any component may be overwritten afterward with its own assignment:

Arrays of types and nested types

Typed variables compose exactly like intrinsics. An array of stations is a structured list; a station component can itself be a type, building a hierarchy of records. Whole-array operations still apply element-wise — stations%altitude in an expression is an array of the component values, which makes reductions over components one-liners:

program station_array
  implicit none
  type :: coordinates
    real(8) :: lat, lon
  end type coordinates
  type :: station
    character(len=24) :: name
    type(coordinates) :: where
    integer :: altitude
  end type station
  type(station) :: net(3)
  integer :: i
  net(1) = station("Chur",     coordinates(46.85d0, 9.53d0),  556)
  net(2) = station("Jungfrau", coordinates(46.55d0, 7.98d0), 3571)
  net(3) = station("Lugano",   coordinates(46.00d0, 8.95d0),  273)
  print '(a,i0)', "highest station is #", maxloc(net%altitude, dim=1)
  do i = 1, 3
     print '(a,2f7.2)', trim(net(i)%name), net(i)%where%lat, net(i)%where%lon
  end do
end program station_array

Allocatable components and deep copy

An allocatable component makes the type self-sizing. Assignment of such a type performs a deep copy — the target gets its own allocation with the same contents — so two variables never alias the same history array. This is the property that keeps derived types safe to return from functions and pass by value; a pointer component (the pointers lesson) would share, which is sometimes the point and sometimes the bug:

program deep_copy
  implicit none
  type :: series
    real(8), allocatable :: y(:)
  end type series
  type(series) :: s1, s2
  allocate (s1%y(3)); s1%y = [1.0d0, 2.0d0, 3.0d0]
  s2 = s1                       ! deep copy: separate allocations
  s2%y(1) = 99.0d0             ! changes only s2
  print *, s1%y                 ! 1 2 3 — untouched
  print *, s2%y                 ! 99 2 3
end program deep_copy

Design for extension

Derived types become objects in the oop lesson: procedures bound to the type (procedure :: write => station_write), access control inside modules (private components), and inheritance via type, extends(...). Design rule from day one: keep component data private behind a module and let constructors and type-bound procedures be the only door in — it costs nothing now and makes the oop lesson's machinery natural later.

program station_demo
  implicit none
  type :: weather_station
    character(len=32) :: name = "unnamed"
    real(8) :: latitude = 0.0d0, longitude = 0.0d0
    integer :: altitude = 0
    real(8), allocatable :: history(:)
  end type weather_station
  type(weather_station) :: a, b

  a = weather_station(name="Chur", latitude=46.85d0, longitude=9.53d0)
  a%altitude = 556                    ! patch one component
  allocate (a%history(365))
  b = a                               ! full copy: b is independent
  b%name = "Zurich"                   ! changing b leaves a intact
  print '(a,a,1x,i0)', trim(a%name), trim(b%name), a%altitude
end program station_demo