Clicky

Fortran Wiki
Source conventions

There are many possible programming conventions. Example here should not be considered the correct answer.

Contents


Name Prefixes

In languages where procedure and/or variable names have global scope, it is useful to define a common prefix to avoid namespace collisions. Modern languages provide namespace hierarchies that preclude the need for an explicit prefix on every entity.

Fortran Modules are global scope. Module names for a given code project should include a common prefix. For example, a module named ‘constants’ is likely to cause problems.

Entities within a module do not need a namespace prefix. Doing so just makes the code more verbose.

If you want to USE a module without explicit ONLY statements, and still avoid the potential namepsace collisions, one method is to use a namespace-prefix wrapper module. For example:

module zzz_module_prefixed
  use zzz_module, only: &
      zzz_sub1 => sub1, &
      zzz_sub2 => sub2
end module zzz_module_prefixed

Now, “use zzz_module_prefixed” imports the entire module, but with namespace prefixes on all entities.

Name Suffixes

Fortran entities all share a common namespace. You cannot have a procedure and a type with the same name. Therefore, type suffixes are useful. The Fortran convention is to prefer full words, so it seems reasonable to use full words whenever it would not become too verbose. I use the following:

Entitysuffix
MODULE_module
TYPE_type
PROGRAM_program
POINTER_ptr
SUBROUTINE_sub *
FUNCTION_func *

* The subroutine and function suffixes are obviously too verbose. These are only used when there is a reason to have subroutine and function versions of the same routine. I typically add a suffix only for the less-used variant. This could be avoided if Fortran allowed specific procedures to share the same generic name.

I do not use a suffix for allocatables because they behave more like normal variables. Pointers need more care, because they can leak memory.

LUNs defined by literal integers

(Joe Krahn) Logical Units should never be defined by a literal integer, including standard I/O units 5 and 6. Traditionally, this required user-defined procedures to allocate a free UNIT, and parameters to define standard I/O units. F2003 defines the units in ISO_FORTRAN_ENV, and F2008 include the OPEN NEWUNIT specifier to automatically allocate a free unit.

  • Use UNIT=* to read or write to standard I/O.

  • Use the values from ISO_FORTRAN_ENV for standard I/O units when necessary.

  • Always allocate units using F2008 NEWUNIT=, when it is available. Until then, use a function to locate an available unit number.

IMPLICIT NONE

IMPLICIT NONE has been strongly encouraged for some time. Joe Krahn instead always uses compiler options that disable the default implicit-none. Maybe the next Fortran standard will define IMPLICIT-NONE as the default? Meanwhile, it is just another line of extra verbosity.

Filename suffixes

Use ‘.f’ for all Fortran source formats. Traditionally, Fortran source always had the ‘.f’ suffix. With Fortran90, ‘.f90’ became the convention, Fortran95 still used ‘.f90’. The ‘.f90’ is really more to indicate free-format source rather than the appropriate language version. Fixed-format should no longer be used for new code. Rather than complicate the suffix issue further by including ‘.f03’ and ‘.f08’, Fortran should revert to the single common suffix, as is the convention in most languages. For example, C99 code still uses the ‘.c’ suffix.

On the other hand, compilers use the suffix to detect whether the source is fixed-form or free-form. If you are using only free-form source, then the above convention is appropriate, and a compiler option must be used to tell the compiler to interpret all files as free-form. If you are mixing fixed and free source, then the most practical solution, until compilers can detect it automatically, is to use ‘.f’ for all fixed-form source and ‘.f90’ for all free-form, regardless of which language specification is being used, thus avoiding the proliferation of version-specific suffixes.

Multiple compiler builds

For code intended to be portable, it is useful to compile code under multiple compilers. It is also useful to build both debug and optimized versions. Rather than copying or symlinking source files to multiple build locations, all builds can be done in the same location using subdirectories for intermediate and output files.

This is an example Makefile fragment. It is trimmed down from the actual Makefile to reduce verbosity. It defines $(OUTDIR) to be a subdirectory specific to a given compiler and build option, where all object amd .mod files are written. You can make a small change, then re-build any of the build forms without cleaning and starting from scratch each time.

#BUILD options: openmp, release, profile, debug
#FC options: ifort, gfortran

ifeq ($(BUILD),)
BUILD=release
endif
ifeq ($(FC),)
FC=gfortran
endif
OUTDIR = $(FC).$(BUILD)

#===================================================================
ifeq ($(FC),ifort)
CC = icc
FFLAGS = -implicitnone -free -module $(OUTDIR) -sox
FPPFLAGS = -cpp -I $(OUTDIR)
ifeq ($(BUILD),debug)
  FFLAGS += -g -mp -traceback -debug extended
else
  ifeq ($(BUILD),profile)
    FFLAGS += -O2 -p -prof-genx
  else
  # release or openmp
  FFLAGS += -O3 -IPF-fp-relaxed
    ifeq ($(BUILD),openmp)
      FFLAGS += -openmp
      LDFLAGS += -openmp
    endif
  endif
endif
ifneq ($(BUILD),openmp)
  FFLAGS += -stand f03
endif
endif #ifort

#===================================================================
ifeq ($(FC),gfortran)
CC = gcc
FFLAGS =  -fimplicit-none -ffree-form -J$(OUTDIR)
FPPFLAGS = -xf77-cpp-input -I $(OUTDIR)
ifeq ($(BUILD),debug)
  FFLAGS += -g -fbounds-check
  FFLAGS += -Wunused-parameter -Wunused-variable -Wuninitialized else
  FFLAGS += -O2
endif
endif #gfortran
#===================================================================
ifeq ($(FFLAGS),)
$(error unknown compiler)
endif

Procedure status return codes

Good C programming generally includes status return values for all functions that can produce an error, and proper code should check the error status from every call. Checking and passing back status codes in a call hierarchy can add overhead.

C++ removes the overhead by error handler support that can catch all errors in a hierarchy in one place. Unfortunately, making the errors recoverable also adds overhead, and the throw/catch mechanism can become quite complex for the compiler to handle,

In procedural code, the end result is normally aborted program execution, so all of the error-handling hierarchy is an unnecessary hassle. In this case, the most efficient error-handling system is to simply stop at the point of the error, possibly calling a routine to print information about the error context.

One approach is to make status return codes an optional argument. If the status return is present, the caller can decide how to handle the error. If it is absent, abort with an error message. This is how many Fortran intrinsics work, and it is an effective design that lets the caller choose either a simple procedural method, or some form of error handling.

Also see the page on Error handling.