UP | HOME

Why TREXIO ships the Fortran module as source, and will never ship a compiled .mod file

Table of Contents

This comes up often enough that it's worth writing down once, in full, so it can be pointed to instead of re-argued from scratch every time.

1. The short version

TREXIO's Fortran interface is a thin ISO-C-binding wrapper around a C library. We distribute it as a source file (trexio_f.f90) that you #include and compile yourself, alongside your own code. We do not ship a precompiled .mod file, and we never will. This is not an oversight, a missing packaging step, or something we haven't gotten around to. It's a deliberate design decision, and reversing it would reintroduce exactly the problem the design exists to avoid.

2. The .mod file is not what people think it is

When someone asks "why can't you just ship the .mod file, like a .h header?", the request smuggles in an assumption: that a .mod file is a Fortran header, a portable description of an interface. It isn't. A .mod file is a compiler-internal, undocumented, binary serialization of compiler state. There is no standard for its format. There is no cross-compiler compatibility guarantee, and often no cross-version guarantee within the same compiler. gfortran cannot read an Intel-compiled .mod, and gfortran 12 frequently can't read a gfortran 14 .mod either.

Contrast this with a C header (.h) plus a compiled shared library, e.g. zlib + zlib-devel. That works across compilers because C has a real, stable, platform-level ABI: calling convention, name mangling (or lack thereof), struct layout are all fixed by the platform, independent of which compiler produced libz.so. The header is just a declaration; it carries no compiled state. apt install zlib1g-dev gives you something that links against any compiler's output, because the contract being offered is a platform ABI contract, not a compiler-internal one.

Fortran has no equivalent of this. There is no "module ABI" that compilers agree on, because modules were never specified as an interchange format. They're an implementation detail of each compiler's front end. So a package that ships a .mod file under a -dev label is not offering the same guarantee that a C -dev package offers, even though the naming convention makes it look identical. It is a strictly weaker contract wearing the same badge: it silently locks every downstream consumer to one specific compiler (and often one specific version of it).

3. Why this actually matters for TREXIO specifically

This isn't an abstract purity argument. TREXIO is meant to be linked into quantum chemistry and electronic structure codes running on HPC clusters, where the whole point of environment-modules-style systems is that users mix and match compilers: a code built with nvfortran for GPU offload might need to link a CPU-side library that's fastest when built with ifx, or a cluster might carry three different Fortran toolchains side by side because different application codes require them. If TREXIO shipped a compiled .mod, the library would silently dictate which Fortran compiler every downstream project is allowed to use. Not because of anything intrinsic to the C library underneath, but purely as an artifact of how the Fortran binding happens to be packaged. That's backwards: a binding layer should not impose more constraints than the library it wraps.

4. Why source distribution has essentially no downside here

The objection to source distribution is usually "isn't that wasteful, recompiling the interface for every consumer?" For a library with a heavy, compiled Fortran implementation, that might be a fair concern. It doesn't apply to TREXIO, because there is no compiled Fortran logic to duplicate: trexio_f.f90 is an ISO-C-binding declaration layer over a C library. All the real computation already lives in libtrexio.so, compiled once, with the ordinary platform C ABI. Compiling the binding file costs a fraction of a second and produces no behavioral difference between consumers. It's pure interface declaration, the Fortran-world equivalent of a header. You're not paying for redundant computation; you're paying a trivial, one-time interface-generation cost in exchange for the thing Fortran cannot otherwise give you: compiler independence.

5. In practice: how a Fortran project uses it

Concretely, this means that Fortran users are expected to include the source version of the module directly in their own source tree, rather than link against a precompiled one. The corresponding file is installed to a standard location when TREXIO is installed via make install: it lands in the include directory alongside the library.

To avoid re-including this file by hand every time TREXIO is updated, the recommended pattern is to create a small wrapper file once, named for example trexio_module.F90 (note the capital F in the suffix, so the preprocessor runs), containing just:

#include <trexio_f.f90>

That file is compiled like any other source file in the project, and the resulting trexio_module.o is added at link time along with the rest of the object files.

6. Anticipating the usual counterarguments

6.1. "But other Fortran libraries ship .mod files, why can't you?"

Some do, and for libraries genuinely intended to be Fortran-only, Fortran-to-Fortran, that can be a defensible choice with its own tradeoffs. It is not a good model for a binding to a library implemented in another language, where the whole point is to be usable from arbitrary Fortran compilers without imposing a downstream toolchain choice that has nothing to do with the underlying library.

6.2. "Within our distro (Fedora/Debian/etc.), everything is rebuilt with one system compiler anyway, so this never bites."

True, and within a distro's own closed rebuild chain, shipping a .mod is self-consistent. The distro's own tooling absorbs the risk by always rebuilding dependents together. But that guarantee is narrower than it looks: the moment someone builds against the distro's TREXIO package from outside that closed chain (a cluster user with a module-loaded compiler that isn't the distro's system compiler, which is the norm rather than the exception in HPC) the fragility reappears exactly as described above. A packaging convenience internal to one distribution's build farm is not a substitute for actual compiler independence.

6.3. "Isn't recompiling part of the library at every downstream build unusual?"

It's exactly what C/C++ header-only components do routinely, and nobody considers that unusual. It's considered good practice specifically because it avoids baking in a fixed toolchain assumption. We're applying the same principle to the one part of TREXIO (the Fortran interface) that has no native ABI stability to rely on instead.

7. Bottom line

Shipping a compiled .mod file would trade a real, structural cross-compiler guarantee for a cosmetic resemblance to how C -dev packages look, while quietly imposing a compiler lock-in on every downstream Fortran project that TREXIO's C core doesn't have and doesn't need. Since trexio_f.f90 is a thin, cheaply-regenerable ISO-C-binding layer rather than compiled application logic, there is no meaningful cost to distributing it as source. Only a bounded, one-time interface compile in exchange for compiler independence across the entire Fortran ecosystem. This is a permanent design decision rather than an open question.

Author: TREX-CoE

Created: 2026-10-19 Mon 11:44

Validate