Variables, Declarations and Attributes
|
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, working from the final draft J3/23-007r1). Its examples were
compiled and run on 2026-10-18 with GNU Fortran 16.2 (-std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all) and LLVM
Flang 23, and the output shown is the real output. Declarations are where a Fortran program says what every name is: its type, kind,
shape, lifetime, visibility and how it may be used. Because the language was designed for numerical code that lives for decades, the
rules are strict in some places (a dummy argument declared intent(in) really cannot be assigned) and surprisingly permissive in others
(a misspelt name used to become a new variable without any diagnostic). This page covers the type declaration statement and its
attributes, implicit none, named constants, initialization, specification expressions, the block construct and the Fortran 2023
typeof and classof declarations.
The intrinsic types and kind parameters used in declarations are in
Intrinsic Types and Kinds; the executable statements that use the
variables are in Expressions and Assignment. Array shapes, allocation,
pointers and derived types have their own pages (Arrays Fundamentals,
Allocatable Arrays and Dynamic Memory,
Pointers and Targets and
Derived Types); here they appear only as attributes. The scoping units that contain
declarations are described in
Program Structure and Source Form and
Modules and Submodules. Readers who know C will recognise parts of this
page in Storage Duration, Scope and Linkage and
Constants, Enumerations and Initialization; the main
difference is that Fortran declarations carry intent and aliasing information that a C compiler has to infer or be told with const and
restrict.
The type declaration statement
A type declaration statement (S 8.2, rule R801) has the form:
declaration-type-spec [ [ , attr-spec ] ... :: ] entity-decl-list
-
The
declaration-type-specis an intrinsic type with its parameters (integer(ip),real(dp),character(len=*)), a derived type (type(grid_t)), a polymorphic type (class(solver_t),class(*)),type(*)for assumed-type dummies, or, since Fortran 2023,typeof(x)orclassof(x)(see The rank clause, typeof and classof (Fortran 2023)). -
The
attr-speclist gives attributes that apply to every entity in the statement. The attributes are listed in Attribute reference. The same attribute shall not appear twice in one statement (C801), and the::separator is required whenever there is an attribute or an initialization (C806). -
Each
entity-decl(R803) is a name followed by an optional array-spec in parentheses, an optional coarray-spec in brackets, an optional*lencharacter length and an optional initialization. An array-spec or coarray-spec on one entity overrides thedimensionorcodimensionattribute for that entity only; a*lenoverrides the character length for that entity only. -
With
parameter, every entity must be initialized (C807). Dummy arguments, function results, automatic objects and allocatable variables cannot be initialized (C808).
The first example declares named constants and arrays with the heatflow kinds module (dp is real64, ip is int32), and shows the
per-entity overrides:
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
implicit none
private
public :: sp, dp, ip, lp
integer, parameter :: sp = real32
integer, parameter :: dp = real64
integer, parameter :: ip = int32
integer, parameter :: lp = int64
end module heatflow_kinds
program declarations
use heatflow_kinds, only: dp, ip
implicit none
! Attributes in the attribute list apply to every entity in the statement.
real(dp), parameter :: lx = 1.0_dp, ly = 0.5_dp
integer(ip), parameter :: nx = 5_ip, ny = 3_ip
! dimension in the attribute list gives a default shape ...
real(dp), dimension(nx, ny) :: u, unew
! ... that an array-spec in the entity-decl overrides (here: a vector).
real(dp), dimension(nx, ny) :: u0, probe(nx)
! A character length can be overridden per entity with *len.
character(len=8) :: label, short*4
! Initialization appears in the entity-decl (and needs the :: separator).
integer(ip) :: step = 0_ip
logical :: converged = .false.
u = 0.0_dp
unew = 0.0_dp
u0 = 20.0_dp
probe = u0(:, 1)
label = 'plate'
short = 'abcd'
print '(a, 2i3)', 'shape of u: ', shape(u)
print '(a, 2i3)', 'shape of u0: ', shape(u0)
print '(a, i3)', 'size of probe: ', size(probe)
print '(a, i3, a, i3)', 'len(label): ', len(label), ' len(short): ', len(short)
print '(a, a)', 'short: ', short
print '(a, i3, l2)', 'step, converged: ', step, converged
print '(a, f5.2)', 'dx: ', lx/real(nx - 1_ip, dp)
print '(a, f5.2)', 'dy: ', ly/real(ny - 1_ip, dp)
end program declarations
shape of u: 5 3
shape of u0: 5 3
size of probe: 5
len(label): 8 len(short): 4
short: abcd
step, converged: 0 F
dx: 0.25
dy: 0.25
The dimension override is easy to miss when reading code: u0 has the shape (5, 3) from the attribute list, while probe(nx) has
the shape (5) because its own array-spec wins. The same applies to short*4, which is a character(len=4) although the statement
begins with character(len=8). The statement is accepted without warnings by both compilers. Assigning a longer string to a shorter
character variable truncates it silently in the standard, and gfortran reports it with -Wcharacter-truncation.
Some practical rules for declarations follow from the standard.
-
All declarations belong in the specification part, before the first executable statement, with the order
use,import,implicit, then other declarations. In ablockconstruct the same applies inside the block (see The block construct). -
A name in a type declaration statement must not already have a different type: giving a variable a type twice is an error, and a variable typed by the implicit rules can only be confirmed, never changed, by a later declaration (S 8.7).
-
The old form without
::(real x, y(10)) is still valid when there are no attributes and no initialization, and it is what most legacy code uses. New code should always write the::separator. It is required as soon as an attribute or an initializer is added, and mixing styles invites mistakes. -
character*20 sanddouble precisionare older spellings.character*nis obsolescent in the standard (S B.3.8);double precisionis not obsolescent but is replaced byreal(dp)with a kind fromiso_fortran_env, as described in Intrinsic Types and Kinds.
Implicit typing and implicit none
Historically a name that was not declared was given a type from its first letter: i to n were default integer and every other
letter default real. That mapping is still the default in every program unit and interface body in Fortran 2023 (S 8.7), so
this program is standard-conforming, compiles, and does the wrong thing silently. It is shown here as a deliberately legacy example,
which does not follow the conventions of this section (no implicit none):
! Deliberately legacy code: there is no IMPLICIT NONE, so a misspelt name is
! silently a new implicitly typed variable. Do not write programs like this.
program implicit_legacy
total = 0.0
do i = 1, 3
total = total + 0.5
end do
! Typing error: "totl" is a brand new variable of default real, value undefined.
half = totl / 2.0
n = 7
x = 7 / 2
print *, 'i is ', i, ' (letters I-N are integer)'
print *, 'total =', total
print *, 'x = 7/2 gives', x, ' n/2 gives', n/2
print *, 'a typo-variable was compiled without a single error'
end program implicit_legacy
Compiled with gfortran -std=f2023 -Wall -Wextra, it produced two warnings (-Winteger-division for 7 / 2 and -Wuninitialized for totl) and this output:
i is 4 (letters I-N are integer)
total = 1.50000000
x = 7/2 gives 3.00000000 n/2 gives 3
a typo-variable was compiled without a single error
totl is a different, never-assigned variable of default real, i is an integer, x is a real that received the result of an
integer division, and none of that is an error. Flang compiles and runs the same program without any warning at all.
implicit none
implicit none removes the implicit mapping for the scoping unit. It must appear after any use statements and before any other
declaration (C895), at most once per scoping unit. Module procedures, internal procedures and block constructs use the mapping of their
host, so one implicit none at the top of a module or program covers everything it contains. Interface bodies and external
subprograms do not inherit, so each needs its own.
The same code with implicit none is rejected with a precise message:
! Intentionally wrong: shows what implicit none reports for the typo of 02.
program error_typo
implicit none
real :: total, half
total = 1.5
half = totl/2.0
print *, half
end program error_typo
This example is intentionally invalid, to show the diagnostic. GNU Fortran 16.2 reports:
05_error_typo.f90:6:13:
6 | half = totl/2.0
| 1
Error: Symbol 'totl' at (1) has no IMPLICIT type; did you mean 'total'?
LLVM Flang 23 reports error: No explicit type declared for 'totl'. Both compilers also offer a switch that behaves as if every
procedure began with implicit none: -fimplicit-none, accepted by both GNU Fortran and Flang. The convention of this
section is to put implicit none in the source and keep the flag in the compiler options as a safety net for files that forgot it.
implicit none (type, external)
Fortran 2018 added an optional list to the statement: implicit none (type, external). The type item is the classic behaviour. The
external item requires every external or dummy procedure used in the scoping unit to have an explicit interface or the external
attribute (C899), so that a call to an undeclared external procedure becomes an error instead of an implicit interface with
unchecked arguments. Writing implicit none alone is the same as implicit none (type), and in both forms no other implicit statement may appear in the scoping unit
(C897). A list of only external leaves the implicit typing rules in force. This page and the rest of the section use the two-item form in new code that
calls external procedures, and the plain form elsewhere.
The next program has one external subprogram, an interface body that gives the caller an explicit interface, and implicit none
(type, external) in both program units:
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp
integer, parameter :: dp = real64
end module heatflow_kinds
! An external procedure (no module): its interface is implicit at the call site.
subroutine scale_field(u, factor)
use heatflow_kinds, only: dp
implicit none (type, external)
real(dp), intent(inout) :: u(:)
real(dp), intent(in) :: factor
u = u*factor
end subroutine scale_field
program implicit_none_external
use heatflow_kinds, only: dp
implicit none (type, external)
! With (external) every external procedure used here needs an explicit
! interface or the external attribute; an interface body provides it.
interface
subroutine scale_field(u, factor)
import :: dp
real(dp), intent(inout) :: u(:)
real(dp), intent(in) :: factor
end subroutine scale_field
end interface
real(dp) :: u(3)
u = [1.0_dp, 2.0_dp, 3.0_dp]
call scale_field(u, 2.0_dp)
print '(a, 3f6.1)', 'scaled: ', u
end program implicit_none_external
scaled: 2.0 4.0 6.0
The interface body needs an import statement naming dp to make the kind from the host module visible; an interface body does not see its host’s
names otherwise. The best answer for new code is a module procedure, which always has an explicit interface (see
Modules and Submodules); the example uses an external procedure
only because that is where implicit none (external) matters.
Without the interface body, the call is rejected. This second example is intentionally invalid:
! Intentionally non-conforming under implicit none (type, external): the call
! to the external subroutine has neither an interface body nor the external attribute.
program error_implicit_external
implicit none (type, external)
real :: u(3) = [1.0, 2.0, 3.0]
call scale_field(u, 2.0)
print *, u
end program error_implicit_external
04_error_implicit_external.f90:6:26:
6 | call scale_field(u, 2.0)
| 1
Error: Procedure 'scale_field' called at (1) is not explicitly declared
Flang says 'scale_field' is an external procedure without the EXTERNAL attribute in a scope with IMPLICIT NONE(EXTERNAL). Flang’s
switch for the same check is -fimplicit-none-ext; GNU Fortran has no separate switch for it, so the statement in the source is the
portable way to get it.
Other uses of the implicit statement
implicit integer (a-h) and similar statements remap the letters. They are valid but can never be combined with implicit none,
and the authors of the Metcalf-Reid-Cohen-Bader reference (MRCB App. A.9) recommend against them; that is the authors' advice, not an
obsolescence in the standard. Under implicit none every name needs an explicit type before it is used in a specification expression or a parameter statement, so keep
the explicit type declaration first.
Status in the standard
Default implicit typing is not obsolescent in Fortran 2023. The Fortran 2028 working draft J3/26-007r1 adds it to the list of obsolescent
features (Annex B.3.15: "a significant source of programming errors"). It is still described only as upcoming here, because Fortran 2028
is at committee-draft stage and is not published. The practical advice is the same either way: every program unit and interface body should
begin with implicit none. See
Legacy Features and Modernization and
What’s New and Fortran 2028 Preview.
Attribute reference
An attribute is a property of an entity other than its type, and Fortran has more of them than most languages because it separates what a name is (type, kind, shape) from how it may be used (intent, aliasing, visibility). The attributes of S 8.5 are given in the attribute list of a type declaration statement or in a separate attribute specification statement (S 8.6, see Attribute specification statements). The table lists each with its clause, where it can appear, what it does and the standard in which it appeared.
| Attribute | S clause | Applies to | Effect and main constraints | Since |
|---|---|---|---|---|
|
8.5.13 |
Named constants, any intrinsic or derived type |
The entity is a constant defined by a constant expression and cannot be a variable, coarray or procedure (C852). Must be initialized (C807). |
F77 (statement); F90 (attribute) |
|
8.5.8 |
Variables, named constants, dummies, function results |
Gives the rank and bounds: explicit shape, assumed shape |
F90 |
|
8.5.3 |
Variables and components (also dummies and function results) |
Storage is created by |
F90 (local arrays); TR 15581 and F2003 (dummies, results, components, scalars) |
|
8.5.14 |
Variables, components, dummies, function results, procedures |
Can be associated with a target during execution. Cannot be combined with |
F90 |
|
8.5.18 |
Variables |
Allows pointers to refer to the object. Cannot be combined with |
F90 |
|
8.5.10 |
Dummy data objects and dummy procedure pointers |
Declares the intended direction of data flow; the compiler can reject assignments and passing an unsuitable actual argument. |
F90 |
|
8.5.12 |
Dummy arguments |
The argument may be omitted in the call; test with |
F90 |
|
8.5.19 |
Dummy data objects |
Pass by value: the dummy is a private copy. Not allowed with |
F2003 |
|
8.5.16 |
Local variables, common blocks, procedure pointers |
The variable keeps its value, allocation and association status between invocations. Not allowed for dummies, function results or automatic objects (C862, C814). |
F77 |
|
8.5.15 |
Module variables and procedure pointers |
Readable through |
F2003 |
|
8.5.2 |
Entities in a module specification part |
Controls what other scoping units can access by |
F90 |
|
8.5.7 |
Array pointers, assumed-shape arrays and assumed-rank dummies |
Guarantees unit-stride storage, which allows pointer rank remapping and avoids copy-in/copy-out (C830). |
F2008 |
|
8.5.4 |
Variables |
The variable may take part in asynchronous input/output or asynchronous communication; disables code motion around it. |
F2003 |
|
8.5.20 |
Variables that are not |
May be referenced or changed by means outside the program (hardware, another thread); every access goes to memory. |
F2003 |
|
8.5.6 |
Variables and components |
Declares a coarray. The sum of rank and corank cannot exceed 15 (C822). Local coarrays must be |
F2008 |
|
8.5.5 |
Module variables, common blocks, types, procedures |
Interoperates with a C variable, type or function (C818-C821). |
F2003 |
|
8.5.17 |
Dummy data objects, allocatables and pointers |
Declares the rank with a constant expression instead of an array-spec. Equivalent to |
F2023 |
|
8.5.9, 8.5.11 |
Procedures |
Marks a name as an external procedure or as an intrinsic one (mutually exclusive, C842). Covered on the procedures page. |
F77 |
The restrictions in the table give the usual rejected combinations: pointer with allocatable or target, value with intent(out),
protected outside a module, save on a dummy, parameter on a variable. These are constraints of the standard, and every conforming
compiler must diagnose them.
intent
intent says how a dummy argument is used (S 8.5.10). intent(in) means the dummy shall not be defined or become undefined during
the invocation, intent(out) means it becomes undefined on entry (except default-initialized components) and the procedure is expected
to define it, and intent(inout) means both. A nonpointer intent(in) object cannot appear in a variable definition context (C846), which
includes assignment, being the target of read, and being passed to another dummy that is intent(out) or intent(inout). For a pointer dummy,
intent(in) constrains the association status, not the target: you may assign to the pointee, you may not re-point it (C847).
-
An allocatable dummy with
intent(out)has its actual argument deallocated when the procedure is invoked (S 9.7.3.2). Passing a large buffer to such a dummy therefore frees it, which is a frequent source of "my array disappeared" reports. -
The actual argument for an
intent(out)orintent(inout)dummy must be definable: a literal or an expression is rejected at the call site. -
A dummy without
intentis not an error in the standard, but it gives up the compiler checks and the optimiser’s aliasing information. This page’s examples give every dummy anintent; the best-practice reasoning is in Best Practices and Style.
An intentionally invalid example shows the diagnostic when an intent(in) dummy is assigned:
! Intentionally invalid: assigns to an intent(in) dummy argument.
module counter
implicit none
contains
subroutine bump(by)
integer, intent(in) :: by
by = 0
end subroutine bump
end module counter
12_error_intent.f90:7:5:
7 | by = 0
| 1~~
Error: Dummy argument 'by' with INTENT(IN) in variable definition context (assignment) at (1)
Flang reports Left-hand side of assignment is not definable and, as the reason, 'by' is an INTENT(IN) dummy argument. Both compilers stop at compile time;
there is no runtime check for a violation of intent.
value, optional, protected, contiguous and rank
The following example, a module for tracking a run, shows several attributes working together. The module is private by default and
lists its public names; steps_taken is protected, so other units can read it but only the module can change it; advance takes a
value argument whose changes stay local; scaled_copy has an optional argument, tested with present; and norm_inf receives a contiguous,
target array and builds a rank-one pointer onto it with bounds remapping, which the standard allows only for contiguous targets.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
module run_state
use heatflow_kinds, only: dp, ip
implicit none
private ! everything private unless listed below
public :: steps_taken, reset_counter, advance, scaled_copy, norm_inf, first_row_sum
integer(ip), protected :: steps_taken = 0_ip ! readable outside, writable only here
real(dp), parameter, public :: half = 0.5_dp ! accessibility inside the attribute list
contains
subroutine reset_counter()
steps_taken = 0_ip
end subroutine reset_counter
! value: the dummy is a private copy; the actual argument can be an expression.
subroutine advance(by)
integer(ip), value :: by
by = by*2_ip ! legal: changes only the local copy
steps_taken = steps_taken + by
end subroutine advance
! intent, optional and present(); intent(out) makes y undefined on entry.
subroutine scaled_copy(x, y, factor)
real(dp), intent(in) :: x(:)
real(dp), intent(out) :: y(:)
real(dp), intent(in), optional :: factor
y = x
if (present(factor)) y = y*factor
end subroutine scaled_copy
! contiguous guarantees unit stride; target allows pointers to refer to it.
function norm_inf(u) result(r)
real(dp), intent(in), contiguous, target :: u(:, :)
real(dp) :: r
real(dp), pointer, contiguous :: flat(:)
flat(1:size(u)) => u ! pointer rank remapping needs a contiguous target
r = maxval(abs(flat))
end function norm_inf
! An ordinary assumed-shape dummy; the rank clause equivalent is shown in the next example.
function first_row_sum(u) result(s)
real(dp), intent(in) :: u(:, :)
real(dp) :: s
s = sum(u(1, :))
end function first_row_sum
end module run_state
program attributes
use heatflow_kinds, only: dp, ip
use run_state
implicit none
integer(ip) :: k
real(dp) :: x(3), y(3), u(2, 3)
k = 5_ip
call advance(k)
call advance(3_ip) ! a constant is fine for a value dummy
print '(a, i0, a, i0)', 'k after advance: ', k, ' steps_taken: ', steps_taken
x = [1.0_dp, -4.0_dp, 2.0_dp]
call scaled_copy(x, y)
print '(a, 3f6.1)', 'copy: ', y
call scaled_copy(x, y, factor=half)
print '(a, 3f6.1)', 'scaled by half:', y
u = reshape([1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp, 5.0_dp, -6.0_dp], [2, 3])
print '(a, f6.1)', 'norm_inf: ', norm_inf(u)
print '(a, f6.1)', 'first row sum: ', first_row_sum(u)
call reset_counter()
print '(a, i0)', 'steps_taken after reset: ', steps_taken
end program attributes
k after advance: 5 steps_taken: 16
copy: 1.0 -4.0 2.0
scaled by half: 0.5 -2.0 1.0
norm_inf: 6.0
first row sum: 9.0
steps_taken after reset: 0
Points worth noting in this example:
-
advance(k)doubles its private copy and leaveskuntouched, sokis still5whilesteps_takenincreased by10and then by6. Avaluedummy can be given a literal or an expression (advance(3_ip)), which anintent(inout)dummy cannot. -
yisintent(out)inscaled_copy. Because it is an ordinary array, the dummy simply becomes undefined on entry; for an allocatable dummy the actual argument would be deallocated. -
An absent
optionalargument must not be referenced, sofactoris only used behindpresent. Passing an absent optional on to another procedure’s optional dummy is allowed and keeps it absent. -
The module-wide
privatestatement, thenpublic :: a, bfor the entities that make up the interface, is the usual way to keep an interface small. Attributeprotectedis specified only in a module and checked by the compiler on everyuse(C856, C859).
Protection is a compile-time check. This intentionally invalid program assigns to the protected variable from the main program:
! Intentionally invalid: assigns to a protected module variable outside its module.
module counter
implicit none
integer, protected :: steps = 0
contains
subroutine bump(by)
integer, intent(in) :: by
steps = steps + by
end subroutine bump
end module counter
program error_protected
use counter
implicit none
call bump(1)
steps = 10
print *, steps
end program error_protected
14_error_protected.f90:16:3:
16 | steps = 10
| 1~~~~~
Error: Variable 'steps' is PROTECTED and cannot appear in a variable definition context (assignment) at (1)
Flang says 'steps' is protected in this scope and names the use statement that made it visible.
volatile and asynchronous
volatile (S 8.5.20) tells the compiler that something outside the Fortran program can read or write the variable: memory-mapped hardware,
a signal handler, or a thread started from C. Every access must therefore go to memory, which switches off most optimisation of
that variable. asynchronous (S 8.5.4) is narrower: the variable may be the target of asynchronous input/output or of an asynchronous
communication that is still pending, so the compiler must not move accesses across the statements that start and complete the transfer. Its
use in asynchronous I/O is on
Derived-Type and Asynchronous I/O. The authors of MRCB (App. A.15)
recommend asynchronous over volatile where it is enough; again that is a recommendation, volatile is not obsolescent.
In a block construct, an asynchronous or volatile statement that names a variable of the host gives that variable the attribute inside the construct
only (S 11.1.4).
codimension and bind
codimension declares a coarray and is discussed in
Coarrays, Images and Synchronization. A coarray local
to a procedure must be allocatable or save, because an automatic coarray would need an implicit synchronisation on entry (S 8.5.6, C826). Fortran 2023 also allows a
data object with a coarray component to be an array or allocatable. bind(c) on a variable, type or procedure is described in
Interoperability with C.
The rank clause, typeof and classof (Fortran 2023)
Fortran 2023 adds three declaration forms that avoid repeating information (S 7.3.2.1 and S 8.5.17):
-
rank(n)declares a dummy, allocatable or pointer by rank with a constant expression. In the example below, the dummyais declaredreal(dp), rank(2), intent(in), an assumed-shape array of rank 2, and the localtis declaredreal(dp), allocatable, rank(rank(f%u)), so it has the rank of another object. The rank can be 0 for a scalar. -
typeof(x)declares an entity with the same declared type and type parameters asx(a non-polymorphic type).xmust be previously declared, may not be unlimited polymorphic or of abstract type (C711), andtypeofmay be used only in a declaration (C709). -
classof(x)is the polymorphic counterpart: an entity of the same declared type asx, polymorphic. It cannot be used for intrinsic-typex(C712).
These forms are most useful in generic code, where a variable must match the type of a dummy without naming it. They are also the Fortran 2023 building blocks for the generic programming facilities described in Generic and Rank Independent Programming.
Compiler support: GNU Fortran 16.2 rejects both rank(n) and typeof/classof (the diagnostics are Invalid character in name and
Mangled derived type definition), so the program below was compiled and run with LLVM Flang 23 only and is not checked with -std=f2023 on gfortran:
! Fortran 2023 rank clause and typeof/classof.
! gfortran 16.2 rejects both; LLVM Flang 23 accepts them.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, int64, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
module probes
use heatflow_kinds, only: dp, ip
implicit none
private
public :: field_t, total, describe
type :: field_t
integer(ip) :: nx = 0_ip
real(dp), allocatable :: u(:, :)
end type field_t
contains
! rank(2) declares an assumed-shape dummy of rank 2, the same as a(:, :).
function total(a) result(s)
real(dp), rank(2), intent(in) :: a
real(dp) :: s
s = sum(a)
end function total
subroutine describe(f)
class(field_t), intent(in) :: f
typeof(f%nx) :: n ! same type and kind as f%nx: integer(ip)
classof(f) :: copy_of_f ! same declared type as f; polymorphic
allocatable :: copy_of_f
real(dp), allocatable, rank(rank(f%u)) :: t ! same rank as f%u
n = f%nx
allocate (copy_of_f, source=f)
allocate (t(n, n))
t = 1.0_dp
print '(a, i0, a, i0, a, f4.1)', 'n = ', n, ', rank of t = ', rank(t), ', sum(t)/n**2 = ', sum(t)/real(n*n, dp)
print '(a, l2)', 'copy has same type as f: ', same_type_as(copy_of_f, f)
end subroutine describe
end module probes
program rank_typeof_classof
use heatflow_kinds, only: dp, ip
use probes
implicit none
type(field_t) :: f
real(dp) :: a(2, 3)
f%nx = 3_ip
allocate (f%u(3, 3))
f%u = 0.0_dp
a = reshape([1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp, 5.0_dp, 6.0_dp], [2, 3])
print '(a, f5.1)', 'total(a) = ', total(a)
call describe(f)
end program rank_typeof_classof
total(a) = 21.0
n = 3, rank of t = 2, sum(t)/n**2 = 1.0
copy has same type as f: T
Source: J3/23-007r1, 7.3.2.1 and 8.5.17 RANK clause, LLVM Flang: Fortran standards support, WG5 N2212: The new features of Fortran 2023
Check each compiler’s own Fortran 2023 support table (Intel ifx, NAG, Cray) before relying on them. Portable code that has to build everywhere writes the type out.
The block construct
block (S 11.1.4, since Fortran 2008) is an executable construct that can contain its own specification part. Its declarations are
construct entities with the scope of the block. It is the Fortran counterpart of declaring a local variable in the middle of a C block.
-
The specification part may contain
use,importand any declaration exceptcommon,equivalence,intent,namelist,optional, statement functions andvalue(C1107). The block itself may not begin with aformatordatastatement (C1109). -
Specification expressions in the block are evaluated on entry, so a
blockis the place for automatic arrays sized by a value computed earlier in the procedure. The type parameters are fixed at theblockstatement and do not change if the variables they use change afterwards (S 8.3). -
A variable declared in the block with the same name as one outside hides it; the outer variable is unchanged.
-
Unsaved allocatable variables of the block are deallocated when the construct ends. A
savevariable in a block retains its value and is shared by all instances of the subprogram (S 8.5.16). -
Implicit typing is not affected by
block: the mapping is the one of the host scoping unit. Asavestatement outside a block does not make the block’s own variables saved. -
A construct name allows
exitto leave the block early, and a branch can leave but not enter it.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
program block_construct
use heatflow_kinds, only: dp, ip
implicit none
real(dp) :: u(6), residual
real(dp) :: tmp
integer(ip) :: n, pass
u = [0.0_dp, 4.0_dp, 8.0_dp, 8.0_dp, 4.0_dp, 0.0_dp]
n = size(u, kind=ip)
tmp = -1.0_dp
! A block with its own declarations: they exist only inside the construct.
smoothing: block
real(dp) :: work(n) ! automatic array sized at run time
real(dp) :: tmp ! hides the outer tmp, never touches it
integer :: i
work = u
do i = 2, int(n) - 1
tmp = 0.5_dp*(work(i - 1) + work(i + 1))
u(i) = 0.5_dp*(work(i) + tmp)
end do
residual = maxval(abs(u - work))
if (residual < 1.0e-12_dp) exit smoothing ! leave the construct early
print '(a, es9.2)', 'residual after one sweep: ', residual
end block smoothing
print '(a, f5.1)', 'outer tmp unchanged: ', tmp
print '(a, 6f6.2)', 'u: ', u
! A saved variable inside a block keeps its value between executions.
do pass = 1_ip, 3_ip
block
integer, save :: visits = 0
visits = visits + 1
print '(a, i0, a, i0)', 'pass ', pass, ', visits ', visits
end block
end do
end program block_construct
residual after one sweep: 1.00E+00
outer tmp unchanged: -1.0
u: 0.00 4.00 7.00 7.00 4.00 0.00
pass 1, visits 1
pass 2, visits 2
pass 3, visits 3
The visits variable inside the unnamed block is saved, so it counts the three executions even though a new block is entered
each time. Without save it would be recreated on every entry. block is not a function: there is no value, so a block is useful for scoping
temporaries and for exit, not for returning results. See
Control Flow for exit and construct names.
Named constants and constant expressions
The parameter attribute (S 8.5.13) makes an entity a named constant. Its value comes from a constant expression and its type, kind
and shape are fixed at the declaration. A named constant cannot be modified, has no storage that can be accessed by address (it cannot be a
target), and may be used wherever a literal of that type is allowed, including kind parameters (real(wide)), array bounds and
initializations. A constant expression (S 10.1.12) is built from:
-
literals and other named constants (or their subobjects);
-
array constructors and structure constructors whose elements are themselves constant expressions;
-
elemental standard intrinsic functions of constant arguments (
atan,nint,real,selected_real_kind); -
transformational standard intrinsic functions of constant arguments, apart from
command_argument_count,get_team,num_images,team_numberandthis_image, which are never allowed, andnullandtransfer, which have rules of their own; -
specification inquiries on constants (
size,kind,digits,tiny,huge,len), and selected inquiries fromieee_arithmetic; -
the
ac-do-variableof an array constructor’s implied do, and a parenthesised constant expression.
No user-defined operation or function may appear. The kind of every operand should be explicit: 4.0_dp*atan(1.0_dp) is a double-precision
value of pi, while 4*atan(1.0) is a default-real value that is widened afterwards, losing the extra digits.
An implied-shape array, written with (*) for a named constant, takes its extent from the initializer, so adding an element does not
require editing a size. This applies to named constants only (C838).
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
implicit none
private
public :: sp, dp, ip, lp
integer, parameter :: sp = real32
integer, parameter :: dp = real64
integer, parameter :: ip = int32
integer, parameter :: lp = int64
end module heatflow_kinds
program named_constants
use heatflow_kinds, only: dp, ip
implicit none
! Constant expressions: literals, other named constants and intrinsic
! functions of constant arguments.
real(dp), parameter :: pi = 4.0_dp*atan(1.0_dp)
real(dp), parameter :: lx = 1.0_dp, ly = 0.5_dp
integer(ip), parameter :: nx = 41_ip
integer(ip), parameter :: ny = nint(real(nx - 1_ip, dp)*ly/lx) + 1_ip
! A named array constant; the implied shape (*) takes its extent from the value.
integer(ip), parameter :: sample_i(*) = [1_ip, (nx - 1_ip)/2_ip + 1_ip, nx]
! The kind of a real can itself come from a constant expression.
integer, parameter :: wide = selected_real_kind(p=15, r=300)
real(wide), parameter :: tiny_value = tiny(1.0_wide)
! Character constants: the length can be assumed from the value.
character(len=*), parameter :: title = 'heatflow plate'
character(len=*), parameter :: names(3) = ['dirichlet', 'neumann ', 'periodic ']
! A derived-type constant built from a structure constructor.
type :: point_t
real(dp) :: x, y
end type point_t
type(point_t), parameter :: origin = point_t(0.0_dp, 0.0_dp)
! A constant expression can use array inquiry and transformational functions.
integer(ip), parameter :: total = sum(sample_i)
integer, parameter :: nsamples = size(sample_i)
print '(a, f10.7)', 'pi: ', pi
print '(a, i0, a, i0)', 'grid: ', nx, ' x ', ny
print '(a, i0, a, 3i4)', 'samples: ', nsamples, ' -> ', sample_i
print '(a, i0)', 'sum of samples:', total
print '(a, i0, a, i0)', 'wide kind: ', wide, ', digits ', digits(1.0_wide)
print '(a, es12.3e3)', 'tiny(wide): ', tiny_value
print '(3a, i0)', 'title: "', title, '", length ', len(title)
print '(a, a)', 'names(2): ', trim(names(2))
print '(a, 2f4.1)', 'origin: ', origin%x, origin%y
end program named_constants
pi: 3.1415927
grid: 41 x 21
samples: 3 -> 1 21 41
sum of samples:63
wide kind: 8, digits 53
tiny(wide): 2.225E-308
title: "heatflow plate", length 14
names(2): neumann
origin: 0.0 0.0
Notes on the example:
-
The named constant
title, declared withcharacter(len=*), takes its length from the value, and a character array constantnamestakes the length from the longest element, which is why the shorter strings are padded with blanks to the same length by hand; the standard requires all elements of an array constructor to have the same length unless the constructor begins with a type specification (the type and length followed by a double colon). -
wideis a kind obtained fromselected_real_kindin a constant expression and then used inreal(wide). The kinds module of the project does the same once for the whole program; see Intrinsic Types and Kinds. -
The middle sample is written
(nx - 1_ip)/2_ip + 1_ipso that the division is exact. Withnxequal to 41,nx/2_ip + 1_iptruncates the quotient, which gfortran reports with-Winteger-division(part of-Wall). -
Changing a named constant is an error, as the compilers show:
! Intentionally invalid: a named constant cannot be redefined.
program error_parameter
implicit none
integer, parameter :: nx = 41
nx = 81
print *, nx
end program error_parameter
This intentionally invalid program is reported by GNU Fortran 16.2 as:
11_error_parameter.f90:5:3:
5 | nx = 81
| 1~~
Error: Named constant 'nx' in variable definition context (assignment) at (1)
Flang says Assignment to constant 'nx' is not allowed. In GNU Fortran, -Wunused-parameter (named constants that are never used) is part of -Wextra, and -Wunused-dummy-argument (unused
dummy arguments) is part of -Wall, so a clean build under the flags of this section catches leftover constants and arguments.
The parameter statement (parameter (pi = 3.14159_dp)) is an older spelling. It is valid, but the type must be known before it is
used, so with implicit none the type declaration has to come first; MRCB App. A.6 recommends the attribute form, and so do the examples here.
Initialization, implicit save and the data statement
A variable declared with an initializer, as in integer(ip) :: count = 0_ip, is explicitly initialized (S 8.4). Two consequences surprise
programmers coming from C or Python:
-
The initialization happens once, before the program starts, not each time the procedure is entered. Explicit initialization of a variable that is not in a common block implies the
saveattribute (S 8.4), so the variable keeps its value between calls. In C terms, it isstatic, not an automatic variable assigned on entry. -
A variable without an initializer is not zero. A local variable that is not saved is undefined on entry to the procedure, and reading it before assigning is a programming error that the standard does not require the compiler to detect.
A variable declared in the specification part of a main program, a module or a submodule implicitly has the save attribute (S 8.5.16). The same
holds for a common block in these units. Local variables of other procedures are unsaved, unless they are initialized or named in a save
statement. A function result, a dummy argument and an automatic object can never be saved (C862, C814).
The decision for a local variable can be summarised as:
The following module contains three counters that differ only in how they start. next_with_init uses an initializer and therefore keeps
counting; next_without_init assigns count = 0 in the executable part and so always returns 1; next_with_save declares save without an initializer
and sets the first value with a guard, which is the explicit way of saying the same thing as the initializer. ticks is a module variable and is always saved. The program also shows
data statements (explicit initialization in the old style, with a repeat factor 2*3.0_dp and an implied do loop for a two-dimensional array) and
a pointer initialized to null().
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
module step_counter
use heatflow_kinds, only: dp, ip
implicit none
private
public :: next_with_init, next_without_init, next_with_save, ticks
! Module variables always have the save attribute.
integer(ip) :: ticks = 0_ip
contains
function next_with_init() result(n)
integer(ip) :: n
integer(ip) :: count = 0_ip ! explicit initialization implies save
count = count + 1_ip
n = count
end function next_with_init
function next_without_init() result(n)
integer(ip) :: n
integer(ip) :: count
count = 0_ip ! executable assignment: runs on every call
count = count + 1_ip
n = count
end function next_without_init
function next_with_save() result(n)
integer(ip) :: n
integer(ip), save :: count ! save without initialization: undefined at first
logical, save :: first = .true. ! a guard written in the modern style
if (first) then
count = 0_ip
first = .false.
end if
count = count + 1_ip
n = count
ticks = ticks + 1_ip
end function next_with_save
end module step_counter
program initialization_and_save
use heatflow_kinds, only: dp, ip
use step_counter
implicit none
integer :: k
real(dp) :: a(4)
integer(ip) :: m(2, 3)
integer :: i, j
character(len=3) :: tag(2)
real(dp), pointer :: p => null()
! DATA statement: the same effect as initialization in the declaration.
data a /1.0_dp, 2.0_dp, 2*3.0_dp/
data ((m(i, j), i=1, 2), j=1, 3) /1_ip, 2_ip, 3_ip, 4_ip, 5_ip, 6_ip/
data tag(1) /'abc'/, tag(2) /'xyz'/
do k = 1, 3
print '(a, i0, a, 3(i0, 1x))', 'call ', k, ': init/noinit/save = ', &
next_with_init(), next_without_init(), next_with_save()
end do
print '(a, i0)', 'ticks (module variable): ', ticks
print '(a, 4f4.1)', 'a: ', a
print '(a, 6i2)', 'm in array element order: ', m
print '(a, 2(a, 1x))', 'tag: ', tag
print '(a, l2)', 'p associated: ', associated(p)
end program initialization_and_save
call 1: init/noinit/save = 1 1 1
call 2: init/noinit/save = 2 1 2
call 3: init/noinit/save = 3 1 3
ticks (module variable): 3
a: 1.0 2.0 3.0 3.0
m in array element order: 1 2 3 4 5 6
tag: abc xyz
p associated: F
Things to remember from the data statement (S 8.6.7):
-
It gives explicit initialization, so the variables are implicitly saved. A variable that also has default initialization (a derived type with component initializers) cannot appear in a
datastatement. -
Values are matched in array element order, so
data m /1, 2, 3, 4, 5, 6/fills the first column first; an implied do (((m(i, j), i=1, 2), j=1, 3)) lets the layout be spelled out. A repeat count is writtenn*value. -
A
datastatement among executable statements is obsolescent (S B.3.5). The modern replacement is an initializer in the declaration or, for large tables, a named-constant array and an assignment. -
A variable may be initialized only once in the whole program: a
datastatement and an initializer for the same variable conflict.
Compilers differ in what happens to saved and unsaved variables that the program never defines, and none of it is portable.
GNU Fortran leaves unsaved locals undefined and puts large local arrays (more than -fmax-stack-var-size, 65536 bytes by default) in static memory,
where they are typically zero at program start; LLVM Flang zero-initializes saved variables without initializers as an extension, except
scalars of the main program (see Flang: Extensions). Neither behaviour is guaranteed by the standard, so
the code that relies on it fails under another compiler or after an optimisation level change.
The options that change storage and initialization are:
| Option | Compiler | Effect |
|---|---|---|
|
GNU Fortran |
Treats every local of non- |
|
GNU Fortran |
Largest local array (in bytes) placed on the stack; larger ones use static memory unless the procedure is |
|
GNU Fortran |
No static memory for locals; needed when non- |
|
GNU Fortran, Flang |
Put all arrays of unknown size and array temporaries on the stack; large local arrays then need a larger stack limit. |
|
GNU Fortran |
Give unsaved locals a known value, such as zero or a signalling NaN, to expose or tame uninitialized use. They do not initialize pointers or allocatable arrays. |
|
Flang |
Initialize locals without an explicit or default initialization. |
|
Flang |
Place all main program variables in static memory. |
|
GNU Fortran |
Warn about use of an undefined local. Enabled by |
A small intentionally wrong program shows why uninitialized variables survive testing: residual is never assigned when nsteps is 0.
! Intentionally wrong: "residual" is never defined when the loop does not run.
! Compile with -finit-real=snan -ffpe-trap=invalid to make the bug visible.
program error_uninitialized
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp) :: residual
integer :: step, nsteps
nsteps = 0
do step = 1, nsteps
residual = 1.0_dp/real(step, dp)
end do
if (residual < 1.0e-6_dp) then
print '(a)', 'converged'
else
print '(a)', 'not converged'
end if
end program error_uninitialized
With the strict flags of this page (no optimization), GNU Fortran 16.2 gives no warning, and the program prints "converged"
because the undefined value of residual happened to compare as smaller than the tolerance. With -O2 it warns 'residual' is used uninitialized [-Wuninitialized]. Compiled with
-finit-real=snan -ffpe-trap=invalid, the first use of the variable aborts the program:
Program received signal SIGFPE: Floating-point exception - erroneous arithmetic operation.
Backtrace for this error:
...
#3 0x4008b8 in error_uninitialized
at 16_error_uninitialized.f90:13
Floating point exception
Source: GNU Fortran: Options for code generation conventions, GNU Fortran: Options to request or suppress errors and warnings
(The frame addresses are omitted; the line and file of the offending comparison are in the backtrace.)
Specification expressions and automatic objects
Array bounds and character lengths of local variables may depend on run-time values if they are written as specification expressions (S 10.1.11).
This is how a procedure obtains a work array of the right size without allocate. A restricted expression may use:
-
constants and constant expressions;
-
dummy arguments that are not
optionaland notintent(out), and objects accessible by host or use association or in common; -
inquiry functions on them (
size,lbound,ubound,len,shape,kind), andpresent; -
any standard intrinsic function whose arguments are themselves restricted expressions;
-
references to a specification function: a nonintrinsic function that is
pure, not internal, not a statement function and has no dummy procedure (S 10.1.11).
An automatic data object is a nondummy object whose type parameter or array bound depends on a specification expression that is not a
constant expression (S 8.3). It is created on entry to the procedure (or on execution of the block statement) and disappears on return. It cannot have the
save attribute (C814) or an initializer (C808), and the values in the bound expressions are captured at entry: changing the variables afterwards does not
resize the object (S 8.3).
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
module smoothing
use heatflow_kinds, only: dp, ip
implicit none
private
public :: smooth, padded_width
contains
! A specification function: pure, not internal, not an intrinsic. It may
! appear in the declarations of other procedures.
pure function padded_width(n) result(w)
integer(ip), intent(in) :: n
integer(ip) :: w
w = n + 2_ip
end function padded_width
subroutine smooth(u, label)
real(dp), intent(inout) :: u(:, :)
character(len=*), intent(in) :: label
! Automatic objects: their shape and length come from specification
! expressions evaluated each time the procedure is entered.
real(dp) :: work(size(u, 1), size(u, 2))
real(dp) :: halo(padded_width(int(size(u, 1), ip)))
character(len=len(label) + 5) :: banner
integer :: i, j
work = u
do j = 2, size(u, 2) - 1
do i = 2, size(u, 1) - 1
u(i, j) = 0.25_dp*(work(i - 1, j) + work(i + 1, j) + work(i, j - 1) + work(i, j + 1))
end do
end do
halo = 0.0_dp
banner = '[' // label // '] ok'
print '(a, 2i3, a, i3, a, a)', 'work shape', shape(work), ' halo size', size(halo), ' ', banner
end subroutine smooth
end module smoothing
program specification_expressions
use heatflow_kinds, only: dp
use smoothing, only: smooth
implicit none
real(dp) :: small(4, 4), large(6, 5)
small = 0.0_dp
small(2:3, 2:3) = 8.0_dp
large = 1.0_dp
call smooth(small, 'small')
call smooth(large, 'large plate')
print '(a, 4f6.2)', 'small(2, :) = ', small(2, :)
end program specification_expressions
work shape 4 4 halo size 6 [small] ok
work shape 6 5 halo size 8 [large plate] ok
small(2, :) = 0.00 4.00 4.00 0.00
The two automatic arrays work and halo and the character variable banner have sizes that come from size(u, 1), a specification function call and len(label).
The call to smooth with a 4 by 4 and then a 6 by 5 array shows that the sizes follow the dummy argument at each call.
Practical consequences:
-
Automatic arrays are normally placed on the stack. Large ones can overflow it, which appears as a segmentation fault on entry to the procedure rather than as a Fortran error. Remedies are
allocatablelocal arrays (heap) or raising the stack limit (ulimit -son Unix systems);-fstack-arraysmakes the stack choice explicit in GNU Fortran and in Flang. See Allocatable Arrays and Dynamic Memory and Performance and Memory Layout. -
If a specification expression is wrong (for instance a negative size), the array is zero-sized, not an error;
-fcheck=boundscatches later out-of-range subscripts. -
An automatic array is not the same as an assumed-shape dummy such as
u(:, :)(which has the shape of the actual argument and no storage of its own); the dummy forms are covered in Array Arguments: Assumed Shape, Size and Rank. -
Evaluation of a specification expression must not call a procedure defined by the subprogram that contains it (S 10.1.11), and the order in which specification expressions are evaluated is processor dependent, so they should not depend on each other’s side effects (they can have none if the functions are pure).
Attribute specification statements
Every attribute can also be given in a separate statement after the declaration (S 8.6): dimension, allocatable, pointer, target, intent,
optional, parameter, save, value, protected, public/private, volatile, asynchronous, contiguous and codimension. The module below has the same effect as
one declaration per entity but spreads each across several statements; it is valid, but it is harder to read and easier to get wrong.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: dp, ip
integer, parameter :: dp = real64
integer, parameter :: ip = int32
end module heatflow_kinds
! Attribute specification statements: each attribute is given in a separate
! statement after the type declaration. Every entity could instead be written
! in one declaration, for example "real(dp), parameter, public :: gain = 2.0_dp".
module probe_bank
use heatflow_kinds, only: dp, ip
implicit none
private
integer(ip) :: nprobes
real(dp) :: gain
real(dp) :: readings
dimension readings(3)
parameter (nprobes = 3_ip, gain = 2.0_dp)
public :: nprobes, gain, readings, record
protected :: readings
contains
subroutine record(k, reading)
integer(ip) :: k
real(dp) :: reading
value :: k
intent(in) :: reading
readings(k) = reading*gain
end subroutine record
end module probe_bank
program attribute_statements
use heatflow_kinds, only: ip, dp
use probe_bank
implicit none
integer(ip) :: k
do k = 1_ip, nprobes
call record(k, real(k, dp))
end do
print '(a, i0, a, f4.1)', 'nprobes = ', nprobes, ', gain = ', gain
print '(a, 3f5.1)', 'readings: ', readings
end program attribute_statements
nprobes = 3, gain = 2.0
readings: 2.0 4.0 6.0
The rules are: an entity may be given an attribute only once per scoping unit (C815), so dimension readings(3) cannot be repeated;
the type must be declared before a parameter statement uses the name; and a dimension statement on an untyped name would create an
implicitly typed variable, which implicit none forbids. The authors of MRCB recommend not using the dimension,
codimension and parameter statements at all (MRCB App. A.6), because the type declaration then looks like the declaration of a scalar while the shape is
declared elsewhere. That is the authors' advice and it is shared by this section: put every attribute in the declaration so the whole description of
an entity is in one place. The accessibility statements are the exception: public :: a, b and a bare private are the usual way to define a module interface (as in
run_state above).
Compiler support and standards history
| Feature | Standard | Notes |
|---|---|---|
|
F2018 |
Accepted by GNU Fortran 16.2 and Flang 23 ( |
Implicit save of main program, module and submodule variables |
F2008 |
Earlier editions required an explicit |
|
F2008 |
Checked on both compilers (example above). |
|
F2008, F2003 |
Checked on both compilers (examples above). |
|
F2008, F2003 |
Not exercised on this page; see the pages that cover them. |
|
F2023 |
Rejected by GNU Fortran 16.2, accepted by Flang 23; check the Intel and NAG tables before use. |
Default implicit typing obsolescent |
F2028 (draft J3/26-007r1, B.3.15) |
Not obsolescent in F2023; the F2028 text is still a working draft. |
|
F77 and earlier |
The |
The standard-level support of other compilers is tracked on the compilers page; see Compilers and Compiler Options for the options used on this page and the per-compiler Fortran 2023 support tables.
Where the books are outdated
The MRCB chapters on declarations (Ch. 8, with Ch. 2 for the basics) take Fortran 2018 as the base language and Chapters 22 and 23 add Fortran 2023. Three points need adding for current practice:
-
MRCB Appendix A lists the authors' deprecations, among them the
dimension,codimensionandparameterstatements, non-defaultimplicitmappings,double precisionandvolatile. They are recommendations, not entries in the standard’s obsolescent list (Annex B.3), which for declarations contains onlycharacter*n,dataamong executables, assumed character length functions andcommon/equivalence. -
The book has no information about compilers. In practice
rank(n),typeofandclassof(the Fortran 2023 declarations in its Chapters 22 and 23) are currently accepted by LLVM Flang 23 and rejected by GNU Fortran 16.2, as shown above. -
The authors call implicit typing dangerous (MRCB §8.2.1) and recommend
implicit none, but it is not obsolescent in Fortran 2023; the Fortran 2028 working draft J3/26-007r1 makes it obsolescent. Treatimplicit noneas mandatory.
References
-
J3/23-007r1: Fortran 2023 final draft clauses 7.3.2.1, 8.2 to 8.8, 9.7.3.2, 10.1.11, 10.1.12, 11.1.4 and Annex B.3
-
J3/26-007r1: Fortran 2028 working draft, Annex B.3.15 (default implicit typing)
-
GNU Fortran: Options controlling Fortran dialect (
-fimplicit-none,-std) -
GNU Fortran: Options to request or suppress errors and warnings
-
GNU Fortran: Options for code generation conventions (
-fno-automatic,-finit-*,-fmax-stack-var-size,-fstack-arrays)