Derived-Type and Asynchronous I/O
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-18. Every
example was compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2)
and run, and every example was also built and run with LLVM Flang 23; the output shown is real output, and where the two
compilers print something different, or one of them rejects the program, the page says so and shows both. Examples that
are deliberately wrong are labelled as such and show the real diagnostics. Defined input/output is specified in 12.6.4.8
(procedures, execution, resolution), 13.7.6 (the DT edit descriptor), 15.4.3.2 and 15.4.3.4.4 (the generic interface
block form) and 7.5.5 (the generic type-bound form). Asynchronous input/output is specified in 12.5.6.5 (open), 12.6.2.5
and 12.6.2.9 (data transfer statements), 12.7 (waiting), 12.10.2.22 (inquire with pending=) and 8.5.4 and 8.6.3 (the
asynchronous attribute and statement). Recursive input/output is 12.12, and the encoding of Unicode files is 12.5.6.9.
Two features that are rare in tutorials share this page because both change what a data transfer statement means. With
defined input/output the program, not the library, decides how an object of derived type is written or read: a
statement such as print *, grid calls a procedure of the type. With asynchronous input/output a data transfer
statement may return before the transfer is finished, and a later wait completes it, so computation can overlap with
transfers. The examples reuse the pieces of the running example, heatflow: the grid_t of
heatflow_grid defines write(formatted) for itself, and snapshots of the temperature field are written
asynchronously while the next sweep is computed. Ordinary input/output is covered in
Input/Output: Units and Files,
Format Specifications and Edit Descriptors
and
Internal Files, Namelist and Non-Advancing I/O;
derived types and their type-bound procedures in
Derived Types and
Type-Bound Procedures, Abstract Types and Finalization.
Defined input/output at a glance
A defined input/output procedure is a subroutine with one of four fixed interfaces, connected to a derived type either by
a generic type-bound procedure (generic :: write(formatted) => name) or by an interface block
(interface write(formatted)). The program need not supply all four (12.6.4.8.2).
| Generic specification | Used when | Dummy arguments after dtv |
|---|---|---|
|
a formatted, list-directed or namelist read reaches an item of the type |
|
|
a formatted, list-directed or namelist write reaches an item of the type |
|
|
an unformatted read reaches an item of the type |
|
|
an unformatted write reaches an item of the type |
|
The dummy arguments always have these characteristics, with arbitrary names (the names used here are the standard’s):
| Argument | Declaration | Meaning |
|---|---|---|
|
|
the object being transferred (the "derived-type variable") |
|
|
the unit to use in the child data transfer statements |
|
|
|
|
|
the integers in parentheses after |
|
|
zero on success, positive on error, |
|
|
an explanatory message when |
The procedure is selected, when an item of derived type is reached in a data transfer, if both of these are true
(12.6.4.8.4): the transfer was initiated by a list-directed, namelist or unformatted statement, or an explicit format is
given and the edit descriptor for that item is DT; and a suitable generic type-bound procedure or generic interface
exists. For the type-bound form the binding is looked up in the declared type and the one in the dynamic type is
selected, so an extension can override it. An explicit format with an ordinary edit descriptor (f8.2) for the type is
an error, and a DT descriptor for a type that has no suitable procedure is also an error (13.7.6). The facility is
independent of the compiler: both gfortran and Flang implement it.
| Statement | Which procedure runs |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Provenance: asynchronous input/output is listed by the LLVM Flang documentation among the Fortran 2003 features (see the status page cited in the references), and gfortran’s manual states that it implements the Fortran 2003 standard with the exception of finalization. This page does not assign an edition to anything else.
A formatted writer for grid_t
The first example is the one heatflow_grid uses. The type-bound generic makes every output statement that reaches a
grid_t call grid_write_formatted. The procedure chooses its behaviour from iotype and v_list: the plain DT
descriptor and list-directed output give a one-line summary (the first v value is the number of decimals), and
DT"table" with two v values prints the whole field as a table, a field width and a number of decimals.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: real64, int32
implicit none
integer, parameter :: dp = real64, ip = int32
end module heatflow_kinds
module heatflow_grid
use heatflow_kinds, only: dp, ip
implicit none
private
public :: grid_t, make_grid
type :: grid_t
integer(ip) :: nx = 0_ip, ny = 0_ip
real(dp), allocatable :: u(:, :)
contains
generic :: write(formatted) => write_formatted
procedure, private :: write_formatted => grid_write_formatted
end type grid_t
contains
function make_grid(nx, ny, hot) result(g)
integer(ip), intent(in) :: nx, ny
real(dp), intent(in) :: hot
type(grid_t) :: g
g%nx = nx
g%ny = ny
allocate (g%u(nx, ny), source=0.0_dp)
g%u(nx/2 + 1, ny/2 + 1) = hot
end function make_grid
subroutine grid_write_formatted(dtv, unit, iotype, v_list, iostat, iomsg)
class(grid_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
character(len=32) :: fmt
integer :: decimals
if (iotype == 'DTtable') then ! DT'table'(w, d): the whole array as rows
decimals = 2
if (size(v_list) >= 2) decimals = v_list(2)
write (fmt, '(a,i0,a,i0,a,i0,a)') '(', dtv%nx, '(f', v_list(1), '.', decimals, '))'
block
integer :: j
do j = 1, dtv%ny
write (unit, fmt, iostat=iostat, iomsg=iomsg) dtv%u(:, j)
if (iostat /= 0) return
if (j < dtv%ny) write (unit, '(a)', iostat=iostat, iomsg=iomsg) new_line('a')
if (iostat /= 0) return
end do
end block
else ! 'LISTDIRECTED', 'NAMELIST' or plain 'DT'
decimals = 3
if (size(v_list) >= 1) decimals = v_list(1)
write (fmt, '(a,i0,a)') '(a,i0,a,i0,a,f0.', decimals, ',a)'
write (unit, fmt, iostat=iostat, iomsg=iomsg) &
'grid_t(', dtv%nx, 'x', dtv%ny, ' max=', maxval(dtv%u), ')'
end if
end subroutine grid_write_formatted
end module heatflow_grid
program dtio_write
use heatflow_kinds, only: ip, dp
use heatflow_grid, only: grid_t, make_grid
implicit none
type(grid_t) :: g
character(len=80) :: line
integer :: ios
character(len=100) :: msg
g = make_grid(4_ip, 3_ip, 98.6_dp)
print '(dt)', g ! plain DT, no v-list: default 3 decimals
print '(dt(1))', g ! v_list = [1]
print *, g ! list-directed: iotype = 'LISTDIRECTED'
write (*, '(a, dt(2), a)') 'before ', g, ' after' ! mixed with other items
print '(dt"table"(7, 1))', g ! iotype = 'DTtable', v_list = [7, 1]
write (line, '(dt)', iostat=ios, iomsg=msg) g ! internal unit: unit argument is negative
print '(a, i0, 2a)', 'iostat=', ios, ' line=', trim(line)
end program dtio_write
Output of gfortran:
grid_t(4x3 max=98.600)
grid_t(4x3 max=98.6)
grid_t(4x3 max=98.600)
before grid_t(4x3 max=98.60) after
0.0 0.0 0.0 0.0
0.0 0.0 98.6 0.0
0.0 0.0 0.0 0.0
iostat=0 line=grid_t(4x3 max=98.600)
LLVM Flang 23 prints the same lines with one difference: for the list-directed print *, g it does not write the leading
blank that gfortran adds before a list-directed record, so the third line starts at the left margin. That blank belongs to the
list-directed record, not to the defined output procedure (the standard leaves the spacing of list-directed output
processor dependent, 13.10.4), so programs must not rely on it. Things to notice in the program:
-
dtvisclass(grid_t)withintent(in)because the type is extensible, andwriteprocedures must not modify the object. A read procedure hasintent(inout). -
The default
print '(dt)'has an emptyv_list. The procedure testssize(v_list)before usingv_list(1). -
The
unitpassed in is not necessarily 6 or a positive number. For the internal file in the last statement it is a negative, processor-dependent value; see The unit argument. -
A newline is written by the procedure. The table is written with one child
writeper row andnew_line('a')between them, because a'(a)'write inside a defined procedure does not start a new record by itself (Child data transfer statements). A slash edit descriptor inside the child format is the usual alternative. -
write (fmt, ...)builds a format. That is an internal write whose format depends onv_list; internal files are allowed inside a defined procedure (Restrictions).
iotype and v_list
The DT edit descriptor has the form DT [ char-literal-constant ] [ ( v-list ) ] (13.3.2, R1307). The string
is not a format: it is passed to the procedure so that one type can have several output styles, and the integers (R1312,
v is a signed-int-literal-constant) are passed so that field widths and similar tuning can be chosen at the call. This
program only reports what it receives.
module probe_mod
implicit none
private
public :: probe_t
type :: probe_t
integer :: id = 0
contains
procedure, private :: show => probe_write
generic :: write(formatted) => show
end type probe_t
contains
subroutine probe_write(dtv, unit, iotype, v_list, iostat, iomsg)
class(probe_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, '(a, i0, 3a, i0, a, *(i0, 1x))', iostat=iostat, iomsg=iomsg) &
'id=', dtv%id, ' iotype="', iotype, '" size(v_list)=', size(v_list), ' v_list=', v_list
end subroutine probe_write
end module probe_mod
program trace_arguments
use probe_mod, only: probe_t
implicit none
type(probe_t) :: p
character(len=40) :: fmt
integer :: n
namelist /run/ p
p%id = 7
print '(dt)', p
print '(dt(10, 4, 2))', p
print '(dt"Link List"(10, 4, 2))', p
print '(dt"")', p
print *, p
fmt = '(dt"x"(3, 5))' ! the v values reach v_list
print fmt, p
print '(2(dt(1)), a)', p, p, '|' ! one descriptor per effective item
print '(dt)', [probe_t(1), probe_t(2)] ! an array: one call per element
write (*, nml=run)
n = 0
end program trace_arguments
Output of gfortran:
id=7 iotype="DT" size(v_list)=0 v_list=
id=7 iotype="DT" size(v_list)=3 v_list=10 4 2
id=7 iotype="DTLink List" size(v_list)=3 v_list=10 4 2
id=7 iotype="DT" size(v_list)=0 v_list=
id=7 iotype="LISTDIRECTED" size(v_list)=0 v_list=
id=7 iotype="DTx" size(v_list)=2 v_list=3 5
id=7 iotype="DT" size(v_list)=1 v_list=1 id=7 iotype="DT" size(v_list)=1 v_list=1 |
id=1 iotype="DT" size(v_list)=0 v_list=
id=2 iotype="DT" size(v_list)=0 v_list=
&RUN
P=id=7 iotype="NAMELIST" size(v_list)=0 v_list=
/
The reported differences with Flang are only in the layout of list-directed and namelist output: Flang does not write the
leading blank of the list-directed record and writes the namelist group on a single line,
&RUN P=id=7 iotype="NAMELIST" size(v_list)=0 v_list=/, where gfortran puts &RUN, the item and the closing slash on
three lines. Those are processor-dependent layouts. What is fixed by the standard, and identical in both compilers:
-
iotypeis"DT"for a bareDT,"DT"followed by the literal forDT"literal","LISTDIRECTED"for list-directed transfers and"NAMELIST"for namelist transfers. The note in 13.7.6 givesDT'Link List'(10, 4, 2)as passing"DTLink List"and[10, 4, 2]. -
v_listhas the same number of elements as thev-list, in the same order, and is zero-sized when there is nov-list, and for list-directed and namelist transfers. -
An array is processed one element at a time and each element calls the procedure:
[probe_t(1), probe_t(2)]gives two calls (the last two lines before the namelist group), and2(dt(1))withp, pgives two as well. -
The character literal of the descriptor is a character constant of default kind (C1309); the
vvalues are default integers.
A DT edit descriptor corresponds to one effective item; it consumes one item of the output list like any data edit
descriptor and, if the list is exhausted, ends format processing (dt(1) repeated with 2(dt(1)) is correct for two items).
It cannot correspond to an item that is not of derived type (13.7.6), so print '(dt)', 1 is an error.
Zero and negative v values in gfortran
The standard allows any signed integer constant as a v value, including zero and negative ones (R1312), and a procedure may
interpret them as it likes. gfortran 16.2 rejects them: a literal format is diagnosed at compile time, and a format built
at run time fails when the statement executes. This program is deliberately written to show it:
module probe_mod
implicit none
type :: probe_t
integer :: id = 0
contains
procedure, private :: show => probe_write
generic :: write(formatted) => show
end type probe_t
contains
subroutine probe_write(dtv, unit, iotype, v_list, iostat, iomsg)
class(probe_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, '(a, i0, a, *(i0, 1x))', iostat=iostat, iomsg=iomsg) &
'id=', dtv%id, ' v_list=', v_list
end subroutine probe_write
end module probe_mod
program negative_v
use probe_mod, only: probe_t
implicit none
type(probe_t) :: p
p%id = 7
print '(dt(-3, 0))', p
end program negative_v
gfortran 16.2 refuses to compile it:
02b_negative_v.f90:27:14:
27 | print '(dt(-3, 0))', p
| 1
Error: Positive width required in format string at (1)
LLVM Flang 23 accepts it and the procedure receives v_list = [-3, 0]:
id=7 v_list=-3 0
A format held in a variable (fmt = '(dt"x"(3, 0))') is reported by gfortran at run time as Positive integer required in
format. Until that is fixed, treat the values in v_list as positive widths and counts if the code has to build with
gfortran, and use iotype strings for anything else.
Reading with a defined procedure
A read procedure receives dtv with intent(inout) and unit, and reads with child data transfer statements on that
unit (see Child data transfer statements). Its job is to report problems through iostat and iomsg: it assigns a
positive value to iostat for an error, iostat_end for end-of-file, iostat_eor for end-of-record, and zero
otherwise, and sets a message when iostat is not zero (12.6.4.8.3). The procedure below reads a heat source written as
three list-directed values, ix iy temperature, validates them, and commits to dtv only if everything was read; a failure in the middle leaves
the object untouched. The parent read statements show the four ways the procedure’s errors are seen by the caller.
module hotspot_mod
use, intrinsic :: iso_fortran_env, only: real64, iostat_end
implicit none
private
public :: hotspot_t
integer, parameter :: dp = real64
! A heat source given as "ix iy temperature" (three values on the current record).
type :: hotspot_t
integer :: ix = 0, iy = 0
real(dp) :: temperature = 0.0_dp
contains
generic :: read(formatted) => read_formatted
generic :: write(formatted) => write_formatted
procedure, private :: read_formatted => hotspot_read
procedure, private :: write_formatted => hotspot_write
end type hotspot_t
contains
subroutine hotspot_read(dtv, unit, iotype, v_list, iostat, iomsg)
class(hotspot_t), intent(inout) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
integer :: ix, iy
real(dp) :: t
if (iotype /= 'LISTDIRECTED' .or. size(v_list) > 0) then
iostat = 1
iomsg = 'hotspot_t can only be read with list-directed input'
return
end if
read (unit, *, iostat=iostat, iomsg=iomsg) ix, iy, t ! child read: continues at the parent position
if (iostat /= 0) return ! pass iostat_end and errors up unchanged
if (ix < 1 .or. iy < 1) then
iostat = 2
iomsg = 'hotspot indices must be positive'
return
end if
dtv%ix = ix ! dtv is class(...): assign components, not the whole object
dtv%iy = iy
dtv%temperature = t
end subroutine hotspot_read
subroutine hotspot_write(dtv, unit, iotype, v_list, iostat, iomsg)
class(hotspot_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
character(len=24) :: fmt
integer :: decimals
decimals = 2
if (size(v_list) > 0) decimals = v_list(1) ! DT(n): n decimals
write (fmt, '(a, i0, a)') '(2(i0, 1x), f0.', decimals, ')'
if (iotype == 'LISTDIRECTED') fmt = '(2(i0, 1x), f0.2)'
write (unit, fmt, iostat=iostat, iomsg=iomsg) dtv%ix, dtv%iy, dtv%temperature
end subroutine hotspot_write
end module hotspot_mod
program dtio_read
use hotspot_mod, only: hotspot_t
implicit none
type(hotspot_t) :: a, b
character(len=64) :: msg
character(len=24) :: text(4)
integer :: ios, k
text = [character(len=24) :: '3 4 98.6', '5 6 20.0, 7 8 30.5', '0 1 5.0', '2 x 1.0']
read (text(1), *) a
print '(a, dt(4))', 'one: ', a
read (text(2), *) a, b ! two items in one record, list-directed separators
print '(a, dt, a, dt)', 'two: ', a, ' and ', b
do k = 3, 4
read (text(k), *, iostat=ios, iomsg=msg) a
print '(a, i0, a, i0, 2a)', 'record ', k, ': iostat=', ios, ' iomsg=', trim(msg)
end do
read (text(1), '(dt)', iostat=ios, iomsg=msg) a ! formatted read: our procedure refuses
print '(a, i0, 2a)', 'DT read: iostat=', ios, ' iomsg=', trim(msg)
end program dtio_read
Output of gfortran:
one: 3 4 98.6000
two: 5 6 20.00 and 7 8 30.50
record 3: iostat=2 iomsg=hotspot indices must be positive
record 4: iostat=5010 iomsg=Bad integer for item 2 in list input
DT read: iostat=1 iomsg=hotspot_t can only be read with list-directed input
Flang prints the same lines except for record 4, where the child read’s message comes from the Flang runtime:
record 4: iostat=1001 iomsg=Bad character 'x' in INTEGER input field. The value of iostat and the text of iomsg for a
failed conversion are processor dependent; the ones produced by the procedure itself (iostat=2, iostat=1) are the
program’s. Observations:
-
The child read passes
iostatthrough. A conversion error, the end of the file or the end of a record in the child read setiostatto the processor’s value, and the procedure returns it unchanged (if (iostat /= 0) return). Dropping that test and continuing is the classic bug: the later statements then run on undefined data. -
dtvis polymorphic. Whole-object assignment to aclass(hotspot_t)dummy is not allowed (both compilers rejectdtv = hotspot_t(ix, iy, t): "Nonallocatable variable must not be polymorphic in intrinsic assignment" in gfortran), so the components are assigned one by one. A type with many components can use a non-polymorphic helper with atype(...)dummy. -
One record can hold several objects.
read (text(2), *) a, bconsumes5 6 20.0, 7 8 30.5with two calls, because the child read stops after the third value and the next call starts where it left off. -
The
iotypecheck is optional but worth having. The explicit-format read in the last statement is refused with the procedure’s own message, whichiomsg=returns.
An iostat from a defined procedure is delivered to the parent statement. If the parent has no iostat=, err= or
end=, the program terminates and the processor makes the iomsg available in some way (12.6.4.8.3); both compilers print it:
module fail_mod
implicit none
type :: fussy_t
integer :: id = 1
contains
procedure, private :: wf
generic :: write(formatted) => wf
end type fussy_t
contains
subroutine wf(dtv, unit, iotype, v_list, iostat, iomsg)
class(fussy_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
if (size(v_list) /= 1) then
iostat = 17
iomsg = 'fussy_t needs exactly one v value: DT(width)'
return
end if
write (unit, '(i0)', iostat=iostat, iomsg=iomsg) dtv%id
if (.false.) print *, iotype
end subroutine wf
end module fail_mod
program unhandled
use fail_mod
implicit none
type(fussy_t) :: f
integer :: ios
character(len=80) :: msg
write (*, '(dt(1))') f
write (*, '(dt)', iostat=ios, iomsg=msg) f
print '(a, i0, 2a)', 'handled: iostat=', ios, ' iomsg=', trim(msg)
write (*, '(dt)') f ! no iostat=: the program terminates
print '(a)', 'not reached'
end program unhandled
gfortran prints the following, with the message in the runtime error (the first statement prints 1; the second fails inside the
procedure, which wrote nothing, so its record is empty):
1
handled: iostat=17 iomsg=fussy_t needs exactly one v value: DT(width)
At line 36 of file 17_unhandled_iostat.f90 (unit = 6, file = 'stdout')
Fortran runtime error: fussy_t needs exactly one v value: DT(width)
Error termination. Backtrace:
Flang terminates with its own wording, fatal Fortran runtime error(/work/17_unhandled_iostat.f90:36): fussy_t needs exactly one v value: DT(width) and Aborted, which shows that the message is carried through
in both compilers.
The unit argument
The unit argument is the unit on which the child statements must write or read. The standard says what it holds in
which situation (12.6.4.8.3): the unit number if the parent uses a unit number, output_unit for print and
an asterisk write, input_unit for an asterisk read, and otherwise, for an internal file, a processor-dependent negative
value. Note 3 adds that it is also negative when the parent used an external unit connected with newunit=.
module unit_probe
use, intrinsic :: iso_fortran_env, only: output_unit, error_unit
implicit none
type :: probe_t
integer :: id = 1
contains
procedure, private :: wf
generic :: write(formatted) => wf
end type probe_t
contains
subroutine wf(dtv, unit, iotype, v_list, iostat, iomsg)
class(probe_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
character(len=12) :: kind_of_unit
if (unit == output_unit) then
kind_of_unit = 'output_unit'
else if (unit < 0) then
kind_of_unit = 'negative'
else
kind_of_unit = 'other'
end if
write (unit, '(a, i0, 2a)', iostat=iostat, iomsg=iomsg) 'probe ', dtv%id, ' on unit: ', trim(kind_of_unit)
if (iotype == 'DT' .and. size(v_list) < 0) iostat = 1
end subroutine wf
end module unit_probe
program unit_argument
use unit_probe
implicit none
type(probe_t) :: p
character(len=40) :: s
integer :: u
print '(dt)', p ! PRINT: output_unit
write (*, '(dt)') p ! asterisk unit: output_unit
write (s, '(dt)') p ! internal file: negative
print '(a)', trim(s)
open (newunit=u, status='scratch') ! newunit values are negative too
write (u, '(dt)') p
rewind (u)
read (u, '(a)') s
print '(a)', trim(s)
close (u)
open (7, status='scratch') ! a positive unit number
write (7, '(dt)') p
rewind (7)
read (7, '(a)') s
print '(a)', trim(s)
end program unit_argument
probe 1 on unit: output_unit
probe 1 on unit: output_unit
probe 1 on unit: negative
probe 1 on unit: negative
probe 1 on unit: other
Flang prints the same five lines. The practical rules follow from this: always write to the unit that was passed, never
to a hard-coded unit, and never test unit > 0 to detect "a file": inquire (unit=unit, ...) on a negative internal unit
gives an error with iostat_inquire_internal_unit, and newunit files are also negative.
Interface blocks, unformatted procedures and TYPE versus CLASS
The generic type-bound form needs access to the type definition. When the type cannot be modified, for instance because it
comes from a library, the same procedures can be attached with an interface block, which is the form 15.4.3.4.4 describes:
interface write(unformatted) followed by module procedure or an interface body. The unformatted procedures have no
iotype or v_list, and they decide the layout in the file. The program below writes only the step and a
single-precision residual, to produce a compact record, and reads them back.
module sample_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
! A type we pretend we cannot modify: no bindings, so DTIO comes from an interface block.
type :: sample_t
integer :: step = 0
real(dp) :: residual = 0.0_dp
end type sample_t
interface write(unformatted)
module procedure sample_write_u
end interface
interface read(unformatted)
module procedure sample_read_u
end interface
interface write(formatted)
module procedure sample_write_f
end interface
contains
subroutine sample_write_u(dtv, unit, iostat, iomsg)
class(sample_t), intent(in) :: dtv
integer, intent(in) :: unit
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, iostat=iostat, iomsg=iomsg) dtv%step ! only the step and a single-precision residual
if (iostat /= 0) return
write (unit, iostat=iostat, iomsg=iomsg) real(dtv%residual)
end subroutine sample_write_u
subroutine sample_read_u(dtv, unit, iostat, iomsg)
class(sample_t), intent(inout) :: dtv
integer, intent(in) :: unit
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
real :: r
read (unit, iostat=iostat, iomsg=iomsg) dtv%step
if (iostat /= 0) return
read (unit, iostat=iostat, iomsg=iomsg) r
if (iostat /= 0) return
dtv%residual = real(r, dp)
end subroutine sample_read_u
subroutine sample_write_f(dtv, unit, iotype, v_list, iostat, iomsg)
class(sample_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
character(len=32) :: fmt
integer :: digits
digits = 2
if (size(v_list) > 0) digits = v_list(1)
if (iotype == 'LISTDIRECTED') digits = 4
write (fmt, '(a, i0, a, i0, a)') '(a, i0, a, es', digits + 8, '.', digits, ', a)'
write (unit, fmt, iostat=iostat, iomsg=iomsg) '[step ', dtv%step, ' residual ', dtv%residual, ']'
end subroutine sample_write_f
end module sample_mod
program unformatted_dtio
use sample_mod
implicit none
type(sample_t) :: a, b
integer :: u, ios
a = sample_t(2000, 1.38e-4_dp)
open (newunit=u, status='scratch', access='stream', form='unformatted')
write (u) a ! unformatted DTIO: 4 + 4 bytes, not 4 + 8
write (u) a%step + 1, a ! DTIO can sit among ordinary items
inquire (unit=u, size=ios)
print '(a, i0)', 'file size in bytes: ', ios
rewind (u)
read (u) b
print '(a, i0, a, es10.3)', 'step=', b%step, ' residual=', b%residual
close (u)
print '(a, dt)', 'sample: ', a ! formatted: the interface block provides the DT procedure
print '(a, dt(3))', 'wide: ', a
print *, a ! list-directed output uses the same procedure
end program unformatted_dtio
Output of gfortran:
file size in bytes: 20
step=2000 residual= 1.380E-04
sample: [step 2000 residual 1.38E-04]
wide: [step 2000 residual 1.380E-04]
[step 2000 residual 1.3800E-04]
Flang prints the same output, except for the leading blank in the last line. The file has 20 bytes: the defined procedure
wrote 4 + 4 bytes for each a and the explicit a%step + 1 wrote 4 bytes. An unformatted defined procedure
does not position the file after the transfer (the child statements are processed differently; see Child data transfer statements),
which is why several of them can be placed in one record. Two details:
-
The residual was stored as a default
realand so it is not what was written:1.38E-4_dpcomes back as1.380E-04only because of the display precision. Defined input/output procedures are an easy place to lose precision silently; use the same kind in both directions unless the file format requires otherwise. -
The interface block is found by name resolution, not by type. A suitable generic interface must be accessible in the scoping unit that contains the data transfer statement (12.6.4.8.4), so a module that defines such an interface has to be
used (use sample_modabove). A type-bound generic is always found with the type.
dtv: class for extensible types, type for the others
dtv is class(t) if t is extensible, which every derived type is unless it is a sequence or bind(c) type
(C1236, R1221), and type(t) otherwise. Writing type(sample_t) for an ordinary type is the first error most people meet. It is
rejected by both compilers. This program is deliberately wrong:
module sample_mod
implicit none
type :: sample_t
integer :: step = 0
end type sample_t
interface write(unformatted)
module procedure sample_write_u
end interface
contains
subroutine sample_write_u(dtv, unit, iostat, iomsg)
type(sample_t), intent(in) :: dtv ! wrong: sample_t is extensible, so this must be class(sample_t)
integer, intent(in) :: unit
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, iostat=iostat, iomsg=iomsg) dtv%step
end subroutine sample_write_u
end module sample_mod
gfortran 16.2:
05_type_vs_class_error.f90:13:29:
13 | subroutine sample_write_u(dtv, unit, iostat, iomsg)
| 1~~
Error: DTIO dummy argument at (1) must be of type CLASS
LLVM Flang 23:
error: Semantic errors in 05_type_vs_class_error.f90
05_type_vs_class_error.f90:14:35: error: Dummy argument 'dtv' of a defined input/output procedure must be CLASS() when the derived type is extensible
type(sample_t), intent(in) :: dtv ! wrong: sample_t is extensible, so this must be class(sample_t)
^^^
For a type-bound generic, C782 requires the type of dtv to be the type that declares the binding. A sequence type is
not extensible, so there the declaration is type(...) instead:
module pair_mod
implicit none
type :: pair_t
sequence ! a sequence type is not extensible, so dtv is declared with type(...)
integer :: ix, iy
end type pair_t
interface write(formatted)
module procedure pair_write
end interface
contains
subroutine pair_write(dtv, unit, iotype, v_list, iostat, iomsg)
type(pair_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, '(a, i0, a, i0, a)', iostat=iostat, iomsg=iomsg) '(', dtv%ix, ',', dtv%iy, ')'
if (.false.) print *, iotype, v_list
end subroutine pair_write
end module pair_mod
program sequence_type
use pair_mod
implicit none
print '(a, dt)', 'pair: ', pair_t(3, 4)
print *, pair_t(5, 6)
end program sequence_type
pair: (3,4)
(5,6)
Flang prints (5,6) without the leading blank on the last line. All length type parameters of the type in dtv
must be assumed (C1237), so a type with a length parameter is declared type(t(*)) or class(t(*)) (see
Parameterized Derived Types).
Child data transfer statements
A statement executed inside a defined input/output procedure on the unit that was passed in is a child data transfer
statement and the statement that invoked the procedure is its parent (12.6.4.8.3). Child statements are processed
differently from ordinary ones:
| Rule | Consequence |
|---|---|
A child statement does not position the file before the transfer |
it continues from where the parent statement left the file (not necessarily at the start of a record); |
An unformatted child statement does not position after the transfer |
several unformatted child statements build one record |
|
child output never starts a new record by itself; write a slash or a line feed explicitly |
The connection modes are those of the parent at the time of the call |
|
A child statement shall not have |
those cannot be used inside the procedure (Note 7) |
Neither parent nor child shall be asynchronous |
|
A list-directed or namelist child statement may contain an item of derived type |
it calls the matching procedure again |
A child statement can contain a |
this is how a component that is itself of a type with defined I/O is handled |
A parent can also be a child, so a procedure can call itself indirectly. If that happens the procedure cannot be declared
non_recursive (Note 2 of 12.6.4.8.3). A procedure is recursive unless it has the non_recursive prefix, and recursive is
advisory only (15.6.2.1); the example spells it out for the reader. The list history_t in the next program writes itself
through a child dt statement that follows the older pointer.
module history_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: sample_t, history_t, push, dp
integer, parameter :: dp = real64
type :: sample_t
integer :: step = 0
real(dp) :: residual = 0.0_dp
contains
procedure, private :: sample_write
generic :: write(formatted) => sample_write
end type sample_t
! A singly linked list of samples; its DTIO procedure calls itself through a child statement.
type :: history_t
type(sample_t) :: sample
type(history_t), pointer :: older => null()
contains
procedure, private :: history_write
generic :: write(formatted) => history_write
end type history_t
contains
subroutine sample_write(dtv, unit, iotype, v_list, iostat, iomsg)
class(sample_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
if (iotype == 'NAMELIST' .and. size(v_list) > 0) then
iostat = 1
iomsg = 'no v-list expected'
return
end if
write (unit, '(a, i0, a, es9.2)', iostat=iostat, iomsg=iomsg) 'step ', dtv%step, ' residual ', dtv%residual
end subroutine sample_write
recursive subroutine history_write(dtv, unit, iotype, v_list, iostat, iomsg)
class(history_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, '(dt)', iostat=iostat, iomsg=iomsg) dtv%sample ! child statement, component with DTIO
if (iostat /= 0) return
if (associated(dtv%older)) then
write (unit, '(a)', iostat=iostat, iomsg=iomsg) new_line('a') ! records are the procedure's business
if (iostat /= 0) return
write (unit, '(dt)', iostat=iostat, iomsg=iomsg) dtv%older ! recursion through a child statement
end if
if (.false.) print *, iotype, v_list
end subroutine history_write
function push(old, step, residual) result(new)
type(history_t), pointer, intent(in) :: old
integer, intent(in) :: step
real(dp), intent(in) :: residual
type(history_t), pointer :: new
allocate (new)
new%sample = sample_t(step, residual)
new%older => old
end function push
end module history_mod
program nested_and_recursive
use history_mod, only: history_t, push, dp
implicit none
type(history_t), pointer :: top => null(), p
integer :: k
do k = 1, 3
top => push(top, 500*k, 1.0e-2_dp/k)
end do
print '(dt)', top ! three child writes, one per node, written with one parent statement
print '(a)', '--- after the list'
do while (associated(top)) ! free the list
p => top%older
deallocate (top)
top => p
end do
end program nested_and_recursive
Output (gfortran and Flang print the same lines):
step 1500 residual 3.33E-03
step 1000 residual 5.00E-03
step 500 residual 1.00E-02
--- after the list
The sample component is handled with dt in the child statement write (unit, '(dt)') dtv%sample. The procedure
writes new_line('a') between the records because a child statement does not end the record by itself. The standard’s own Note 8 and Note 9 show the same ideas (a two-component type with widths from v_list
and a linked list written one per line); the example above is an original one.
Components with their own defined I/O in gfortran
When an item is of a type without defined input/output that has a component of a type with defined input/output, the
item is treated as the list of its components in component order, and the component with the procedure is processed by it
(12.6.3). In gfortran 16.2 that works only when the component with the defined procedure is the first one in storage order. This is the program
that reproduces it, in which inner_t has a write(formatted) binding and outer_t has none:
module m
implicit none
type :: inner_t
integer :: step = 0
contains
procedure, private :: wf
generic :: write(formatted) => wf
end type inner_t
type :: outer_t
character(len=4) :: label = ''
type(inner_t) :: last
integer :: count = 0
end type outer_t
contains
subroutine wf(dtv, unit, iotype, v_list, iostat, iomsg)
class(inner_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (unit, '(a, i0, a)', iostat=iostat, iomsg=iomsg) '<', dtv%step, '>'
if (.false.) print *, iotype, v_list
end subroutine wf
end module m
program p
use m
implicit none
type(outer_t) :: h
h = outer_t('run', inner_t(2000), 7)
print *, h
write (*, '(a4, dt, i3)') h%label, h%last, h%count
write (*, '(a4, dt, i3)') h
end program p
gfortran 16.2 (the number in the first and third line is the characters run interpreted as an integer, that is, the
procedure received the address of the whole object instead of the component):
run <544109938> 7
run <2000> 7
run <544109938> 7
Flang 23 prints <2000> in all three places. If inner_t is moved to be the first component of outer_t gfortran also
prints it correctly. Until the defect is fixed, give an enclosing type its own defined procedure that writes each component
with a child dt statement, as history_write does above, or write the components explicitly. This is a compiler bug
and not a rule of the language; the behaviour of Flang is the one the standard describes.
Restrictions
A defined input/output procedure is more restricted than the code that calls it (12.6.4.8.3):
-
While a parent
readis active, no I/O statement may read from an external unit other thanunit, and none may write to any external unit. While a parentwriteorprintis active, no statement may write to an external unit other thanunit, and none may read from any external unit. -
A data transfer statement that specifies an internal file is permitted while a parent statement is active. That is how
write (fmt, ...) ...is used to build a format in the examples. -
open,close,backspace,endfileandrewindshall not be executed while a parent statement is active. -
The procedure and the procedures it invokes shall not define, or make undefined, any storage unit referenced by an item of the active statement, the format or any specifier, except through
dtv. -
The
v_listandiotypecannot be used to read or write a different object: they are inputs.
The processors do not diagnose most of these. This program writes to error_unit inside a defined output procedure
that was called by a print to output_unit, which breaks the second rule:
module log_mod
use, intrinsic :: iso_fortran_env, only: error_unit
implicit none
type :: noisy_t
integer :: id = 1
contains
procedure, private :: wf
generic :: write(formatted) => wf
end type noisy_t
contains
subroutine wf(dtv, unit, iotype, v_list, iostat, iomsg)
class(noisy_t), intent(in) :: dtv
integer, intent(in) :: unit
character(len=*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(len=*), intent(inout) :: iomsg
write (error_unit, '(a)') 'logging to another external unit' ! not allowed in a defined output procedure
write (unit, '(a, i0)', iostat=iostat, iomsg=iomsg) 'noisy ', dtv%id
if (.false.) print *, iotype, v_list
end subroutine wf
end module log_mod
program other_unit
use log_mod
implicit none
type(noisy_t) :: n
print '(dt)', n
end program other_unit
Both compilers print logging to another external unit and noisy 1 without any diagnostic, even with every check
enabled:
logging to another external unit
noisy 1
It works on these processors, and is not standard conforming. A program that has to be portable sends diagnostics through the
unit it was given, or collects them in iomsg.
A missing procedure is a run time error
If the format has a DT descriptor for a type that has no suitable defined procedure, the program compiles and fails when the
statement executes (this is deliberately wrong):
program no_procedure
implicit none
type :: point_t
integer :: ix = 1, iy = 2
end type point_t
type(point_t) :: p
print '(dt)', p ! no defined output procedure exists for point_t
end program no_procedure
gfortran 16.2:
At line 7 of file 16_no_procedure_error.f90 (unit = 6, file = 'stdout')
Fortran runtime error: Missing DTIO procedure or intrinsic type passed for item 1 in formatted transfer
(dt)
^
Error termination. Backtrace:
LLVM Flang 23 terminates as well, with a message that comes from its treatment of dt as an ordinary data edit descriptor:
fatal Fortran runtime error(/work/16_no_procedure_error.f90:7): Data edit descriptor 'd' may not be used with an INTEGER data item
Aborted
Recursive input/output
A defined input/output procedure is the one case where an I/O statement is meant to execute while another one is active. In
all others the rule of 12.12 applies: an I/O statement executed while another is being executed is a recursive I/O
statement, and it shall not identify an external unit that is identified by the statement being executed. The exception
is a child statement on its parent’s unit. A recursive statement shall not modify any internal unit, except that a recursive
write may modify the internal unit it names itself. In other words, a function referenced in an output list cannot
print, but it can write into its own internal file. This is the failing case:
program recursive_io
implicit none
! The function writes to unit 6 while a PRINT to unit 6 is executing: recursive I/O on one unit.
print *, 'value: ', f(3)
contains
function f(n) result(r)
integer, intent(in) :: n
integer :: r
print *, 'inside f'
r = n*2
end function f
end program recursive_io
gfortran 16.2 stops at run time (the backtrace is omitted):
At line 9 of file 08_recursive_io.f90 (unit = 6, file = 'stdout')
Fortran runtime error: Recursive I/O not allowed
Error termination. Backtrace:
Flang 23 stops too: fatal Fortran runtime error(/work/08_recursive_io.f90:9): Recursive I/O attempted on unit 6, followed by
Aborted. The remedy is to compute the value first and print it after, or to make the function write to
a different external unit. A function that only converts with an internal file is fine:
program recursive_ok
implicit none
! label writes only to an internal file, which is allowed while unit 6 is being written.
write (*, '(2a)') 'text: ', label(7)
contains
function label(n) result(s)
integer, intent(in) :: n
character(len=8) :: s
write (s, '(a, i0)') 'n=', n
end function label
end program recursive_ok
text: n=7
Another corner of 12.12: an I/O statement shall not cause the value of an established format specification to be modified, so
a statement whose format is held in fmt must not also write into fmt.
Asynchronous input/output
An asynchronous data transfer statement initiates a transfer and may return before it has finished. The processor is allowed to perform it asynchronously, but is not required to (12.6.2.5), and the order of the records and file storage units is the same as if the statements had been synchronous. The mechanism has four parts:
| Part | Rule |
|---|---|
|
the unit allows asynchronous transfers; the default is |
|
this statement is asynchronous. It must name an external unit (C1226) and the specifier value is a constant expression (12.6.2.5). |
|
a scalar default integer that receives a processor-determined value: zero if the transfer has already completed, otherwise an identifier different from the identifier of any other pending transfer on the unit (12.6.2.9) |
|
completes the transfer. Only after that are the variables defined (for input) or free to be modified (for output) |
While a transfer is pending, the pending input/output storage sequence (the items of the list, the group objects of a
namelist, and the size= variable) is owned by the I/O system. A variable that is part of it is a pending
input/output storage sequence affector. For a pending output the program must not redefine an affector,
make it undefined or change its pointer association status; for a pending input it must not even reference it, define it,
or change its association status (12.6.4.1). Both hold until the wait. To let the compiler know where that can happen the variable has the
asynchronous attribute (8.5.4).
The asynchronous attribute
The attribute can be given in a type declaration (real(dp), asynchronous :: a(:,:)) or with the asynchronous statement
(8.6.3). A variable that appears in an asynchronous data transfer statement implicitly has the attribute in that scoping
unit, and the attribute can be confirmed explicitly (12.6.2.5). The rule in 8.5.4 is that the base object of a variable has
to have the attribute in a scoping unit if the variable is a dummy argument or appears in an executable statement or
specification expression there, and any statement of the scoping unit is executed while the variable is a pending
affector. An object with the attribute may be associated with one without it, including by use or host association, and
subobjects inherit it. A block construct can give a variable the attribute inside the construct only. The attribute
exists to tell the compiler which variables may be touched by a pending transfer, so that it does not move references
to them across the statements that start and complete it (Note to 8.5.4). This example shows a snapshot writer: it declares
the attribute because the transfer started in write_snapshot may still be pending when it returns.
module snapshot_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, jacobi_sweep, write_snapshot
integer, parameter :: dp = real64
contains
! One Jacobi sweep of the interior; returns the largest change.
subroutine jacobi_sweep(u, unew, residual)
real(dp), intent(in) :: u(:, :)
real(dp), intent(inout) :: unew(:, :)
real(dp), intent(out) :: residual
integer :: nx, ny
nx = size(u, 1)
ny = size(u, 2)
unew = u
unew(2:nx-1, 2:ny-1) = 0.25_dp*(u(1:nx-2, 2:ny-1) + u(3:nx, 2:ny-1) + u(2:nx-1, 1:ny-2) + u(2:nx-1, 3:ny))
residual = maxval(abs(unew - u))
end subroutine jacobi_sweep
! Start writing a snapshot. The dummy argument carries the
! asynchronous attribute because a data transfer that uses it may still be pending on return.
subroutine write_snapshot(unit, u)
integer, intent(in) :: unit
real(dp), intent(in), asynchronous :: u(:, :)
write (unit, asynchronous='yes') u
end subroutine write_snapshot
end module snapshot_mod
program async_write
use snapshot_mod, only: dp, jacobi_sweep, write_snapshot
implicit none
integer, parameter :: n = 64, steps = 4
real(dp), allocatable, asynchronous :: a(:, :), b(:, :)
real(dp) :: residual
integer :: unit, ios, step
logical :: pending
character(len=100) :: msg
allocate (a(n, n), b(n, n))
a = 0.0_dp
a(1, :) = 100.0_dp ! hot edge
open (newunit=unit, file='snapshots.bin', status='replace', access='stream', form='unformatted', &
asynchronous='yes', iostat=ios, iomsg=msg)
if (ios /= 0) error stop trim(msg)
do step = 1, steps
call write_snapshot(unit, a) ! start writing the current grid ...
call jacobi_sweep(a, b, residual) ! ... while the sweep only reads a and writes b
wait (unit, iostat=ios, iomsg=msg) ! complete the transfer before a is modified
if (ios /= 0) error stop trim(msg)
a = b
print '(a, i0, a, es9.2)', 'step ', step, ' residual ', residual
end do
inquire (unit, pending=pending, iostat=ios)
print '(a, l1)', 'anything still pending: ', pending
inquire (unit, size=ios)
print '(a, i0, a)', 'file size: ', ios, ' bytes'
close (unit, status='delete')
end program async_write
Output of gfortran:
step 1 residual 2.50E+01
step 2 residual 1.25E+01
step 3 residual 7.81E+00
step 4 residual 5.47E+00
anything still pending: F
file size: 131072 bytes
Flang prints the same numbers. The structure of the loop is the standard double-buffering pattern: the sweep only
reads a while the snapshot of a is being written (a pending output variable may be referenced but not redefined,
12.6.4.1), and a = b is executed only after the wait. In this program the wait has no id=, which is the portable form:
it completes every pending transfer on the unit. iostat= and iomsg= on the wait carry the errors of the transfer: the
actions of the statement that performs the wait apply (12.7.1).
Which statements perform a wait? Besides wait, 12.7.1 lists backspace, close, endfile, flush, inquire,
print, read, rewind and write; so a later read or write on the unit, or close, also completes the pending
transfers. A file positioning statement waits for all pending transfers on its unit (12.8.1). If an error or end-of-file
condition occurs during a wait for a unit, the processor waits for all pending transfers of that unit (12.7.1).
Reading asynchronously
A read is dangerous in the other direction: the input variable has no defined value until the wait. Both compilers
give the same data here; the difference is where an end-of-file is reported, which the standard allows, since errors can
be raised by the statement that starts the transfer or by the wait (note to 12.7.1). A correct program therefore checks iostat on
both the read and the wait.
program async_read
use, intrinsic :: iso_fortran_env, only: real64, iostat_end
implicit none
integer, parameter :: dp = real64, n = 5
real(dp), asynchronous :: buf(n), next(n)
integer :: unit, ios, k
character(len=100) :: msg
! Create a stream file holding the numbers 1 to 10.
open (newunit=unit, file='data.bin', status='replace', access='stream', form='unformatted')
write (unit) [(real(k, dp), k = 1, 10)]
close (unit)
open (newunit=unit, file='data.bin', status='old', access='stream', form='unformatted', &
action='read', asynchronous='yes')
read (unit, asynchronous='yes', pos=1) buf ! two reads queued on one unit
read (unit, asynchronous='yes', pos=1 + 8*n) next
wait (unit) ! all pending transfers on the unit complete here
print '(a, 5f5.1)', 'buf = ', buf
print '(a, 5f5.1)', 'next = ', next
! Reading past the end of the file: where the end-of-file condition is reported is up to the processor.
read (unit, asynchronous='yes', pos=1 + 8*8, iostat=ios, iomsg=msg) buf
print '(a, l1)', 'end of file reported by the read statement: ', ios == iostat_end
wait (unit, iostat=ios, iomsg=msg)
print '(a, l1)', 'end of file reported by the wait statement: ', ios == iostat_end
close (unit, status='delete')
end program async_read
Output of gfortran (the data are right, and the end-of-file is reported by the wait):
buf = 1.0 2.0 3.0 4.0 5.0
next = 6.0 7.0 8.0 9.0 10.0
end of file reported by the read statement: F
end of file reported by the wait statement: T
LLVM Flang 23 prints the first two lines, then reports the end-of-file already in the read statement:
buf = 1.0 2.0 3.0 4.0 5.0
next = 6.0 7.0 8.0 9.0 10.0
end of file reported by the read statement: T
end of file reported by the wait statement: F
id= and pending=
id= gives each request a handle that wait (unit, id=...) and inquire (unit, id=..., pending=...) can use to
refer to one request. When id= appears in inquire, pending= shall appear too (C1250). The pending= logical
is false when the identified transfer (or, without id=, all previously pending transfers on the unit) is complete, and then the
inquire performs the wait operation; otherwise it is true and nothing is waited for (12.10.2.22). A processor may always return
true until the wait, or always false after waiting; the standard lists three acceptable behaviours. A zero value in id= means
the transfer is already complete (12.6.2.9), and wait with id=0 is allowed.
program async_ids
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64, n = 5
real(dp), asynchronous :: first(n), second(n)
integer :: unit, id1, id2, k
open (newunit=unit, file='ids.bin', status='replace', access='stream', form='unformatted')
write (unit) [(real(k, dp), k = 1, 2*n)]
close (unit)
open (newunit=unit, file='ids.bin', status='old', access='stream', form='unformatted', &
action='read', asynchronous='yes')
read (unit, asynchronous='yes', id=id1, pos=1) first
read (unit, asynchronous='yes', id=id2, pos=1 + 8*n) second
print '(a, i0, a, i0)', 'id1 = ', id1, ', id2 = ', id2 ! processor-determined; 0 means already complete
wait (unit, id=id1) ! complete one particular request
print '(a, 5f5.1)', 'first = ', first
wait (unit, id=id2)
print '(a, 5f5.1)', 'second = ', second
close (unit, status='delete')
end program async_ids
gfortran 16.2 prints the following. The first request is reported as already complete (id1 = 0) and the data of
first are nevertheless not defined: the values differ from run to run (zeros, uninitialized garbage, or asterisks where the garbage does not fit the f5.1 field, as in the run shown). This violates
12.6.2.9 (a zero identifier means the transfer is complete, and 12.7.1 says a completed input defines its variables):
id1 = 0, id2 = 1
first = 0.0 0.0***** 0.0 0.0
second = 6.0 7.0 8.0 9.0 10.0
LLVM Flang 23 returns the identifiers 1 and 2, and both variables are defined after their waits:
id1 = 1, id2 = 2
first = 1.0 2.0 3.0 4.0 5.0
second = 6.0 7.0 8.0 9.0 10.0
The workaround on gfortran 16.2 is to complete requests with wait (unit) without id=, as the previous two programs do.
A related observation: in a variant of the snapshot program that completed each write with wait (unit, id=id), gfortran 16.2 reported
anything still pending: T after the loop, where Flang reported F. Waiting without id= gave F on both.
Asynchronous input/output without id= has a cost in diagnosability. Errors and end-of-file for such a statement may be
reported during any later data transfer or wait on the unit, and then the actions of that later statement are taken
(12.6.4.1), so the code cannot tell which read failed. The note to that clause draws the conclusion: reliably attributing an
end-of-file to a read needs id= on every asynchronous read of the unit.
The attribute is not diagnosed when it is missing
The compiler cannot see across procedures that a transfer is pending, so omitting the attribute is not an error it can report.
The program below is not conforming: start_write has no asynchronous attribute on its dummy argument, so the compiler may
keep u in a register or move the assignment a = 2.0_dp before the transfer has finished, and the file may receive
the new values.
module snapshot_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
contains
subroutine start_write(unit, u)
integer, intent(in) :: unit
real(dp), intent(in) :: u(:) ! no asynchronous attribute here
write (unit, asynchronous='yes') u
end subroutine start_write
end module snapshot_mod
program forgotten
use snapshot_mod
implicit none
real(dp) :: a(4)
integer :: unit
a = 1.0_dp
open (newunit=unit, status='scratch', access='stream', form='unformatted', asynchronous='yes')
call start_write(unit, a)
a = 2.0_dp ! a is a pending storage sequence affector, and may not be defined
wait (unit)
end program forgotten
Both compilers accept it and run it without a message (there is no output). It is a good example of a bug that only appears at a higher optimization level, on a processor that really performs the transfer asynchronously, or with another compiler. Declare the attribute on every dummy argument, module variable and local variable that can be an affector, and complete the transfer before touching the data.
Compiler support
Asynchronous transfers are allowed to be synchronous. The LLVM Flang documentation lists asynchronous input/output as only
partially supported ("IO will happen synchronously"), and the identifiers Flang printed above (1 and 2) are consistent with
that. gfortran’s behaviour was observed above only through its results, not its implementation: with it an id= of zero
was returned for the first request and inquire (..., pending=...) could be true after a wait. A program that has to be
portable should treat asynchronous='yes' as an optimization hint, use wait (unit) to complete every transfer, test
iostat on both the transfer and the wait, and not assume overlap. Measure before relying on it: file systems and the I/O
library decide whether any overlap occurs, and a second thread with !$omp or a coarray image doing the writing is often a
more predictable way of overlapping I/O with computation (see
OpenMP and OpenACC and
Coarrays, Images and Synchronization).
The asynchronous attribute has a second use. The same attribute marks variables that take part in asynchronous
communication, for instance the buffers of non-blocking MPI calls (mpi_isend and the like, 18.10.4 Asynchronous
communication), and the rule in 8.5.4 applies to them in the same way; see
MPI with mpi_f08.
UTF-8 files
Default character kind is whatever the processor chooses (usually a single byte per character). The standard way to hold
and store Unicode is the ISO 10646 character kind, obtained with selected_char_kind('ISO_10646') (16.9.180), which
returns a kind value or -1 if the processor does not support it. Characters of that kind are stored in memory as UCS-4
code points. To write them to a file as UTF-8, open the unit with encoding='UTF-8' (12.5.6.9, only for formatted I/O); the
default is processor dependent. inquire (unit, encoding=...) returns 'UTF-8' for such a connection and 'UNDEFINED' for an
unformatted one (12.10.2.10).
program utf8_files
implicit none
integer, parameter :: ucs4 = selected_char_kind('ISO_10646')
character(kind=ucs4, len=:), allocatable :: title, back
character(len=100) :: enc, msg
character(kind=ucs4, len=40) :: line
integer :: unit, ios, n
print '(a, i0)', 'selected_char_kind("ISO_10646") = ', ucs4
if (ucs4 < 0) stop 'ISO 10646 characters are not supported'
title = ucs4_'Temperatura ' // char(int(z'00b0'), ucs4) // ucs4_'C: ' // char(int(z'03b1'), ucs4) // ucs4_' = 0.01'
open (newunit=unit, file='heat.txt', status='replace', encoding='UTF-8', iostat=ios, iomsg=msg)
if (ios /= 0) error stop trim(msg)
write (unit, '(a)') title
write (unit, '(a)') ucs4_'caf' // char(int(z'00e9'), ucs4)
inquire (unit, encoding=enc)
print '(2a)', 'encoding reported by inquire: ', trim(enc)
close (unit)
inquire (file='heat.txt', size=n)
print '(a, i0, a)', 'file size: ', n, ' bytes' ! UTF-8 on disk: 2 bytes for each non-ASCII character
open (newunit=unit, file='heat.txt', status='old', encoding='UTF-8')
read (unit, '(a)') line
n = len_trim(line)
back = line(1:n)
print '(a, i0, a, i0)', 'characters read: ', n, ', code of the 13th: ', iachar(back(13:13), ucs4)
close (unit, status='delete')
end program utf8_files
Output (gfortran and Flang print the same lines):
selected_char_kind("ISO_10646") = 4
encoding reported by inquire: UTF-8
file size: 33 bytes
characters read: 24, code of the 13th: 176
The file has 33 bytes: 24 characters and a record end on the first line, 4 characters and a record end on the second, and
one extra byte for each of the three non-ASCII characters (°, α, é), which take two bytes in UTF-8. The kind-4 literals
need the ucs4_ prefix, and kinds cannot be mixed in a concatenation, which both compilers report at compile time as
an operand kind mismatch. Writing a character constant with code points above 127 in the source requires the compiler to
read the source as UTF-8, which is not part of the standard; building the characters with char(code, ucs4) as above is
portable. For an internal file the kind of the list items is restricted by the kind of the file (12.6.3). More character
handling is in Characters and Strings.
Where the books are outdated
-
Defined input/output and
iotype. Older texts treat derived-type I/O as a vendor extension, or stop at namelist. The procedures are standard and both gfortran and Flang implement them, but the behaviour of the details is uneven (zero and negativevvalues, defined I/O on components), as shown above. -
classfordtv. Examples that declaretype(t)fordtvpredate the rule for extensible types (C1236) and are rejected today. -
recursiveis no longer needed to call a procedure from itself;non_recursiveis the opt-out (15.6.2.1). -
Asynchronous I/O is often presented as a guarantee of overlap. It is a permission: processors may do the transfer synchronously, as Flang does.
-
Unicode. Texts that predate
selected_char_kind('ISO_10646')andencoding='UTF-8'recommend storing bytes in default characters and decoding them by hand. -
Free-form escape sequences in character constants are not standard Fortran; use
char(code, kind)or theachar/iacharfunctions.
Pitfalls
| Pitfall | What to do |
|---|---|
|
use |
Using |
|
Zero or negative |
rejected (compile time with a literal format, run time otherwise); use positive values |
Hard-coding unit 6 |
write to the |
Ignoring |
return immediately if it is not zero, and set |
Whole-object assignment to a polymorphic |
assign the components (or use a non-polymorphic helper) |
A type with allocatable or pointer components in a formatted statement without defined I/O |
not allowed (12.6.3); define |
A child |
write |
Writing to another external unit inside the procedure |
not conforming and not diagnosed (12.6.4.8.3); report through |
A function in an output list that does I/O on the same unit |
recursive I/O error (12.12); compute the value first |
Components with defined I/O under gfortran 16.2 |
give the enclosing type its own procedure that uses a child |
Reading or modifying an asynchronous variable before the wait |
the variable is a pending affector (12.6.4.1); wait first |
Forgetting the |
not diagnosed; add it in every scoping unit that sees the variable while a transfer can be pending |
Assuming asynchronous means concurrent |
it may be synchronous; Flang currently performs the transfers synchronously |
|
observed to leave input undefined for an |
Checking |
errors can show up at the |
Combining |
neither parent nor child may be asynchronous (12.6.4.8.3) |
|
the specifier applies only to formatted connections (12.5.6.9) |
See also Common Gotchas and Pitfalls,
Best Practices and Style and
Object Oriented Programming for the type-bound
procedures that carry the write(formatted) generic.