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 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
4174procedure 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
85118procedure has an implicit interface: it is an ` external ` element defined in
86119another 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
93126Moving the ` factorial ` subroutine to a module is as simple as:
94127
@@ -151,32 +184,47 @@ $ ./a.out
151184Factorial 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
0 commit comments