Start With Explicit Status Handling Where Failure Is Expected
File I/O is where most Fortran programs first meet the real world, and the real world is unreliable. Opening a file that doesn't exist, reading malformed data, or exhausting available memory are all predictable failure modes. Fortran gives you the tools to handle them without drama: `iostat`, `iomsg`, and `stat`.
Consider a basic file open:
open(unit=10, file='data.csv', status='old', iostat=ios, iomsg=msg)
if (ios /= 0) then
write(*,*) 'Cannot open file: ', trim(msg)
stop 1
end if
Skip the `iostat` check and the runtime simply aborts with a terse, often unhelpful error. Handle it explicitly and you get the full system message in `msg`, plus the chance to decide what happens next: retry with a fallback path, prompt the user, or exit cleanly with context.
There's no denying this pattern is verbose. You'll write similar checks for `read` statements, `allocate` calls, and anywhere `stat=` is accepted. Some might argue it clutters the code. But in application code, that verbosity is actually the point. Each check is a visible decision point. You know exactly what the program does when something goes wrong, and so does anyone reading it six months later.
Propagate Errors Cleanly Across Procedures and APIs
Returning a result alongside an error indicator is one of the most composable patterns available in modern Fortran. A small derived type carrying an integer code and a character message does the job cleanly, and it sidesteps the ambiguity that comes with overloading `NaN` or sentinel integers as failure signals.
```fortran
type :: error_t
integer :: code = 0
character(len=128) :: message = ''
end type
```
A numerical kernel can return this alongside its real result, keeping the function's intent visible at every call site. Magic return values like `-9999.0` tell you something went wrong but nothing about where or why.
There's no denying the interface ceremony is heavier. Each procedure now carries an extra `intent(out)` argument, and callers must check it. The payoff comes when a lower-level routine sets `err%message = 'interpolation: index out of bounds at i=47'` and the calling routine prepends its own context before passing it upward. No duplicate output, no hidden state.
Pure functions complicate this slightly. Since they cannot have side effects, threading an `error_t` out argument is the cleanest option rather than returning `NaN` and hoping the caller notices. For library APIs especially, explicit error objects make testing straightforward and integration predictable.
Use `Error Stop` Sparingly and Only for Truly Fatal States
Reaching for `error stop` too quickly is one of the most common habits to unlearn. It terminates the entire program immediately, flushes no buffers in a guaranteed way across all compilers, and in a parallel image context it signals abnormal termination to every other image. That's a significant side effect for what might just be a bad input file.
There are situations where `error stop` is exactly right. If you've validated array dimensions earlier in a pipeline and then discover they're inconsistent at a later stage that assumed correctness, something has gone badly wrong internally. Killing the program loudly is better than silently corrupting results.
```fortran
if (size(a, 1) /= size(b, 1)) error stop "Internal error: dimension mismatch after validation"
```
A recoverable input error is a different story. If a user supplies an out-of-range coefficient, you can return an `iostat`-style status code and let the caller decide. Terminating there removes that choice entirely.
Inside reusable library procedures, `error stop` is almost always the wrong default. The caller may be running in a long-lived application, handling multiple datasets in sequence, or logging failures gracefully. Yanking control away from them isn't defensive programming. It's just abrupt. Small standalone executables are more forgiving, but even there, propagating status gives you testability for free.
Good Error Handling Makes Fortran Code Easier to Trust
Different failures behave differently, and one approach for each error generates troubles for the long term. Use `iostat` limitations to handle errors and apply some recovery for transient laughs, for example if a file is missing or improperly structured, or a unit has already been used. If something is deemed unrecoverable, use `error stop` to give up control and likely clean up, and guard against silent corruption in such cases. In code must be reusable, make your errors verbose and divergent with optional status arguments and clean-separated involved message strings that correspondingly prop up default otherways into detection and response. Real error handling isn't always about following new conventions and fads; it is more about keeping the bugs out in the open and avoiding future novice hacks.