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)).
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.
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;
}
-
constfunction:- Depends only on
aandb. If successive calls are made with the sameaandbvalues, the output will not change. - Returns a value without modifying any data outside the function.
- Depends only on
-
purefunction:- 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 sameavalue, the output can differ depending on the state ofc. - Returns a value without modifying any data outside the function.
- Depends on
-
"Normal" function:
- Depends on
c, a global variable. This restricts the function to bepure, at most. - However, the function also modifies
c, memory outside its scope, thus leading to a "normal" function.
- Depends on
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
-
purefunction:- 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 sameavalue, the output can be different depending on the state ofc. - Returns a value without modifying any data outside the function.
- Depends on
-
"Normal" function:
- Depends on
c, a public variable. This restricts the function to bepure, at most. - However, the function also modifies
c, memory outside its scope, thus leading to a "normal" function.
- Depends on
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.