Functions and subroutines
Naming a piece of work: functions that return a value, subroutines that report through their arguments, intent declarations that say who may change what, and arrays passed without their size.
Two kinds of procedure
A function returns one value, and it returns it through its own name: integer function add_up(values) assigns to add_up, and a caller writes total = add_up(xs) exactly as if it were a built-in. A subroutine does work rather than produce a value, is called with call, and reports back through arguments declared intent(out).
Use a function when there is one answer and the name can be part of an expression. Use a subroutine when there are several answers, or none — a routine that reads data, prints a report, or updates two arrays in place has nothing to return.
A function that calls itself must be declared recursive, and the compiler needs to see that keyword in the definition. Without it the compiler rejects the self-call rather than guessing that you meant recursion. Recursion is natural for a tree or a factorial, but the declaration is not optional.
intent says who may change what
Every dummy argument gets an intent. intent(in) means the procedure may read it and the compiler will reject any attempt to assign to it. intent(out) means the procedure is expected to fill it in, and the caller's value is gone. intent(inout) means both.
This is more than documentation. An intent(in) argument cannot be silently overwritten halfway through a long routine, and a reader of the signature knows at a glance which names come back changed. It is the cheapest correctness check in the language.
Where the procedures live
The contains statement sits at the end of the program, and the procedures follow it, with end program after the last one. This puts them in the same file and the same scope, so they are visible to the program without any further declaration.
Internal procedures do see the program's variables, which is convenient and worth resisting: a procedure that reads a global is harder to test and harder to move. Passing what it needs as arguments costs a line and buys a procedure that works anywhere.
An array argument is declared with values(:), an assumed shape. The colon means "the size comes from whatever the caller passed", so the same function works on an array of three or three million, and size(values) gives the number the caller used.
A procedure defined outside the program, in its own file, has no explicit interface unless you write one. An interface block repeats the argument types and intents so the compiler can check the call. Modules avoid the repetition: the interface is generated from the procedure itself, which is one of the reasons chapter 10 puts procedures in a module.
A function and a subroutine, side by side
program demo
implicit none
integer :: xs(3), total
real :: avg
xs = [2, 4, 6]
total = add_up(xs)
call mean_of(total, size(xs), avg)
print *, total, avg
contains
integer function add_up(values)
integer, intent(in) :: values(:)
add_up = sum(values)
end function add_up
subroutine mean_of(total_in, count, result)
integer, intent(in) :: total_in, count
real, intent(out) :: result
result = real(total_in) / real(count)
end subroutine mean_of
end program demoArguments are shared, not copied
Fortran passes arguments by reference in effect: the procedure works on the caller’s storage, so an assignment to an intent(out) or intent(inout) argument is visible after the call returns. intent does not change that mechanism — it tells the compiler which direction the data may flow, so an assignment that would surprise the caller is rejected.
A function’s result is a variable with the function’s name, and it has to be assigned before the function returns. If a branch leaves it untouched, the value is undefined; it may be zero, or whatever was in that memory. Modern Fortran also lets you name the result — integer function span(values) result(width) — which keeps the body readable and stops the function name from being both the routine and a variable.
The value attribute passes a copy rather than a reference, which is useful when a procedure should not be able to change the caller’s variable even by accident. It is not the default, and adding it changes the interface, so the declaration has to match on both sides.
Three procedure mistakes to know: calling a function as if it were a subroutine, or forgetting call before a subroutine, produces an error that reads like a type error; assigning to an intent(in) argument is rejected for the same reason; and a function that assigns its result on only one branch returns undefined data on the other. The last one compiles cleanly, which is what makes it dangerous.
Try these next
- Add an
intent(inout)argument that counts how many values are above the mean. - Write a function that returns the larger of two integers, and call it inside a
print. - Change
swapto swap three values and check the result.
One value out, one value back
Run it, then change the array to [3, 5, 8, 10, 12] and let size decide the count. Then make add_up take a second argument and add it to the total.
Swap two values, measure a range
Run it and note that `swap` changes the caller’s variables. Then add a value to the array, and change `span` to return the mean instead, keeping the `result` name honest.
If a procedure needs more than a handful of arguments, or its signature keeps growing as the program does, that is usually a record asking to exist. Chapter 11 gives one student three fields and a name.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does intent(in) promise about an argument?
