Some common pitfalls in writing Fortran functions

Recently, I have been trying to port or interface some Fortran code for a project in Python. It was quite an uphill struggle for me. I thought I knew how to write subroutines in Fortran 90, but when I decided to write pure functions in the latest Fortran style, which was Fortran 2008, it did not go as well as I expected. The compiler yelled at me a screenful of errors. After quite a while of debugging a simple array function and reading a bunch of StackOverflow questions, I learned how to deal with these pitfalls the hard way.

  1. “Function result … at (1) has no implicit type”

    The example below will raises an error of this at the compile-time.

    function foo(x) result(bar)
        implicit none
        integer, intent(in) :: x
        bar = 42
    end function foo
    

    This is a common compiler error thrown at functions. What it means is that the compiler cannot determine the return type of the function; it must be explicitly declared. But where and how to declare the type? There are two ways to fix it. One way is to declare the type of function at the beginning,

    integer function foo(x) result(bar)
        implicit none
        integer, intent(in) :: x
        bar = 42
    end function foo
    

    This seems to be the recommended way, because it makes it clear that this function returns an integer (kinda like how you would write a C function.)

    Another way is to declare the result variable inside the function body. This works just fine. But nowadays it seems that the former one is the more prevalent style.

    function foo(x) result(bar)
        implicit none
        integer, intent(in) :: x
        integer :: bar
        bar = 42
    end function foo
    

    Note that you cannot initialize the result variable bar in the example above. For example, if you write integer :: bar = 42, the compiler will again yell at you “Function result ‘bar’ at (1) cannot have an initializer”. I think this style would be more prone to error than integer function foo(x) result(bar).

    So, in short, always declare the return type if the function does not return an array. (I’ll deal with the array function case later.)

  2. Function result is defined in the function header, not in the function body. You cannot decide which variable to return later in the function body, because there is no C-like return statement (or, you could also say, the Fortran return statement has an entirely different meaning).

    The result variable is defined with the result keyword. For example, in

    integer function foo(x) result(bar)
        ...
    end function foo
    

    the variable to return to the caller is bar and cannot be anything else.

    The result statement can be omitted. In this case, the result variable will have the same name as the function. For example, in

    integer function foo(x)
        ...
    end function foo
    

    the result variable is foo.

    On the other hand, the absence of a C-like return statement makes perfect sense because the function already knows which variable is the result variable the moment it is declared.

  3. Do not declare the type of the result variable twice. If the result variable type is already declared in the function header, there is no need to declare it again in the function body. This means the following example is wrong.

    integer function foo(x) result(bar)
        integer, intent(in) :: x
        integer :: bar = 42
    end function foo
    

    The compiler would raise an error for duplicate declarations, “Symbol ‘bar’ at (1) already has basic type of INTEGER”.

  4. Do not use intent(out) in the declaration of the result variable in the function body. The following example is wrong.

    function foo(x) result(bar)
        implicit none
        integer, intent(in) :: x
        integer, intent(out) :: bar
        bar = 42
    end function foo
    

    The compiler will raise an error at the line function foo(x) result(bar): “Symbol at (1) is not a DUMMY variable”. It is not obvious that this message would refer to the misuse of intent(out).

    See also: https://stackoverflow.com/questions/24170024/function-in-fortran-passing-array-in-receiving-array-out.

  5. Functions that return an array

    When the result variable is an array, we cannot declare its type in the function header. This means the following would not work.

    integer(1:5) function vec_foo(x) result(bar)
        ...
    end function vec_foo
    

    In this case, we do need to declare the result variable in the body of the function. For example,

    ! an array function that returns a fixed-length array
    function foo(x) result(bar)
        implicit none
        integer, intent(in) :: x
        integer, dimension(5) :: bar
        bar = [1, 2, 3, 4, 5]
    end function foo
    

    A function may also return a automatically allocated array whose size depends on the arguments. The result variable just needs to be declared with dimension variables determined from the arguments.

    See also: https://stackoverflow.com/questions/43768605/function-result-has-no-implicit-type.

  6. Array dimension must be a constant or an expression that evaluates to a constant in the declaration of an automatic array. (It is the same in C99.) The compiler error corresponding to this would be “Unexpected data declaration statement at (1)”. See also: https://stackoverflow.com/questions/13630254/unexpected-data-declaration-error-in-fortran-when-creating-array.

  7. There are two ways for a program to use a function defined in the same source file. The function may be included in the program,

    program my_program
        ...
        print *, foo(x)
    
    contains
    
        integer function foo(x)
            ...
        end function foo
    
    end program my_program
    

    Or, the function can be defined in a module that is placed in the same source file as the main program. (This seems to be the preferred style.)

    module my_module
    
    contains
    
        integer function foo(x)
            ...
        end function foo
    
    end my_module
    
    program my_program
        use my_module
        implicit none
        ...
        print *, foo(x)
    end program my_program
    

    If something goes wrong, the gfortran compiler will raise an error “Function result has no implicit type”, which is not very helpful in my opinion.

    See also: https://stackoverflow.com/questions/17751863/function-has-no-implicit-type.

    For more on the use of modules, subroutines, and functions: https://stackoverflow.com/questions/8412834/correct-use-of-modules-subroutines-and-functions-in-fortran.

  8. Polymorphism is only allowed by elemental functions, which apply the same operations on every element of an array. This means that function overloading can work on real and real, dimension(10), but cannot work on real and character, dimension(80) (a string of 80 characters long). For example, there is no easy way to mimic the Julia behavior of multiple dispatch

    add(x::Number, y::Number) = x + y
    add(x::String, y::String) = string(x, y)
    

    It also means that a function cannot decide to return a scalar or vector based on the result of evaluating a condition within the function at runtime. For example, in Python, we can write

    import numpy as np
    
    
    def scalar_or_vector(flag: bool) -> Union[int, np.ndarray]:
        if flag:
            return 0
        else:
            return np.ones(10)
    

    Again, there is no way this can be easily reproduced in Fortran.

    With some effort, however, there is a workaround using the interface construct. See the example presented in: https://stackoverflow.com/questions/49014237/fortran-function-that-returns-scalar-or-array-depending-on-input.

  9. More about functions: Use the pure keyword for pure functions, i.e., functions that have no side effects. Use the elemental keyword for elementwise operations that are to be applied on an array. I like to use them because these restrictions will help you write the program in a defensible way.