Fortran array arguments
last modified November 14, 2026
A procedure that works on an array does not have to know its size when it is
written. Fortran offers four ways to declare an array dummy argument, each
with a different amount of shape information: explicit shape, where the extent
is an expression passed by the caller; assumed shape, where a colon lets the
compiler pass the bounds along with the data; assumed size, the old form that
passes only the address; and assumed rank, which accepts an array of any rank.
This article shows each form, how sections reach the procedure, and how
elemental and pure procedures turn a scalar
function into an array function.
Explicit-shape arguments
An explicit-shape dummy is declared with an extent, as in
integer, intent(in) :: a(n), where n is another
dummy argument. The procedure receives the address of the first element and
trusts the caller that n elements follow. The caller may pass a
larger array, in which case only the first n elements are visible,
and the procedure may modify the elements when the intent allows it.
program g1
implicit none
integer :: a(5)
a = [3, 1, 4, 1, 5]
call show(a, 5)
call scale(a, 5, 10)
call show(a, 5)
contains
subroutine show(a, n)
integer, intent(in) :: n
integer, intent(in) :: a(n)
print *, a
end subroutine show
subroutine scale(a, n, factor)
integer, intent(in) :: n, factor
integer, intent(inout) :: a(n)
a = a * factor
end subroutine scale
end program g1
show prints the five elements, and scale multiplies
each of them by ten in place, because its argument is
intent(inout). The second call to show therefore
prints 30, 10, 40, 10 and 50. The length is passed separately and must agree
with the declaration of the actual argument; nothing checks that at run time
unless bounds checking is enabled, because the procedure has no descriptor
that could carry the size.
$ gfortran g1.f90 -o g1
$ ./g1
3 1 4 1 5
30 10 40 10 50
Assumed-shape arguments
An assumed-shape dummy is declared with a colon, as in
integer, intent(in) :: a(:). The compiler passes a descriptor that
contains the bounds, so the procedure can call size,
lbound, ubound and shape on the
argument, and it works for an array of any length. The price is that the call
needs an explicit interface, which a module or an internal procedure supplies.
Inside the procedure the lower bound is 1 by default, even when the actual
argument was a section that started elsewhere.
module arraymod
implicit none
contains
subroutine show_bounds(a)
integer, intent(in) :: a(:)
print *, 'lbound:', lbound(a), 'size:', size(a)
end subroutine show_bounds
function total(a) result(s)
integer, intent(in) :: a(:)
integer :: s
s = sum(a)
end function total
end module arraymod
program g2
use arraymod
implicit none
integer :: a(6)
a = [1, 2, 3, 4, 5, 6]
call show_bounds(a)
call show_bounds(a(2:4))
print *, 'total: ', total(a)
print *, 'total of a(2:4):', total(a(2:4))
end program g2
The first call sees six elements starting at index 1, and the second sees
three elements, also starting at index 1, because the dummy was declared with
the default lower bound. The sums are 21 for the whole array and 9 for the
section 2, 3, 4. If the procedure needed the original indices it could declare
a(2:), but for a sum the renumbering makes no difference. Both
procedures are in a module, which is what gives the calls their explicit
interfaces.
$ gfortran g2.f90 -o g2 $ ./g2 lbound: 1 size: 6 lbound: 1 size: 3 total: 21 total of a(2:4): 9
Assumed-size arguments
The oldest form declares the last dimension with an asterisk, as in
integer, intent(in) :: a(*). It passes only the address of the
first element, so the procedure knows neither the size nor the bounds and must
receive the length separately, exactly like an explicit-shape argument but
without even the declared extent. Only the last dimension may be an asterisk,
and the form survives mainly in old libraries and in code that interfaces with
C.
program g3
implicit none
integer :: a(5)
a = [1, 2, 3, 4, 5]
call show_first(a, 3)
contains
subroutine show_first(a, n)
integer, intent(in) :: n
integer, intent(in) :: a(*)
integer :: i
do i = 1, n
print *, a(i)
end do
end subroutine show_first
end program g3
The subroutine prints the first three elements and ignores the rest, which is legal because the caller passed an array with at least three. The declaration says nothing about how many elements exist, so an index beyond the real end is undetected by the procedure itself. This is why new code prefers assumed shape whenever an explicit interface is available.
$ gfortran g3.f90 -o g3
$ ./g3
1
2
3
An assumed-size array has no shape to report, and asking for its size is a compile-time error rather than a wrong answer at run time:
program g3b
implicit none
integer :: a(5)
a = [1, 2, 3, 4, 5]
call show_first(a, 3)
contains
subroutine show_first(a, n)
integer, intent(in) :: n
integer, intent(in) :: a(*)
print *, size(a)
end subroutine show_first
end program g3b
The compiler rejects size(a) because the upper bound of the last
dimension is unknown. The message points at the reference and names the reason,
and the fix is to pass the length as an argument, as the previous example does,
or to switch the dummy to assumed shape.
$ gfortran g3b.f90 -o g3b
g3b.f90:12:22:
12 | print *, size(a)
| 1
Error: The upper bound in the last dimension must appear in the reference to the assumed size array ‘a’ at (1)
Assumed-rank arguments
Fortran 2018 adds the assumed-rank dummy, written a(..), which
accepts an actual argument of any rank, including a scalar. The procedure
cannot index the argument directly; it first asks for the rank with a
select rank construct and handles each case in its own branch.
This is how a generic wrapper can forward a scalar, a vector and a matrix to
the right implementation without writing three procedures.
program g4
implicit none
integer :: v(4)
integer :: m(2, 2)
v = [1, 2, 3, 4]
m = reshape([1, 2, 3, 4], [2, 2])
call describe(v)
call describe(m)
call describe(7)
contains
subroutine describe(a)
integer, intent(in) :: a(..)
select rank (a)
rank (0)
print *, 'scalar:', a
rank (1)
print *, 'vector of', size(a), ':', a
rank (2)
print *, 'matrix of', size(a), ':', a
rank default
print *, 'other rank'
end select
end subroutine describe
end program g4
The first call takes a vector of four elements, the second a 2 by 2 matrix
with the same four elements, and the third a scalar. Each branch of
select rank sees a dummy of the matching rank, so indexing and
inquiry functions work as usual inside it. The rank default
branch would catch a rank that the procedure does not handle; here it never
runs.
$ gfortran g4.f90 -o g4 $ ./g4 vector of 4 : 1 2 3 4 matrix of 4 : 1 2 3 4 scalar: 7
Sections and contiguity
A section passed to an assumed-shape dummy does not have to occupy
consecutive memory. a(1:8:2) selects every second element, and
is_contiguous reports whether an array or section is stored
without gaps. A dummy declared with the contiguous attribute
promises the procedure that its argument is contiguous, so when the actual
argument is strided the compiler makes a contiguous temporary copy and passes
that instead. The copy costs time and memory, which is why the attribute is
worth adding only where the procedure relies on it.
program g5
implicit none
integer :: a(8)
integer :: i
a = [(i, i = 1, 8)]
call plain(a(1:8:2))
call needs_contiguous(a(1:8:2))
contains
subroutine plain(x)
integer, intent(in) :: x(:)
print *, 'plain: contiguous =', is_contiguous(x)
end subroutine plain
subroutine needs_contiguous(x)
integer, intent(in), contiguous :: x(:)
print *, 'contiguous dummy: ', is_contiguous(x)
end subroutine needs_contiguous
end program g5
Both calls pass the same strided section. The first dummy has no
contiguous attribute, so the section keeps its stride and
is_contiguous is false. The second dummy requires contiguous
storage, so the compiler inserts a temporary, and inside the procedure the
inquiry is true. The example is compiled with -fcheck=bounds
rather than -fcheck=all, because the full check also prints a
runtime warning each time it creates such a temporary.
$ gfortran -std=f2018 -fcheck=bounds g5.f90 -o g5 $ ./g5 plain: contiguous = F contiguous dummy: T
Optional arrays
An array dummy can be optional, like any other argument, and the
procedure tests present before using it. This allows one
procedure to serve both a default case and a caller that supplies extra data.
The argument must not be referenced when it is absent, so the test has to come
first, and an assumed-shape optional dummy still needs an explicit interface.
program g6
implicit none
integer :: a(4)
a = [1, 2, 3, 4]
call report(a)
call report()
contains
subroutine report(a)
integer, intent(in), optional :: a(:)
if (present(a)) then
print *, 'sum:', sum(a)
else
print *, 'no array given'
end if
end subroutine report
end program g6
The first call supplies the array, so the subroutine prints its sum, 10. The
second call omits it entirely, and the subroutine takes the other branch.
Testing present(a) is the only safe way to ask; indexing an absent
argument or using it in an expression would be an error.
$ gfortran g6.f90 -o g6 $ ./g6 sum: 10 no array given
Elemental and pure procedures
An elemental procedure is written for scalar arguments and can be
called with arrays, in which case it is applied element by element and returns
an array of the same shape. Elemental procedures are pure by default, and a
pure procedure promises not to modify its arguments or any global
state, which lets the compiler call it from contexts that require that
guarantee. Both attributes are checked when the procedure is compiled.
module mathmod
implicit none
contains
elemental function square(x) result(y)
integer, intent(in) :: x
integer :: y
y = x * x
end function square
pure function mean(a) result(m)
real, intent(in) :: a(:)
real :: m
m = sum(a) / size(a)
end function mean
end module mathmod
program g7
use mathmod
implicit none
real :: x(4)
x = [1.0, 2.0, 3.0, 4.0]
print *, 'squares:', square([1, 2, 3, 4])
print *, 'mean: ', mean(x)
end program g7
square is declared for one integer, yet the call with a
four-element constructor returns 1, 4, 9 and 16, because the elemental
attribute makes the compiler generate the loop. mean is an
ordinary pure function over an assumed-shape array, and it returns 2.5. A pure
function may not assign to its arguments, use intent(out), or
write to a module variable, which is exactly the restriction that makes it
safe to use inside array expressions.
$ gfortran g7.f90 -o g7 $ ./g7 squares: 1 4 9 16 mean: 2.50000000
A complete example
The module below collects four procedures that work on a real array:
mean and stddev are pure functions,
normalize modifies the array in place into zero mean and unit
standard deviation, and max_position returns the index of the
largest element. The program prints the statistics, normalizes the data, and
prints the result. Note that maxloc(a, 1) returns a scalar for a
rank-one array, which is what the integer result needs.
module stats
implicit none
contains
pure function mean(a) result(m)
real, intent(in) :: a(:)
real :: m
m = sum(a) / size(a)
end function mean
pure function stddev(a) result(s)
real, intent(in) :: a(:)
real :: s, m
m = mean(a)
s = sqrt(sum((a - m) ** 2) / size(a))
end function stddev
subroutine normalize(a)
real, intent(inout) :: a(:)
real :: m, s
m = mean(a)
s = stddev(a)
a = (a - m) / s
end subroutine normalize
pure function max_position(a) result(p)
real, intent(in) :: a(:)
integer :: p
p = maxloc(a, 1)
end function max_position
end module stats
program g8
use stats
implicit none
real :: x(5)
x = [2.0, 4.0, 4.0, 4.0, 5.0]
print *, 'mean: ', mean(x)
print *, 'stddev:', stddev(x)
print *, 'max at:', max_position(x)
call normalize(x)
print *, 'normalized:'
print *, x
end program g8
The five values have a mean of 3.8 and a standard deviation of about 0.98. The largest value is 5, at position 5. Normalizing subtracts the mean and divides by the standard deviation, so the result has values around zero and one, with the same shape as the input. The module procedures call each other by name, and because they are all in the same module each call has an explicit interface.
$ gfortran g8.f90 -o g8 $ ./g8 mean: 3.79999995 stddev: 0.979795873 max at: 5 normalized: -1.83711731 0.204124197 0.204124197 0.204124197 1.22474492
Next steps
The Fortran modules article explains why the procedures above need a module to be checked, and the allocatable arrays article shows how the shape of an argument can change from call to call. The array intrinsics article covers the reductions that these procedures use, and the arrays article describes sections in more detail, and the Fortran strings article moves from arrays to text. A later article on recursion returns to procedures that call themselves.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have passed arrays to procedures with explicit shape, assumed shape, assumed size and assumed rank, seen how sections are re-indexed and when they are copied, made an array argument optional, and written elemental and pure procedures. The previous article, Fortran allocatable arrays, explains how those arguments get their size.
Author
List all Fortran tutorials.