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
55Calling 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 for
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 for array arguments.
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+ Alternatives for less common scenarios are discussed after the code examples
69+ below.
3670
3771### Code examples
3872
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
73+ The following program calculates the factorial of a number. To reflect a common
74+ project layout with multiple source files, the main program and the factorial
4175procedure are in different files:
4276
4377``` fortran {4,5} showLineNumbers
@@ -85,10 +119,10 @@ The compiler cannot catch this bug during compilation because the called
85119procedure has an implicit interface: it is an ` external ` element defined in
86120another source file.
87121
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.
122+ A simple solution is to encapsulate the procedure within a module. This makes
123+ an explicit interface available to callers through ` use ` association
124+ mechanisms, allowing the compiler to verify the provided arguments against the
125+ actual dummy arguments.
92126
93127Moving the ` factorial ` subroutine to a module is as simple as:
94128
@@ -151,32 +185,47 @@ $ ./a.out
151185Factorial of 5 is 120
152186```
153187
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- > ```
188+ The problem is not limited to ` external ` procedures. Any call made without an
189+ explicit interface carries the same risk. For example, in the following program
190+ there is no ` implicit none ` statement, so ` factorial ` is an implicit entity
191+ that also lacks an explicit interface when called:
192+
193+ ``` fortran {5} showLineNumbers
194+ program test_implicit_interface
195+ use iso_fortran_env, only: real32
196+ real(kind=real32) :: number, result
197+
198+ number = 5
199+ call factorial(number, result)
200+ print *, "Factorial of", number, "is", result
201+ end program test_implicit_interface
202+ ```
203+
204+ > [ !TIP]
205+ > Internal procedures also provide explicit interfaces automatically through
206+ > host association. They are a good choice when a procedure is only needed
207+ > within a single host procedure:
170208>
171- > Note the absence of `implicit none`, allowing the symbol `factorial` to be
172- > interpreted as an implicitly declared entity.
209+ > ``` fortran showLineNumbers
210+ > subroutine sub()
211+ > call internal()
212+ > contains
213+ > subroutine internal()
214+ > ! statements...
215+ > end subroutine internal
216+ > end subroutine sub
217+ > ```
173218
174219> [!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:
220+ > A handwritten `interface` block can provide an explicit interface, but also
221+ > introduces risks. The interface must duplicate the target procedure's
222+ > definition, but the compiler does not verify whether this replica matches the
223+ > actual specification of the procedure or not.
224+ >
225+ > In this example, the interface declared manually for `factorial` is
226+ > incorrect; the dummy arguments are `real` instead of `integer`. The compiler
227+ > will accept the call because it is only checked against the handwritten
228+ > `interface`, producing an incorrect result during execution:
180229>
181230> ```fortran {6,7} showLineNumbers
182231> program test_implicit_interface
@@ -198,23 +247,20 @@ Factorial of 5 is 120
198247> end program test_implicit_interface
199248> ```
200249>
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.
250+ > Generally, manual `interface` blocks should be reserved for cases where
251+ > module procedures aren't applicable, such as calling interoperable C/C++
252+ > procedures, dummy procedures, and procedure pointers.
205253
206254> [!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.
255+ > Many modern Fortran features require explicit interfaces, including
256+ > assumed-shape arrays, optional arguments, procedure arguments, and more.
257+ > Avoiding implicit interfaces is therefore also a prerequisite for writing
258+ > robust modern Fortran.
214259
215260> [!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.
261+ > If modifying legacy code is not feasible, consider creating module procedures
262+ > that wrap the legacy ones. This provides safer interfaces for call sites
263+ > while preserving existing implementations intact.
218264
219265### Related resources
220266
0 commit comments