Skip to main content

PWR003: Explicitly declare pure functions

Issue

Mark functions that do not produce side effects as pure to facilitate reasoning about data flow and program semantics, improving readability, maintainability, and correctness.

In computer programming, a function can be considered pure if its only side effect is returning a value, without modifying any memory outside its own local scope. Some programming languages provide built-in keywords to declare these properties, whereas others need compiler extensions.

Actions

Add the appropriate annotations to the function. For instance:

  • The GCC compiler for C provides the extension __attribute__((pure)).
tip

GCC's __attribute__((const)) goes a step further by stating that the function's return value depends solely on the values of its input arguments, thus not being affected by any system resource that may change between calls; e.g., depending on global data, receiving a pointer.

  • The Clang compiler for C supports both GCC's extensions.

  • The ARM compiler for C supports both GCC's extensions.

warning

This compiler also provides the __pure keyword, but it is equivalent to __attribute__((const))!

  • In the Fortran programming language, use the built-in keyword pure.

Relevance

Explicitly declaring properties about the functions called in the code provides valuable hints that help both the compiler and the developer better understand program semantics and data flow. Determining whether a function is pure can be a difficult and time-consuming task, especially in complex codebases.

One of the main challenges in large software projects is the correct management of data scoping and side effects; that is, handling variables in a way that avoids unintended interactions and non-obvious bugs. Insights such as annotating pure functions can be particularly helpful in this context.

Code example

C

The C code shown below demonstrates const, pure, and "normal" functions:

// Depends only on its arguments
// No side effects
__attribute__((const)) int example_const(int a, int b) {
return a + b;
}

int c = 1;

// Depends on external data (c)
// No side effects
__attribute__((pure)) int example_pure(int a) {
return a + c;
}

// Depends on external data (c)
// Modifies external data (c)
int example_impure(int a) {
c += 1;
return a + c;
}
  • const function:

    • Depends only on a and b. If successive calls are made with the same a and b values, the output will not change.
    • Returns a value without modifying any data outside the function.
  • pure function:

    • Depends on c, a global variable whose value can be modified between successive calls to the function by other parts of the program. Even if successive calls are made with the same a value, the output can differ depending on the state of c.
    • Returns a value without modifying any data outside the function.
  • "Normal" function:

    • Depends on c, a global variable. This restricts the function to be pure, at most.
    • However, the function also modifies c, memory outside its scope, thus leading to a "normal" function.

In the case of the pure and "normal" functions, it is equivalent that they access a global variable, or a pointer received as an argument, as it is in either case memory outside the scope of the function.

Fortran

The Fortran code shown below demonstrates pure and "normal" functions:

module example_module
implicit none
integer :: c = 1
contains
! Depends on external data (c)
! No side effects
pure function example_pure(a) result(res)
integer, intent(in) :: a
integer :: res
res = a + c
end function example_pure

! Depends on external data (c)
! Modifies external data (c)
function example_impure(a) result(res)
integer, intent(in) :: a
integer :: res
c = c + 1
res = a + c
end function example_impure
end module example_module
  • pure function:

    • Depends on c, a public variable whose value can be modified between successive calls to the function by other parts of the program. Even if successive calls are made with the same a value, the output can be different depending on the state of c.
    • Returns a value without modifying any data outside the function.
  • "Normal" function:

    • Depends on c, a public variable. This restricts the function to be pure, at most.
    • However, the function also modifies c, memory outside its scope, thus leading to a "normal" function.
warning

Fortran procedures marked with the pure attribute do not allow OpenACC/OpenMP parallelization directives. Be aware of this limitation in case you intend to use them.

References