Fortran Best Practice Minibook

This is the same question I asked 10 years ago. Here are the two relevant parts:

I use the assumed-shape in my codes following this recommendation. However, I wonder if explicit-shape is not better after all:

  • It specifies the exact dimensions of all arrays (in and out) at compile time using runtime variables n, m, l, etc.
  • This helps the reader (me) to see what the dimensions are right away.
  • It eliminates the need to manually check that a square matrix was passed into a subroutine that expects A(:,:) (and give an error message otherwise), since one can declare it as X(n,n), and then the compiler itself can simply give a runtime error if an incompatible array is passed in.
  • I also wonder if this might allow some potential compiler optimizations.
  • And it definitely would allow compiler checks for things like matmul (as well as all my functions that use explicit-shape). You do not get any such compile time checks if you pass arrays to matmul with incompatible dimensions, because the compiler does not know until runtime.

The only downside is that an array temporary is created if you pass in non-continguous data. However, in practice I always end up with contiguous arrays. Except when prototyping, but in that case I don’t mind the array temporary.

I wonder if explicit-shape and assumed-shape arrays cannot simply be united:

  • Potentially allow non-contiguous implementation of explicit-shape (thus no array temporaries), unless bind(c), or if interfacing legacy Fortran such as the Lapack library (via interface, possibly one could require the contiguous keyword for that)
  • Allow to allocate explicit-shape

The last point would be very helpful, consider this code:

integer :: n
real(dp), allocatable :: X(n,n), Y(n,n)
integer :: m
read (*,*) m
allocate(X(m,m), Y(m,m))

This would check at runtime (with bounds checking enabled) that the array dimensions are conforming, i.e., that X and Y have exactly the same dimensions, and are square matrices.

One issue is what to do with the variable n, such as what happens if one assigns to it. One possible solution:

real(dp), allocatable :: X(size(X,1),size(X,1)), Y(size(X,1),size(X,1))

However, it is a lot more readable if the variable n is used. Another solution is:

integer, dim :: n
real(dp), allocatable :: X(n,n), Y(n,n)

which can also be used in subroutines as:

subroutine sub(X, Y)
integer, dim :: n = size(X,1)
real(dp), intent(in) :: X(n,n)
real(dp), intent(out) :: Y(n,n)
! No need for these checks:
! if (size(X,1) /= size(X,2)) error stop
! if (size(Y,1) /= size(Y,2)) error stop
! if (size(X,1) /= size(Y,1)) error stop
end subroutine

Also, not everything can be checked even in matmul at compile time, such as when the two arrays declared as:

integer :: n, m
real(dp), allocatable :: X(n,n), Y(m,m)
Z = matmul(X, Y)

But I think a lot more things could be checked. And even in this case, the compiler now knows the runtime constrain n == m, and so can use it for example to give an error message much earlier (say when the n and m is read from a file and it happens that n /= m).