Skip to content

Commit 9ad00f2

Browse files
committed
PWR068: Rewrite to highlight the correctness goal
The current PWR068 leans too hard on "put procedures inside modules". This masks the actual underlying issue, which is calling procedures through implicit interfaces. This rewrite highlights the problem of implicit interfaces first. Then, "module procedures" are presented as the preferred approach to solve the problem, while also clarifying that other mechanisms might be a better fit for other specific scenarios (internal procedures leverage host association, C/C++ interoperable procedures require `interface` blocks, etc.).
1 parent ef01e39 commit 9ad00f2

9 files changed

Lines changed: 124 additions & 79 deletions

File tree

Checks/PWR068/README.md

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,76 @@
1-
# PWR068: Encapsulate procedures within modules to avoid the risks of calling implicit interfaces
1+
# PWR068: Call procedures through explicit interfaces, preferably as module procedures
22

33
### Issue
44

55
Calling a procedure without an explicit interface prevents the compiler from
6-
verifying argument compatibility, increasing the risk of difficult-to-diagnose
7-
runtime bugs.
6+
verifying compatibility between the actual arguments at the call site and the
7+
procedure's dummy arguments. As a result, argument mismatches may compile
8+
without warning and later surface as incorrect results and
9+
difficult-to-diagnose runtime bugs.
810

911
### Actions
1012

11-
To enhance code safety and reliability, encapsulate procedures within modules
12-
to automatically provide an explicit interface at the point of the call.
13+
To enhance code safety and reliability, ensure that procedure calls use
14+
explicit interfaces.
15+
16+
For most Fortran code, prefer defining procedures inside modules. Module
17+
procedures automatically provide explicit interfaces to callers through `use`
18+
association mechanisms, making it the safest and most maintainable approach in
19+
most cases.
20+
21+
Other mechanisms can also provide explicit interfaces when module procedures
22+
are not the right fit. For example:
23+
24+
- Internal procedures also provide explicit interfaces automatically through
25+
host association.
26+
- Explicit `interface` blocks are helpful for C/C++ interoperable procedures,
27+
dummy procedures, and procedure pointers.
1328

1429
### Relevance
1530

16-
Fortran allows procedures to be called without explicit information about the
17-
number of expected arguments, order, or properties such as their type. In such
18-
cases, an _implicit interface_ is used. The caller simply provides a list of
19-
memory addresses, which the called procedure assumes point to variables
20-
matching the dummy arguments. This can easily lead to issues such as:
31+
Fortran allows procedure calls even when the caller has no information about
32+
the number, order, or properties of the dummy arguments (`type`, `kind`,
33+
`rank`, attributes, etc.). In such cases, the call uses an _implicit
34+
interface_.
35+
36+
With an implicit interface, the caller simply provides a list of memory
37+
addresses, and the called procedure interprets them according to its dummy
38+
argument declarations. Because the compiler does not know enough about the
39+
procedure at the call site, it isn't able to detect problems such as:
40+
41+
- **Wrong number of arguments:**
42+
- Missing actual arguments may cause the procedure to access unrelated
43+
memory, leading to undefined behavior.
44+
- Excess actual arguments may also lead to undefined behavior; for example,
45+
by interfering with hidden information passed by the compiler, such as
46+
descriptors.
47+
48+
- **Incompatible argument characteristics:** Passing actual arguments whose
49+
characteristics are incompatible with those of the dummy arguments. For
50+
example, passing a `real` where an `integer` is expected, or using a `real32`
51+
`kind` where `real64` is required.
52+
53+
- **Swapped arguments:** Accidentally changing the order of arguments can also
54+
introduce incompatibilities even when the number of arguments is correct.
55+
56+
> [!TIP]
57+
> To learn more about these issues and the importance of explicit interfaces,
58+
> see [PWR083](../PWR083/), [PWR088](../PWR088/), and [PWR089](../PWR089/).
2159
22-
- **Type mismatches:** Passing variables of one type (e.g., `real`) as another
23-
type (e.g., `integer`) causes errors due to different internal representations.
60+
In contrast, when a procedure call is made through an explicit interface, the
61+
compiler can verify argument compatibility at compile time, catching any errors
62+
before they reach runtime.
2463

25-
- **Missing arguments:**
26-
- **Input arguments:** Omitted arguments are initialized to undefined
27-
values, resulting in unpredictable behavior.
28-
- **Output arguments:** Writing omitted arguments results in invalid memory
29-
accesses, potentially crashing the program.
64+
For most Fortran code, the best way to avoid implicit interfaces is to define
65+
procedures inside modules. The interface is automatically derived from the
66+
procedure definition, remaining consistent at all times.
3067

31-
In contrast, a procedure with an explicit interface informs the compiler about
32-
the expected arguments, allowing it to perform the necessary checks at the
33-
point of the call during compile-time. The preferred approach to ensure a
34-
procedure has an explicit interface is to encapsulate it within a module, as
35-
illustrated below.
68+
Other less common scenarios are discussed after the code examples below.
3669

3770
### Code examples
3871

39-
The following program calculates the factorial of a number. To simulate a real
40-
project with multiple source files, the main program and the factorial
72+
The following program calculates the factorial of a number. To reflect a common
73+
project layout with multiple source files, the main program and the factorial
4174
procedure are in different files:
4275

4376
```fortran {4,5} showLineNumbers
@@ -85,10 +118,10 @@ The compiler cannot catch this bug during compilation because the called
85118
procedure has an implicit interface: it is an `external` element defined in
86119
another source file.
87120

88-
A simple solution is to encapsulate the procedure within a module. This informs
89-
the compiler about the exact location where the called subroutine is defined,
90-
enabling it to verify the provided arguments against the actual dummy
91-
arguments.
121+
A simple solution is to encapsulate the procedure within a module. This makes
122+
an explicit interface available to callers through `use` association
123+
mechanisms, allowing the compiler to verify the provided arguments against the
124+
actual dummy arguments.
92125

93126
Moving the `factorial` subroutine to a module is as simple as:
94127

@@ -151,32 +184,47 @@ $ ./a.out
151184
Factorial of 5 is 120
152185
```
153186

154-
> [!NOTE]
155-
> The previous example demonstrates how calls to `external` procedures are
156-
> performed through implicit interfaces. The same problem would occur if
157-
> `factorial` were an implicitly declared procedure, as shown in the following
158-
> example:
159-
>
160-
> ```fortran {5} showLineNumbers
161-
> program test_implicit_interface
162-
> use iso_fortran_env, only: real32
163-
> real(kind=real32) :: number, result
164-
>
165-
> number = 5
166-
> call factorial(number, result)
167-
> print *, "Factorial of", number, "is", result
168-
> end program test_implicit_interface
169-
> ```
187+
The problem is not limited to `external` procedures. Any call made without an
188+
explicit interface carries the same risk. For example, in the following program
189+
there is no `implicit none` statement, so `factorial` is an implicitly declared
190+
entity that also lacks an explicit interface at the point of call:
191+
192+
```fortran {5} showLineNumbers
193+
program test_implicit_interface
194+
use iso_fortran_env, only: real32
195+
real(kind=real32) :: number, result
196+
197+
number = 5
198+
call factorial(number, result)
199+
print *, "Factorial of", number, "is", result
200+
end program test_implicit_interface
201+
```
202+
203+
> [!TIP]
204+
> Internal procedures also provide explicit interfaces automatically through
205+
> host association. They are a good choice when a procedure is only needed
206+
> within a single host procedure:
170207
>
171-
> Note the absence of `implicit none`, allowing the symbol `factorial` to be
172-
> interpreted as an implicitly declared entity.
208+
> ```fortran showLineNumbers
209+
> subroutine sub()
210+
> call internal()
211+
> contains
212+
> subroutine internal()
213+
> ! statements...
214+
> end subroutine internal
215+
> end subroutine sub
216+
> ```
173217
174218
> [!WARNING]
175-
> It's possible to manually define explicit interfaces using the `interface`
176-
> construct at the call site. However, this approach introduces risks. The
177-
> procedure's definition must be duplicated, but there's no mechanism to ensure
178-
> this replica matches the actual definition of the original procedure, which
179-
> can easily lead to errors:
219+
> A handwritten `interface` block can provide an explicit interface, but also
220+
> introduces risks. The interface duplicates the target procedure's definition,
221+
> but the compiler does not verify whether this replica matches the actual
222+
> specification of the procedure or not.
223+
>
224+
> In this example, the interface declared manually for `factorial` is
225+
> incorrect; the dummy arguments are `real` instead of `integer`. The compiler
226+
> will accept the call because it is only checked against the handwritten
227+
> `interface`, resulting in an unexpected output during execution:
180228
>
181229
> ```fortran {6,7} showLineNumbers
182230
> program test_implicit_interface
@@ -198,23 +246,20 @@ Factorial of 5 is 120
198246
> end program test_implicit_interface
199247
> ```
200248
>
201-
> In this example, the manually declared interface for `factorial` is
202-
> incorrect; the dummy arguments are declared as `real` instead of `integer`.
203-
> This error won't be caught at compile time, and will still result in an
204-
> unexpected output during execution.
249+
> Generally, manual `interface` blocks should be reserved for cases where
250+
> module procedures aren't applicable, such as calling interoperable C/C++
251+
> procedures, dummy procedures, and procedure pointers.
205252
206253
> [!TIP]
207-
> When interoperating between Fortran and C/C++, it's necessary to manually
208-
> define explicit interfaces for the C/C++ procedures to call. Although this is
209-
> not a perfect solution, since there are no guarantees that these interfaces
210-
> will match the actual C/C++ procedures, it's still best to make the
211-
> interfaces as explicit as possible. This includes specifying details such as
212-
> argument intents, to help the Fortran compiler catch early as many issues as
213-
> possible.
254+
> Many modern Fortran features require explicit interfaces, including
255+
> assumed-shape arrays, optional arguments, procedure arguments, and more.
256+
> Avoiding implicit interfaces is therefore also a prerequisite for writing
257+
> robust modern Fortran.
214258
215259
> [!TIP]
216-
> If modifying legacy code is not feasible, create a module procedure that wraps
217-
> the legacy procedure as an indirect approach to ensure argument compatibility.
260+
> If modifying legacy code is not feasible, consider creating module procedures
261+
> that wrap the legacy ones. This provides safer interfaces for call sites
262+
> while preserving existing implementations intact.
218263
219264
### Related resources
220265

Checks/PWR068/benchmark/example.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
function euclidean_distance_f(x1, y1, x2, y2)
55
use iso_c_binding, only : c_double

Checks/PWR068/benchmark/solution.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
module vector_utils
55
implicit none

Checks/PWR068/example.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
program test_implicit_interface
55
use iso_fortran_env, only: real32

Checks/PWR068/example_factorial.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
pure subroutine factorial(number, result)
55
implicit none

Checks/PWR068/solution.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
program test_explicit_interface
55
use mod_factorial, only: factorial

Checks/PWR068/solution_mod_factorial.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
module mod_factorial
55
implicit none

Checks/PWR068/solution_with_type_mismatch.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
! PWR068: Encapsulate procedures within modules to avoid the risks of calling
2-
! implicit interfaces
1+
! PWR068: Call procedures through explicit interfaces, preferably as module
2+
! procedures
33

44
program test_explicit_interface
55
use iso_fortran_env, only: real32

0 commit comments

Comments
 (0)