Records with type
A derived type groups fields under one name, so a student is a name and a score rather than two arrays that must be kept in step. Constructors, % access, and arrays of records.
A type of your own
type :: student ... end type student declares a record type: a list of named fields, each with its own type and length. It describes no data by itself; it is the shape that variables are then made from, with type(student) :: one.
Inside, the fields are declared exactly as ordinary variables: character(len=12) :: name and integer :: score. A field can have a default, as in integer :: score = 0, which is a small but valuable habit — a record that starts in a known state cannot surprise you with an uninitialised number.
A component can be an array: real :: marks(3) inside the type gives every record its own three marks, and roster(i)%marks(2) reads one of them. The array is part of the record, so it moves with it when the record is assigned or passed to a procedure.
Constructors and the percent sign
A value of the type is built with its name as a function: student('Ada', 91) fills the fields in the order they were declared. That value can be assigned to a variable, put in an array constructor, or passed straight to a procedure.
A field is reached with %: one%name, one%score. The same works through an array, so roster(2)%score is the score of the second student, and roster%score is the whole column of scores — an array of integers that sum and maxval accept directly.
A record can also be allocatable: real, allocatable :: marks(:) lets each record hold a different number of marks, at the cost of an allocate before use and a check that it was done. Fixed components are simpler and are the right default when the size is known.
Why not simply use two arrays
A program can keep names(3) and scores(3) and promise to keep them in step. Every insert, delete and sort then has to touch both arrays in the same way, and the day one of them is reordered alone, the report is wrong with no error anywhere.
A record makes that mistake impossible, because the name and the score are one value. Sorting records moves the fields together, passing a record to a procedure passes all of it, and a function that takes a student cannot be handed a score by accident.
This is also where the earlier chapters start to pay off: an array of records is an array like any other, a record is a natural argument to a function, and printing one properly is a formatted write with the fields in order.
Printing a record for a report means naming its components. A format string writes trim(one%name) and one%score as separate items, or an internal write builds a line first. That is why the format and the field widths have to be kept together.
One student, then a roster
type :: student
character(len=12) :: name
integer :: score = 0
end type student
type(student) :: one, roster(3)
one = student('Ada', 91)
roster(1) = student('Bo', 78)
roster(2) = student('Cy', 85)
print *, trim(one%name), one%score
print *, 'best: ', maxval(roster%score)Records can hold records
A derived type is not limited to simple fields. A component can be another derived type, an array, or both: an event can hold a date, and a date can hold three integers of its own. Access follows the same percent sign at every level, so event%when%day reads from the outside in, and roster(i)%marks(2) picks one element of one component.
The type declaration and the variable declaration are separate steps. type :: date describes fields and reserves no storage; type(date) :: today creates one value. A structure constructor fills the fields in declaration order, so date(12, 5, 2027) is day, month, year — and getting that order wrong is a bug the compiler cannot see. Naming the fields in the constructor, date(day=12, month=5, year=2027), makes the order explicit when it matters.
Three record mistakes are common: passing constructor values in the wrong order, because three integers look alike; declaring the type inside one program or procedure and then needing it somewhere else; and giving a character component too little room, so a name is silently truncated when the record is built. The type belongs where every user can see it, and its field lengths are decisions, not details.
Try these next
- Add a
datefield to the student record and print the year. - Use a keyword constructor so the field order is written down.
- Make an array of three events and print the busiest one with
maxvalon its seats.
A roster of three
Run it, then add a fourth student and change the array size to 4 — the loop follows size, so nothing else needs editing. Then print the average score.
An event with a date inside it
Run it and read the nested fields. Then change the event’s date with keyword arguments, and add a `room` component that is a character string of its own.
Give every field a sensible default in the type. A record assembled in pieces then starts from a known state, and forgetting to set one field gives an obvious zero instead of a number left over from another variable.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does roster(2)%score mean?
