🧭 Best Practices for Using Subroutines and Functions in Modern Fortran
- Adisorn O.
- Nov 8
- 3 min read
When developing large, performance-oriented programs—especially in FEM and optimization—it’s critical to keep your Fortran code modular, readable, and safe.
Below are four golden rules for managing subroutines and functions effectively in modern Fortran (Fortran 90+). 🔹1. Use Modules for Organization and Reusability
A module is like a toolbox — it groups related variables, constants, and functions in one unit, and lets other parts of the program access them using the use statement.
This avoids the “copy-paste jungle” of old F77 and guarantees type safety.
✅ Example
module math_utils
implicit none
private ! hide everything by default
public :: vector_norm, dot_product ! selectively expose useful routines
contains
function vector_norm(x) result(norm)
real(8), intent(in) :: x(:)
real(8) :: norm
norm = sqrt(sum(x**2))
end function vector_norm
function dot_product(a,b) result(res)
real(8), intent(in) :: a(:), b(:)
real(8) :: res
res = sum(a*b)
end function dot_product
end module math_utils
Now you can use it anywhere:
program test_mod
use math_utils
implicit none
real(8) :: v(3) = [3.0, 4.0, 12.0]
print *, 'Norm =', vector_norm(v)
end program test_mod🔹 2. Pass Arguments Properly (Allocatable vs. Static Arrays)
Always pass data through arguments, not global variables.Modern Fortran supports allocatable arrays, which make your subroutines reusable and memory-safe.
⚙️ Allocatable Array Example
subroutine compute_stiffness(K, n)
implicit none
integer, intent(in) :: n
real(8), allocatable, intent(out) :: K(:,:)
allocate(K(n,n))
K = 0.0
K = 2.0 ! Example fill
end subroutine compute_stiffnessUsage:
program test_alloc
implicit none
real(8), allocatable :: K(:,:)
call compute_stiffness(K, 4)
print *, 'K(1,1) =', K(1,1)
end program
✅ Tip: In Fortran, you can’t mix fixed and deferred dimensions,
e.g. real(8), allocatable :: K(4,:) ❌ — use K(:,:) instead.
subroutine add_vectors(a, b, c)
real(8), intent(in) :: a(3), b(3)
real(8), intent(out) :: c(3)
c = a + b
end subroutineUse static dimensions only when the array size is known and constant (e.g., 4 DOFs per beam element).
🔹 3. Use contains Correctly and Never Declare a Function Like a Variable
Every program unit (module, subroutine, or program) that defines internal helper routines must include contains.
This separates main logic from local helper functions.
❌ Wrong
subroutine main_calc()
real(8) :: helper ! ❌ This declares a variable, not a function!
call helper()
end subroutine✅ Correct
subroutine main_calc()
implicit none
real(8) :: result
result = helper(3.0)
print *, 'Result =', result
contains
function helper(x) result(y)
real(8), intent(in) :: x
real(8) :: y
y = x**2
end function helper
end subroutine main_calcNow helper() is recognized as an internal function, scoped only to this subroutine.
No need for external declarations or interfaces.
.
🔹 4. Control Scope with private and public
By default, everything in a module is visible externally — which can lead to name conflicts or unwanted exposure.Use private to hide everything, then selectively mark what’s allowed to be used with public.
✅ Example
module fem_helpers
implicit none
private
public :: assemble_beam, beam_stiffness
contains
subroutine assemble_beam(K,F,ke,fe,i1,i2)
real(8), intent(inout) :: K(:,:), F(:)
real(8), intent(in) :: ke(4,4), fe(4)
integer, intent(in) :: i1, i2
integer :: dof(4)
dof = [i1, i1+1, i2, i2+1]
K(dof,dof) = K(dof,dof) + ke
F(dof) = F(dof) + fe
end subroutine assemble_beam
function beam_stiffness(E,sec,L) result(ke)
real(8), intent(in) :: E, sec(5), L
real(8) :: ke(4,4)
! ... fill beam stiffness matrix ...
end function beam_stiffness
subroutine internal_debug() ! not public → invisible outside
print *, "Debug message"
end subroutine internal_debug
end module fem_helpersIn the main program:
program fem_test
use fem_helpers
implicit none
! Can call assemble_beam and beam_stiffness
! Cannot call internal_debug
end programBest Practice | Why It Matters | Key Syntax |
Use module | Reusability, type safety | module ... contains ... end module |
Pass arguments explicitly | Avoid globals, improve flexibility | intent(in/out), allocatable arrays |
Use contains for internal helpers | Keeps functions local & safe | contains ... function/subroutine |
Control visibility with private/public | Prevent name pollution | private, public :: func1, sub2 |


