Intermediate

Functions in Fortran

Learn how to define functions and subroutines in Fortran, including arguments, INTENT attributes, recursion, optional arguments, and passing procedures as parameters

Functions are how Fortran organizes reusable computation, and they are central to the language’s identity — the name Fortran itself comes from “FORmula TRANslation.” From the earliest versions, the ability to write a mathematical formula once and call it repeatedly was the whole point.

Fortran makes a clear distinction that many modern languages blur: it has both functions and subroutines. A function takes inputs and returns a single value, so you use it inside an expression (y = square(x)). A subroutine performs an action and communicates results back through its arguments, so you invoke it with call. Knowing which one to reach for is the first thing to learn about procedures in Fortran.

As an imperative, statically-typed language, Fortran requires you to declare the type of every argument and every result. Modern Fortran organizes procedures inside modules, which give them explicit interfaces — the compiler can then check that every call passes the right number and type of arguments. In this tutorial you’ll learn how to define functions and subroutines, control how arguments flow with intent, write recursive procedures, use optional and keyword arguments, and even pass one procedure to another.

Functions vs. Subroutines

A function returns a value; a subroutine performs an action. The result clause names the variable that holds a function’s return value, and contains groups the procedures inside a module.

Create a file named functions.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module geometry
    implicit none
contains

    ! A function takes inputs and returns a single value
    function circle_area(radius) result(area)
        real, intent(in) :: radius
        real :: area
        real, parameter :: pi = 3.14159265
        area = pi * radius * radius
    end function circle_area

    ! A subroutine performs an action and returns nothing
    subroutine print_banner(title)
        character(len=*), intent(in) :: title
        print '(A)', "==============="
        print '(A)', trim(title)
        print '(A)', "==============="
    end subroutine print_banner

end module geometry

program functions_demo
    use geometry
    implicit none
    real :: area

    call print_banner("Circle Area")

    area = circle_area(2.0)
    print '(A, F8.4)', "Area of radius 2.0: ", area
end program functions_demo

A function is used inside an expression (area = circle_area(2.0)), while a subroutine is invoked with the call statement (call print_banner(...)). The character(len=*) declaration is an assumed-length string — the subroutine accepts a string argument of any length.

Arguments and the INTENT Attribute

The intent attribute documents how each argument is used and lets the compiler catch mistakes. intent(in) is read-only, intent(out) is a result the procedure must set, and intent(inout) is modified in place. Because subroutines can have several intent(out) arguments, they are the idiomatic way to return more than one value.

Create a file named intent_args.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module swap_mod
    implicit none
contains

    ! intent(inout): both arguments are read and modified
    subroutine swap(a, b)
        integer, intent(inout) :: a, b
        integer :: temp
        temp = a
        a = b
        b = temp
    end subroutine swap

    ! Multiple intent(out) arguments return multiple results
    subroutine divmod(numerator, denominator, quotient, remainder)
        integer, intent(in)  :: numerator, denominator
        integer, intent(out) :: quotient, remainder
        quotient  = numerator / denominator
        remainder = mod(numerator, denominator)
    end subroutine divmod

end module swap_mod

program intent_demo
    use swap_mod
    implicit none
    integer :: x, y, q, r

    x = 10
    y = 20
    print '(A, I0, A, I0)', "Before swap: x = ", x, ", y = ", y
    call swap(x, y)
    print '(A, I0, A, I0)', "After swap:  x = ", x, ", y = ", y

    call divmod(17, 5, q, r)
    print '(A, I0, A, I0)', "17 / 5 = ", q, " remainder ", r
end program intent_demo

The I0 edit descriptor prints an integer using the minimum width needed, which keeps formatted output tidy. Declaring intent is optional, but it is a best practice in modern Fortran: if you accidentally assign to an intent(in) argument, the compiler refuses to build.

Recursion

A procedure that calls itself must be marked with the recursive keyword, and a recursive function must use a result clause. Variables declared inside a procedure are local by default — each recursive call gets its own copy, which is exactly what recursion needs.

Create a file named recursion.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module recur
    implicit none
contains

    recursive function factorial(n) result(fact)
        integer, intent(in) :: n
        integer :: fact
        if (n <= 1) then
            fact = 1
        else
            fact = n * factorial(n - 1)
        end if
    end function factorial

    recursive function fib(n) result(f)
        integer, intent(in) :: n
        integer :: f
        if (n < 2) then
            f = n
        else
            f = fib(n - 1) + fib(n - 2)
        end if
    end function fib

end module recur

program recursion_demo
    use recur
    implicit none
    integer :: i

    print '(A)', "Factorials:"
    do i = 1, 6
        print '(A, I0, A, I0)', "  ", i, "! = ", factorial(i)
    end do

    print '(A)', "Fibonacci sequence:"
    print '(10(I0, 1X))', (fib(i), i = 0, 9)
end program recursion_demo

The Fibonacci line uses an implied-do loop (fib(i), i = 0, 9) inside the output list together with the repeating format 10(I0, 1X) — a compact way to print a whole sequence on one line.

Optional and Keyword Arguments

Fortran supports optional arguments via the optional attribute, and the built-in present() function tests whether the caller supplied one. This is how Fortran provides default-like behavior. You can also use keyword arguments to name parameters at the call site, which improves readability and lets you skip optional arguments in the middle of a list.

Create a file named optional_args.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module greeter
    implicit none
contains

    subroutine greet(name, greeting)
        character(len=*), intent(in) :: name
        character(len=*), intent(in), optional :: greeting

        if (present(greeting)) then
            print '(A)', greeting // ", " // trim(name) // "!"
        else
            print '(A)', "Hello, " // trim(name) // "!"
        end if
    end subroutine greet

end module greeter

program optional_demo
    use greeter
    implicit none

    ! Call with only the required argument
    call greet("Ada")

    ! Call with the optional argument supplied
    call greet("Grace", "Welcome")

    ! Keyword arguments name parameters explicitly
    call greet(name="Alan", greeting="Greetings")
end program optional_demo

The // operator concatenates strings. Notice that present(greeting) is the only safe way to know whether an optional argument was passed — reading an absent optional argument is undefined behavior.

Passing Procedures as Arguments

Fortran lets you pass one procedure to another. The receiving procedure declares the expected signature with an interface block, so the compiler still type-checks every call. This enables higher-order patterns like applying a chosen operation to a pair of values.

Create a file named higher_order.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
module ops
    implicit none
contains

    function add(a, b) result(r)
        real, intent(in) :: a, b
        real :: r
        r = a + b
    end function add

    function multiply(a, b) result(r)
        real, intent(in) :: a, b
        real :: r
        r = a * b
    end function multiply

    ! apply takes another function as its first argument
    function apply(func, x, y) result(r)
        real, intent(in) :: x, y
        real :: r
        interface
            function func(a, b) result(res)
                real, intent(in) :: a, b
                real :: res
            end function func
        end interface
        r = func(x, y)
    end function apply

end module ops

program higher_order_demo
    use ops
    implicit none

    print '(A, F6.2)', "add(3, 4)      = ", apply(add, 3.0, 4.0)
    print '(A, F6.2)', "multiply(3, 4) = ", apply(multiply, 3.0, 4.0)
end program higher_order_demo

Because add and multiply are module procedures, they already have explicit interfaces, so they can be passed by name as actual arguments to apply.

Running with Docker

You can compile and run every example with the official GCC image, which includes gfortran — no local install required.

1
2
3
4
5
6
7
8
9
# Pull the official image
docker pull gcc:latest

# Compile and run each example
docker run --rm -v $(pwd):/app -w /app gcc:latest sh -c 'gfortran -o functions functions.f90 && ./functions'
docker run --rm -v $(pwd):/app -w /app gcc:latest sh -c 'gfortran -o intent_args intent_args.f90 && ./intent_args'
docker run --rm -v $(pwd):/app -w /app gcc:latest sh -c 'gfortran -o recursion recursion.f90 && ./recursion'
docker run --rm -v $(pwd):/app -w /app gcc:latest sh -c 'gfortran -o optional_args optional_args.f90 && ./optional_args'
docker run --rm -v $(pwd):/app -w /app gcc:latest sh -c 'gfortran -o higher_order higher_order.f90 && ./higher_order'

Expected Output

Running functions.f90:

===============
Circle Area
===============
Area of radius 2.0:  12.5664

Running intent_args.f90:

Before swap: x = 10, y = 20
After swap:  x = 20, y = 10
17 / 5 = 3 remainder 2

Running recursion.f90:

Factorials:
  1! = 1
  2! = 2
  3! = 6
  4! = 24
  5! = 120
  6! = 720
Fibonacci sequence:
0 1 1 2 3 5 8 13 21 34 

Running optional_args.f90:

Hello, Ada!
Welcome, Grace!
Greetings, Alan!

Running higher_order.f90:

add(3, 4)      =   7.00
multiply(3, 4) =  12.00

Key Concepts

  • Functions vs. subroutines — A function returns a single value and is used in expressions; a subroutine performs an action and is invoked with call.
  • The result clause — Names the variable that carries a function’s return value; it is required for recursive functions.
  • intent attributesintent(in), intent(out), and intent(inout) document and enforce how each argument is used, letting the compiler catch misuse.
  • Multiple return values — Use a subroutine with several intent(out) arguments, since functions return only one value.
  • Recursion — Mark self-calling procedures with recursive; local variables give each call its own private state.
  • Optional and keyword arguments — The optional attribute plus present() provide default-like behavior, and keyword arguments make calls clearer.
  • Modules give explicit interfaces — Defining procedures inside a module with contains lets the compiler type-check every call automatically.
  • Procedures as arguments — An interface block lets you pass one procedure to another for higher-order patterns.

Running Today

All examples can be run using Docker:

docker pull gcc:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining