36 min read

> *"The only way to learn a new programming language is by writing programs in it. The first program to

Prerequisites

  • 1

Learning Objectives

  • Install gfortran on Linux, macOS, or Windows and verify the version from the command line.
  • Write, compile, and run a `Hello, world` program using `program … end program` and `print`.
  • Explain each stage of the compile–link–run cycle and name what the compiler and the linker each produce.
  • Choose the compiler flags that matter early — `-std=f2018`, `-Wall`, `-O2`, `-g`, `-fcheck=all` — and say what each one does.
  • Distinguish free-form from fixed-form source, and explain why all modern Fortran is written free-form.
  • Put `implicit none` in every program unit, and explain the class of bugs it prevents at compile time.

Chapter 2: Setting Up

"The only way to learn a new programming language is by writing programs in it. The first program to write is the same for all languages: Print the words hello, world." — Brian Kernighan and Dennis Ritchie, The C Programming Language

Overview

In Chapter 1 we argued about Fortran. In this chapter you use it. By the end you will have a working compiler on your machine, you will have written and run a program of your own, and — more important than either — you will understand the small pipeline that turns the text you type into a running program. That pipeline, the compile–link–run cycle, is the daily rhythm of every compiled language, and Fortran is the compiled language par excellence. Everything in the rest of this book passes through it, so we spend this chapter making it feel routine.

This is deliberately a slow, careful chapter, because the ninety minutes you spend now getting the toolchain right and understanding what each command does will save you days of confusion later. A scientist who does not understand the difference between a compiler error and a linker error, or who cannot read the version string their compiler prints, will lose hours to problems that a little foundational clarity would have prevented. We are going to prevent them.

In this chapter, you will learn to:

  • Install gfortran — the free GNU Fortran compiler — on Linux, macOS, or Windows, and confirm it works.
  • Write the canonical first program, Hello, world, with program … end program and the print statement.
  • Trace a source file through the compile–link–run cycle: source text → object fileexecutable, and say precisely what the compiler and the linker each contribute.
  • Reach for the handful of compiler flags that matter from your very first build — the ones that make the compiler warn you, check you, optimize for you, and target the right standard.
  • Tell free-form source (what we write) from fixed-form source (what you will inherit), and say why the switch happened.
  • Adopt the single most important habit in the language, implicit none, and understand the whole category of silent, expensive bugs it eliminates before your program ever runs.

Learning Paths

How to read this chapter by track. - 🔬 Scientist ("I just want to compute") — you cannot skip any of this; a compiler is the instrument. Give §2.4 (flags) and §2.7 (your first computation) the most attention, and keep the two compile commands from the Summary taped to your monitor. - 📖 Standard — read straight through; this chapter is short and foundational. - 🔧 Legacy ("I inherited old code") — §2.5 previews the fixed-form source you will meet in the wild, and §2.6 is the habit that old code lacks. The full archaeology of fixed-form waits for Chapter 17. - ⚡ HPC ("I need it fast and parallel") — §2.4 is your bread and butter; note the development-versus-release flag split, and the note in §2.1 about loading a compiler module on a cluster. The deep flag story (-O3, -march=native, -flto) is Chapter 30.


2.1 Installing gfortran

Fortran is a compiled language, which means you need a compiler: a program that reads your Fortran source and produces a native executable. Several excellent Fortran compilers exist — Intel's ifx, the NVIDIA HPC compilers, the classic Cray and IBM compilers you will meet on supercomputers — but the one we use throughout this book is gfortran, the Fortran front end of the GNU Compiler Collection (GCC). It is free, it is open source, it is available on every platform you are likely to use, it implements the modern standard well, and it is almost certainly already installed on the clusters you will eventually run on. Everything in this book targets gfortran 10 or newer, which fully supports the 2018 standard features we rely on. If you have an older gfortran, most examples will still work, but a few 2018-era conveniences will not; upgrade if you can.

Installation differs by operating system. Pick your platform below. If a package name has drifted since this was written — package names do drift — the authoritative, continuously updated instructions live in Appendix C, which also covers the Intel and NVIDIA compilers.

Linux. This is Fortran's native habitat, and installation is a single command. On Debian or Ubuntu and their derivatives:

$ sudo apt update
$ sudo apt install gfortran

On Fedora, Red Hat, or CentOS Stream, the package lives under a slightly different name:

$ sudo dnf install gcc-gfortran

On Arch and its relatives:

$ sudo pacman -S gcc-fortran

A note for cluster users: on a shared HPC system you almost never install anything yourself. Instead you load a compiler with the environment-modules system, typically something like module load gcc or module avail to see what is offered. We flag this now because it surprises newcomers; the mechanics are the same once the compiler is on your PATH.

macOS. Apple ships a C and C++ compiler (Clang, inside the Xcode Command Line Tools) but no Fortran compiler, so you must add one. The simplest route is the Homebrew package manager. gfortran is bundled inside Homebrew's gcc formula — there is no separate gfortran formula — so:

$ brew install gcc

After this, the compiler is on your PATH as gfortran (sometimes as a version-suffixed name such as gfortran-14, in which case use that name, or make an alias). If you prefer MacPorts, sudo port install gcc14 (or the current version) works equally well.

Windows. Windows is the platform with the most choices and the most opportunities for confusion, so we recommend one of two well-trodden paths, both detailed further in Appendix C.

  • MSYS2 gives you a genuine GNU toolchain native to Windows. Install MSYS2, then from its UCRT64 terminal install the Fortran compiler. As of this writing the package is:

console $ pacman -S mingw-w64-ucrt-x86_64-gcc-fortran

  • WSL (the Windows Subsystem for Linux) gives you a real Linux environment inside Windows. Install a distribution such as Ubuntu from the Microsoft Store, open it, and then follow the Linux instructions above (sudo apt install gfortran). For a book whose destination is Linux-based HPC, WSL is an excellent choice: you learn the environment you will ultimately deploy to.

A lightweight portable alternative, if you want a single self-contained folder with no installer, is w64devkit, which bundles gfortran among other tools. Whichever you choose, the goal is identical: a gfortran command your terminal can find.

Verifying the installation

However you installed it, confirm it worked before writing a line of code. Open a terminal and ask the compiler its version:

$ gfortran --version
GNU Fortran (Ubuntu 13.2.0-4ubuntu3) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Your exact banner will differ — a different version number, a different distribution tag, a different year — and that is fine. The one thing that matters is the version number at the end of the first line. Here it is 13.2.0; anything 10.x or newer is comfortably within our baseline. If instead you see

$ gfortran --version
gfortran: command not found

then the compiler is either not installed or not on your PATH, and you should return to the steps above for your platform. This tiny check is worth building into a habit: whenever you sit down at an unfamiliar machine, gfortran --version tells you in one line whether you can work and which compiler you are working with. On a cluster, where several compiler versions may be installed side by side, it is how you confirm which one your module load actually gave you.

🔗 Connection: Recording the exact compiler and version is not mere fussiness — it is the first step of reproducibility, the discipline of making sure a result can be regenerated later. When you publish a computational result, "gfortran 13.2.0 with these flags" belongs in your methods section as surely as the equations do. We make reproducibility a formal practice in Chapter 37.


2.2 Hello, world

Kernighan and Ritchie were right: the first program to write in any language prints hello, world, and it is a more serious exercise than it looks. A program that prints one line still has to be typed without errors, saved to a file, handed to the compiler, linked, and run — so getting it to work proves your entire toolchain end to end. Here it is, in modern Fortran. Type it into a file named hello.f90:

program hello
  implicit none
  print *, "Hello, world!"
end program hello

Four lines, and every one of them earns its place. program hello opens a program unit and gives it a name; the name is yours to choose and should describe what the program does. implicit none is the habit we will justify at length in §2.6 — for now, put it in every program you write and trust that it is protecting you. print *, "Hello, world!" writes text to the screen: print is the statement, the * means "use default list-directed formatting — you figure out how to lay it out," and the quoted text is what gets printed. Finally, end program hello closes the unit; the name after end program must match the name you opened with, and the compiler checks that it does — a small courtesy that catches a surprising number of copy-paste errors.

A few conventions are already visible and worth naming, because they are the house style of all modern Fortran and of this entire book. Keywords are lowercase (program, print, end program), not the shouting uppercase of old FORTRAN. The body of the program is indented two spaces under its program and end program lines — Fortran does not require this (unlike Python, indentation carries no meaning to the compiler), but consistent indentation is how humans read code, so we always do it. And the file extension is .f90, which by universal convention signals free-form modern source to the compiler (more on that in §2.5). You will use .f90 for every file in this book, regardless of which standard year the code targets; the "90" is a historical label for "free-form," not a claim that the code is Fortran 90.

To turn this text into a running program, hand it to gfortran and then run what comes out:

$ gfortran hello.f90 -o hello
$ ./hello
 Hello, world!

The first command compiles and links hello.f90 into an executable named hello (that is what -o hello requests — "output to a file called hello"). The second command, ./hello, runs it. (The ./ tells the shell to run the program in the current directory; on Windows in a plain cmd or PowerShell prompt you would type hello or hello.exe instead.) And there is our line.

Look closely at the output, because there is a genuine subtlety in it. The printed line begins with a single leading space: Hello, world!, not Hello, world! flush against the margin. That space is not a mistake and it is not in your string — it is an artifact of print *, the list-directed form, which is permitted by the standard to insert a leading blank (historically a "carriage control" character). If you want exact control over the output, use a formatted print with an explicit format instead:

program hello_formatted
  implicit none
  print '(a)', "Hello, world!"
end program hello_formatted

Here '(a)' is a format string: the a edit descriptor means "print this as text (a character string)," with no leading blank added. Compile and run it and the line begins flush against the margin:

$ gfortran hello_formatted.f90 -o hello_formatted && ./hello_formatted
Hello, world!

We will treat formats properly in Chapter 7; for now, remember the practical rule that print * is quick and adds a leading space, while print '(a)' gives you exact placement. The && in that command, incidentally, is a shell convenience meaning "run the second command only if the first succeeded" — a tidy way to compile and run in one line, which you will see throughout this book.

🧩 Try It Yourself: Before reading on, actually do this. Type hello.f90, compile it, run it, and confirm you see the greeting with its leading space. Then change the message, recompile, and rerun. That loop — edit, compile, run — is the one you will repeat thousands of times; make it muscle memory now, on a program simple enough that nothing but the toolchain can go wrong.

🐍 Python Comparison: In Python you would write print("Hello, world!") in a file and run it directly with python hello.py — there is no separate compile step, because Python is interpreted: an interpreter reads and executes your source line by line, every time you run it. Fortran is compiled: the translation to machine code happens once, up front, and every run afterward executes that native code at full speed. This is the tradeoff at the heart of why Fortran is fast and Python is convenient. You pay a compile step; you are repaid at every execution — which, for a simulation that runs for hours, is a spectacular bargain.


2.3 The Compile–Link–Run Cycle

That single gfortran hello.f90 -o hello command hid something worth uncovering, because understanding it is the difference between debugging with insight and debugging by superstition. Producing an executable is not one step but two, and the run is a third. The whole sequence is the compile–link–run cycle, and it looks like this:

   source              object               executable
   hello.f90  ──────▶  hello.o   ──────▶    hello       ──────▶   output
              compile            link                    run
  (you write   (machine code,   (machine code with     (the program
   the text)    references not   its libraries linked    actually
                yet resolved)    in — a real program)    executes)

Compiling is the first step: the compiler reads your human-readable source and translates it into machine instructions for your processor, writing the result to an object file.

Definition (object file). The output of compiling a single source file: a file conventionally ending in .o (gfortran produces .o on every platform, Windows included; some Windows-native compilers instead use .obj) containing your code translated into machine instructions, but not yet a runnable program. It is machine code with holes — references to things defined elsewhere, such as the print machinery or a routine in another file, that have not yet been filled in. An object file is a finished puzzle piece, not the finished puzzle.

You can ask gfortran to stop after compiling, producing only the object file, with the -c flag ("compile only, do not link"):

$ gfortran -c hello.f90
$ ls
hello.f90   hello.o

Now there is a hello.o sitting beside your source. It contains the machine code for your program, but you cannot run it — try, and the operating system will refuse, because it is not a complete executable. It is missing, among other things, the actual implementation of print, which lives in the Fortran runtime library. Supplying those missing pieces is the job of the second step.

Linking combines one or more object files with the libraries they depend on and resolves all those dangling references into a single, complete, runnable program.

Definition (linker). The program that takes object files and libraries and stitches them together into an executable, resolving every reference from one piece to another — your call to print gets connected to the runtime library's implementation of it, a call from your main program to a subroutine in another file gets connected to that subroutine, and so on. gfortran runs the linker for you automatically unless you tell it to stop early with -c.

Definition (executable). The final product: a complete, self-sufficient program the operating system can load and run, with every reference resolved and every needed library either linked in or ready to be found at run time. On Linux and macOS it typically has no extension (hello); on Windows it ends in .exe (hello.exe).

Finish the two-step build by hand to see the seam between the stages:

$ gfortran hello.o -o hello
$ ./hello
 Hello, world!

The first command here does only the linking: it takes the object file hello.o, links in the Fortran runtime, and writes the executable hello. This is exactly what the all-in-one gfortran hello.f90 -o hello did — it simply performed both the compile and the link in one invocation, cleaning up the intermediate object file afterward. For a one-file program, the one-shot form is what you will use every day. But the two-step structure is not academic: the whole reason to separate compiling from linking is that a real program is many files, and when you change one of them you want to recompile only that one and then relink, rather than recompiling everything. That incremental discipline is what makes it possible to work on a program of hundreds of files without waiting forever for every build, and it is the reason Fortran programs are organized into modules — the subject of Chapter 8, where this two-step picture grows into the real story of how large Fortran programs are compiled.

🚪 Threshold Concept. Once you see compilation as a pipeline — source becomes object, objects plus libraries become an executable — a great many confusing situations resolve themselves. An error that says "syntax error" or "no IMPLICIT type" is a compiler error: your source did not translate, and no object file was produced. An error that says "undefined reference to …" is a linker error: your source compiled fine, but the linker could not find the definition of something you used — a misspelled routine, a library you forgot to link, a file you forgot to include in the build. These two failures live at two different stages and have two different fixes, and a programmer who can tell them apart at a glance debugs in minutes what a programmer who cannot debugs in hours.

💡 Intuition: Think of compiling as translating each chapter of a book into another language independently, and linking as binding the translated chapters together and adding the index and cross-references so every "see page 40" points somewhere real. You can retranslate one chapter without retranslating the rest — but nothing is a finished book until the binding step resolves all the references.


2.4 The Compiler Flags That Matter Early

We have already been passing an option to gfortran — -o hello, which names the output. Options like this are called compiler flags, and choosing the right handful from the start is one of the highest-leverage habits in the whole book.

Definition (compiler flag). An option passed to the compiler on the command line, conventionally beginning with a hyphen, that modifies how it compiles: what output to produce, which warnings to report, which checks to insert, how hard to optimize, and which version of the language standard to enforce. Flags are how you tell the compiler how to do its job, as opposed to the source, which tells it what to compile.

Five flags earn their place from your very first serious build. Learn what each does and you will understand the two compile commands this book uses everywhere.

-std=f2018 — enforce the standard. By default, gfortran accepts its own dialect, GNU Fortran, which includes non-standard extensions. Passing -std=f2018 tells it to hold you to the 2018 ISO standard and to warn when you stray outside it. This matters because code that relies on one compiler's extensions is code that will not compile on the next compiler — and in scientific computing your code outlives the machine it was born on. Writing to the standard is writing for the future.

-Wall — turn on the warnings. Despite the name, -Wall does not enable literally every warning gfortran can emit (there is -Wextra for more, and -Wpedantic for standard-conformance nags), but it enables a broad, high-value set: unused variables, suspicious comparisons, uninitialized values the compiler can spot, and dozens more. Warnings are the compiler doing free code review. A warning is the compiler saying "this compiles, but it looks like a mistake" — and it is right often enough that you should treat warnings as errors-in-waiting and eliminate every one.

-O2 — optimize. The -O flags control how hard the compiler works to make your code fast. -O0 (the default) does essentially no optimization, compiling quickly and translating your code literally, which is ideal while debugging. -O2 turns on a large, well-tested suite of optimizations and is the sensible default for code you actually want to run fast. There is more beyond it — -O3, -march=native, -flto and their tradeoffs — but that is a Part VII topic, taken up in Chapter 30. For now, -O2 is your release setting.

-g — keep the debugging information. This flag tells the compiler to embed a map, in the executable, from machine instructions back to your source lines and variable names. It costs nothing at run time and it is what lets a debugger show you your code — line numbers, variable names — instead of raw addresses when something goes wrong. Always compile with -g while developing.

-fcheck=all — check at run time. This inserts run-time checks that catch a family of classic mistakes the moment they happen rather than letting them corrupt your results silently: most valuably, accessing an array outside its bounds. These checks cost some speed, which is exactly why they are a development flag, switched on while you build and test and switched off for production runs where you need every cycle. The payoff is enormous: an out-of-bounds access that would otherwise read whatever garbage happened to be in neighboring memory — a bug that can hide for months — instead stops your program immediately with a message naming the file and line.

Put these together and you get the two build profiles you will use for the rest of the book. While developing, favor safety and diagnostics:

$ gfortran -std=f2018 -Wall -g -fcheck=all hello.f90 -o hello

When you want the program to run fast, favor optimization and drop the run-time checks:

$ gfortran -std=f2018 -Wall -O2 hello.f90 -o hello

⚡ Performance Note: The gap between these two profiles is real and sometimes dramatic. A numerical loop compiled with -fcheck=all may run several times slower than the same loop at -O2, because every array access now carries a bounds check. That is a fine price while you are hunting bugs and an unacceptable one during a week-long production run. The discipline — develop with checks on, run with optimization on — is one you adopt now and keep for your whole career. Never publish a timing measured with -fcheck=all; never run an unvalidated code without it.

⚠️ Common Pitfall: Do not confuse "it compiled" with "it is correct." -Wall and -fcheck=all exist precisely because the compiler will happily accept, and run, code that is standard-conforming but wrong — a loop that reads one element past the end of an array, a variable used before it is set. The compiler checks grammar, not intent. These flags recruit it to check a little of your intent too, for free. Leaving them off is not saving effort; it is declining help.

The full debugging toolkit — -fbacktrace to print a stack trace on a crash, -ffpe-trap to stop on invalid floating-point operations, and the gdb and valgrind tools — is the subject of Chapter 13. The five flags above are enough to work well from today.


2.5 Free-Form and Fixed-Form Source

We have called our files .f90 and said the extension signals "free-form" source. It is time to say what that means, because it is the most visible difference between the modern language and the one you will inherit from the past.

Definition (free-form source). The source layout used by all modern Fortran (Fortran 90 onward), in which statements may begin in any column, extend up to 132 characters per line, be indented freely for readability, and carry comments introduced by an exclamation mark ! anywhere on a line. A statement that runs long is continued by ending the line with an ampersand &. Free-form is what you get when the source file has a .f90 (or .f95, .f03, .f08) extension, and it is what we write in every example in this book.

Everything you have typed so far is free-form, and it has felt unremarkable precisely because free-form gets out of your way: you indent where it aids reading, you comment where it aids understanding, and the columns your text lands in mean nothing to the compiler. That freedom is the whole point of the name.

It was not always so. Fortran was born in the era of the punched card, and for its first three decades the language used a rigid, column-oriented layout now called fixed-form source, in which the position of a character on the line was part of its meaning. In brief — we are only previewing it here, because the full, patient treatment belongs to Chapter 17, where you learn to read old code — fixed-form reserved the first five columns for statement labels, used column six to mark a continuation line, expected statements to live in columns 7 through 72, and treated a C (or *) in column one as beginning a comment. Columns 73 onward were ignored entirely, a relic of the days when they held card sequence numbers so a dropped deck could be re-sorted. A fragment of fixed-form looks like this (the leading spaces are load-bearing):

C     THIS IS FIXED-FORM FORTRAN 77 — COLUMNS MATTER
      PROGRAM OLD
      PRINT *, 'HELLO FROM 1977'
      END

and the same idea in the free-form we actually write is simply:

! This is free-form modern Fortran — indent as you like
program modern
  implicit none
  print *, "Hello from today"
end program modern

The differences that matter to you today are two. First, the file extension chooses the form: .f90 and its relatives mean free-form, while .f and .for mean fixed-form, and gfortran decides how to read your file based on that extension (you can override it with a flag, but do not). Second — and this is the practical trap — you cannot freely paste fixed-form code into a .f90 file and expect it to compile, because the continuation and comment conventions differ. When you meet old code, you will convert it deliberately, a skill we build in Part IV.

Why did the language abandon a layout it used for thirty years? Because the column rules were a never-ending source of silent, maddening errors, and because they existed only to serve a physical medium — the punched card — that no longer exists. Free-form is not merely more pleasant; it removes an entire category of bug.

📜 From History: Here is the classic illustration of how dangerous the fixed-form world could be. In fixed-form Fortran, spaces inside a statement are insignificant, so the compiler happily reads DO 5 I = 1.100 — note the period where a comma belongs — not as the intended loop DO 5 I = 1,100 but as an assignment, DO5I = 1.100, quietly creating a new variable named DO5I and giving it the value 1.1. One mistyped character silently turns a loop into an assignment, with no error and no warning, because implicit typing (§2.6) invents the variable on the spot. This example is real and often retold; the popular story that a typo of exactly this kind doomed an early NASA space probe is, to be clear, apocryphal — but the hazard it dramatizes was genuine, and free-form plus implicit none between them abolish it completely. Two of modern Fortran's plainest features are, at heart, answers to bugs like this one.

🔄 Check Your Understanding: 1. What does the .f90 file extension tell the compiler about your source? 2. In free-form source, how do you continue a statement that is too long for one line, and how do you start a comment? 3. Why can't you paste a block of FORTRAN 77 fixed-form code into a .f90 file unchanged?

Answers (1) That it is free-form: statements may start in any column, comments begin with !, and the column rules of fixed-form do not apply. (2) End the line with an ampersand & to continue it on the next line; begin a comment with an exclamation mark ! anywhere on a line. (3) Because fixed-form and free-form use different, incompatible conventions for columns, comments (C in column one versus !), and continuation (column six versus a trailing &); the compiler reads a .f90 file as free-form and will reject fixed-form layout. It must be converted, not pasted.


2.6 implicit none — The Non-Negotiable Habit

We now arrive at the most important two words in this book. You have written implicit none in every program so far on faith. Here is why it is not optional, told through the bug it prevents.

By an old default inherited from the language's earliest days, a Fortran variable that you never declare is not an error. Instead the compiler invents it, silently, giving it a type determined by the first letter of its name: variables whose names begin with the letters i through n are made integer, and everything else is made real. (The mnemonic is that i, j, k, l, m, n are the traditional loop counters and mathematicians' integer indices.) This behavior — the language calls it implicit typing, and we meet it properly as a legacy convention in Chapter 17 — was a convenience in 1957, when saving keystrokes on a punched card mattered. Today it is a menace, for one specific and deadly reason: a typo in a variable name does not cause an error. It silently creates a new variable.

Consider this program, written without the protective habit, and read it as the compiler would:

program buggy_velocity
  ! NOTE: no `implicit none` — this is what NOT to do
  real :: velocity
  velocity = 42.0
  print *, veloctiy      ! <-- typo: veloctiy, not velocity
end program buggy_velocity

You declared velocity and set it to 42.0. Then, in the print, your finger slipped and you typed veloctiy. What happens? Without implicit none, the compiler does not object. It sees an undeclared name beginning with v, invents a brand-new real variable called veloctiy, and — because you never assigned to it — prints whatever value happened to be lurking in that memory, very often 0. Your program runs, produces a plausible-looking number, and is wrong. There is no error, no warning, nothing to alert you. In a four-line program you might spot it; in a four-thousand-line simulation whose answer is merely a little off, this class of bug has cost the scientific community untold hours and, one has to assume, a retracted result or two.

Now add the two words that make the whole problem vanish:

program fixed_velocity
  implicit none            ! <-- the fix: no variable may go undeclared
  real :: velocity
  velocity = 42.0
  print *, veloctiy        ! typo again — but now it cannot slip through
end program fixed_velocity

Definition (implicit none). A statement, placed at the top of every program, module, and procedure (right after the program/module/subroutine/function line), that switches off implicit typing entirely and requires every variable to be explicitly declared. With implicit none in force, using an undeclared name is a compile-time error, not a silently invented variable. It is the single most important line of defensive Fortran, and it belongs in every program unit you ever write.

Compile the fixed version and the compiler stops you cold, at compile time, before the program ever runs:

$ gfortran -std=f2018 -Wall fixed_velocity.f90 -o fixed_velocity
fixed_velocity.f90:5:15:

    5 |   print *, veloctiy        ! typo again — but now it cannot slip through
      |               1
Error: Symbol 'veloctiy' at (1) has no IMPLICIT type

The exact wording and layout of the message vary between compiler versions, but the substance is always this: this name has no type, because you never declared it and I am not allowed to invent one. The typo that would have silently poisoned your results is now a loud, specific, immediate error pointing at the exact line and column. This is the best kind of error — the kind that happens at compile time, on your machine, in front of your eyes, instead of at run time, three weeks later, in a result someone has already trusted.

🐛 Find the Bug: What does the following print, and why is it a disaster waiting to happen? (Assume no implicit none.)

fortran program area pi = 3.14159 radius = 2.0 area_value = pi * radus * radus print *, area_value end program area

AnswerTwo things go wrong, both silent. First, radus is a typo for radius; without implicit none it becomes a new, uninitialized real variable (likely 0.0), so area_value is computed as pi * 0 * 0 = 0 and the program prints 0 — a wrong answer with no complaint. Second, note the program is named area and also uses a variable area_value; had you tried to use area as a variable you would collide with the program name. Add implicit none and declare everything (real :: pi, radius, area_value), and the compiler flags radus immediately. This is exactly the bug §2.6 exists to prevent.

There is a reason the style bible of this book, and the coding standard of essentially every serious Fortran project on Earth, makes implicit none mandatory. It is not a matter of taste. It converts an entire category of silent, expensive, run-time correctness bugs into loud, cheap, compile-time errors. From this point forward, every program unit in this book begins with it, and so should every one of yours. If you internalize nothing else from this chapter, internalize this: implicit none, always, no exceptions.

🔄 Check Your Understanding: 1. Without implicit none, what type does Fortran give to an undeclared variable named n_steps? To one named temperature? 2. In one sentence, what disaster does implicit none prevent? 3. Where in a program unit must the implicit none statement go?

Answers (1) n_steps begins with n, so implicit typing would make it an integer; temperature begins with t, so it would be made a real. (2) It prevents a mistyped variable name from silently becoming a new, uninitialized variable — turning a silent wrong answer into a compile-time error. (3) Immediately after the opening line of the unit (the program, module, subroutine, or function statement) and before any variable declarations.


2.7 Your First Real Computation

Hello, world proves your toolchain works. Now let us compute something — a small step toward the heat solver, and your first program that produces a number rather than a message.

Recall the project from Chapter 1: a square metal plate, held hot on one edge and cold on the other three, whose interior temperature we want to simulate. Long before we can simulate anything, we can ask a much simpler question by hand: if one edge sits at 100 degrees and three sit at 0, what is a rough estimate of the temperature somewhere in the middle? A reasonable first guess — crude, but honest, and something we can compute in four lines — is simply the average of the four edge temperatures. Here is the program:

program plate_estimate
  use, intrinsic :: iso_fortran_env, only: dp => real64
  implicit none

  real(dp), parameter :: hot  = 100.0_dp    ! the one hot edge, in degrees C
  real(dp), parameter :: cold =   0.0_dp    ! the three cold edges, in degrees C
  real(dp) :: edge_average

  edge_average = (hot + cold + cold + cold) / 4.0_dp

  print '(a, f0.2, a)', 'hot edge temperature  : ', hot,          ' C'
  print '(a, f0.2, a)', 'cold edge temperature : ', cold,         ' C'
  print '(a, f0.2, a)', 'average of four edges : ', edge_average, ' C'
end program plate_estimate

Two lines here are new and deserve explanation. The first is use, intrinsic :: iso_fortran_env, only: dp => real64. This borrows a name from a standard module built into every Fortran compiler and, in doing so, gives us a real type with guaranteed precision. We declare our numbers as real(dp) — double precision — and write their literal values with the _dp suffix, as in 100.0_dp. Why go to this trouble instead of a plain real? Because in numerical computing the precision of your reals is a decision you make on purpose, not a default you accept by accident, and controlling it portably is so important that we devote much of Chapter 3 to it. For now, take real(dp) and the _dp suffix as the house style for every real number in this book, and know that Chapter 3 explains exactly what they mean.

The second new element is parameter, which marks hot and cold as named constants — values fixed at compile time that the program is not allowed to change. It is good practice to give the fixed numbers in a computation clear names, and parameter lets the compiler enforce that they stay fixed. Chapter 3 develops this too.

Now compile and run it with the development profile:

$ gfortran -std=f2018 -Wall -g -fcheck=all plate_estimate.f90 -o plate_estimate
$ ./plate_estimate
hot edge temperature  : 100.00 C
cold edge temperature : 0.00 C
average of four edges : 25.00 C

Let us confirm that output by hand, because in this book we always know the answer before the machine tells us. The arithmetic is $(100 + 0 + 0 + 0) / 4 = 100/4 = 25$, so edge_average is 25.00. The f0.2 edit descriptor prints each real with two digits after the decimal point and the minimum width needed (so hot prints as 100.00, cold as 0.00, and the average as 25.00), and the a descriptors print the surrounding text. Notice, too, that these particular numbers — 100, 0, 25 — are represented exactly in the computer's binary floating-point format, so what prints is exact; the day that stops being true, and 0.1 + 0.2 fails to equal 0.3, is the subject of Chapter 20. We flag it now only so the surprise is not a shock later.

Is 25 degrees the true temperature in the middle of the plate? Not exactly — the real steady-state field is the solution of a partial differential equation, and computing it faithfully is the destination of this whole book, reached in Chapter 24. But the estimate is not nonsense, either: for this symmetric arrangement the average of the boundary values is a genuine first approximation to the interior, and you have just written a program that computes it. That is a real computation, produced by a real Fortran program you compiled and ran yourself. The distance from here to the capstone is long, but it is all made of steps exactly this size.

🐍 Python Comparison: The equivalent Python — edge_average = (hot + 3*cold) / 4 — is shorter, because Python needs no type declarations and no compile step. For a four-line arithmetic script, Python wins on convenience, and no honest Fortran advocate pretends otherwise. The Fortran investment — declaring types, choosing precision, compiling — begins to pay only when the computation grows large and repetitive: when instead of averaging four numbers you are sweeping a stencil across a million grid cells, a billion times over, and the difference between compiled real(dp) arithmetic and interpreted Python is the difference between an afternoon and a month. We are writing four lines today so that the ten-thousand-line program those four lines grow into will run at the speed physics demands.


Project Checkpoint

Time to start the program itself. In Chapter 1 you chose your domain (the default is the 2D heat equation) and wrote a plain-language problem statement into a folder called heat-solver/. That folder is your project's home for the next thirty-six chapters. Today you put the first real file in it: the skeleton of the program that will, by the capstone, be a parallel scientific simulation.

Create a file heat.f90 inside heat-solver/. It does not solve anything yet — it announces itself and compiles cleanly, which is exactly the right first milestone. A program that compiles is a foundation you can build on; everything else is added a chapter at a time.

program heat
  implicit none

  print '(a)', '=================================================='
  print '(a)', '  heat-solver : a 2D heat-equation simulation'
  print '(a)', '  Chapter 2 build, the skeleton compiles.'
  print '(a)', '=================================================='
  print '(a)', 'Nothing to solve yet. Come back next chapter.'
end program heat

Compile it with the development profile and run it:

$ gfortran -std=f2018 -Wall -g -fcheck=all heat.f90 -o heat
$ ./heat
==================================================
  heat-solver : a 2D heat-equation simulation
  Chapter 2 build, the skeleton compiles.
==================================================
Nothing to solve yet. Come back next chapter.

We can read that output off by hand with complete confidence: each print '(a)' writes its string verbatim, with no leading blank (that is why the banner lines are flush against the margin, unlike the leading-space output of print *), one line per statement, in order. There is no arithmetic to get wrong — which is the point. This checkpoint is not about computation; it is about establishing that the program exists, that it is named heat, and that your toolchain turns it into a running executable. Every future checkpoint adds to this file or to new files that link with it.

Here is how today's four lines feed the capstone. That program heat line opens the driver — the top-level program that, by Chapter 38, will set up the plate, run the time-stepping loop, and write out results for visualization. Right now the driver only prints a banner. In Chapter 3 it gains a kinds module and the plate's physical constants; in Chapter 4, a time-stepping loop; in Chapter 5, the temperature field as a real 2D array. The banner you print today is the first line of a program you will still be extending thirty-six chapters from now — so choose your wording; you will be looking at it for a while.

Your checkpoint for Chapter 2: create heat-solver/heat.f90 with the skeleton above (or your own banner, if you are building a fluid or N-body solver instead), compile it cleanly with the development flags, run it, and confirm the banner prints. Keep the exact compile command in a note in the folder — you will reuse it constantly, and recording it is your first act of a reproducible build.


Summary

This chapter got a compiler onto your machine and walked your first programs through the pipeline that turns source into results.

Idea The short version
Install gfortran from your package manager: apt install gfortran (Debian/Ubuntu), dnf install gcc-gfortran (Fedora), brew install gcc (macOS), MSYS2 or WSL (Windows). Verify with gfortran --version (want 10+).
Hello, world program name / implicit none / print / end program name. print * is quick but adds a leading blank; print '(a)' prints flush. Files are .f90.
Compile–link–run Source → object file (-c stops here) → executable (the linker resolves references and adds libraries) → run with ./name.
Compiler errors vs linker errors "no IMPLICIT type", "syntax error" = compile stage. "undefined reference to …" = link stage. Different stage, different fix.
The five early flags -std=f2018 (enforce the standard), -Wall (warnings = free review), -O2 (optimize for release), -g (debug info), -fcheck=all (run-time checks for development).
Two build profiles Develop: -std=f2018 -Wall -g -fcheck=all. Release: -std=f2018 -Wall -O2. Never time a run made with -fcheck=all.
Source form Free-form (.f90: any column, ! comments, & continuation) is what we write. Fixed-form (.f/.for: column rules) is legacy — Chapter 17.
implicit none Mandatory, at the top of every program unit. Turns a mistyped variable name from a silent wrong answer into a compile-time error.

The two things to memorize from this chapter. First, the development compile command — gfortran -std=f2018 -Wall -g -fcheck=all file.f90 -o file — because you will type it a thousand times. Second, and above all: implicit none, in every program unit, always. These two habits, formed now on trivially small programs, are the ones that will keep your ten-thousand-line simulation honest.

Spaced Review

This is only the second chapter, so there is nothing yet to circle back on — the spaced review of earlier material begins in Chapter 3, which will send you back here. Instead, in the spirit of Chapter 1, here is a forward teaser: a short list of things you will be able to do by the end of Part I, only two chapters of which you have now seen. Note where each one lives; you are closer to all of them than you think.

  1. Control the precision of every real number in a program portably, and explain why 1.0/3.0 and 1.0_dp/3.0_dp are not the same computation.

    Where you'll learn itKinds and the `dp` parameter, in [Chapter 3](../chapter-03-variables-types-arithmetic/index.md).

  2. Write a loop that marches a simulation forward in time, step by step.

    Where you'll learn itThe `do` loop, in [Chapter 4](../chapter-04-control-flow/index.md).

  3. Store the whole temperature field of the plate in a single array and update it with one statement, no loop written by you.

    Where you'll learn itArrays — Fortran's superpower — in [Chapter 5](../chapter-05-arrays/index.md).

  4. Package the update step as a reusable procedure whose arguments the compiler checks for you.

    Where you'll learn itSubroutines, functions, and `intent`, in [Chapter 6](../chapter-06-procedures/index.md).

What's Next

You have a compiler, a mental model of the pipeline that feeds it, the flags that make it work for you, and the one habit — implicit none — that will save you more grief than any other. What you do not yet have is anything to say: your programs so far print fixed text and average four constants. Chapter 3 fixes that. It introduces Fortran's numeric types, the kind system that gives you portable, controlled precision (and finally explains that real(dp) and _dp you have been taking on faith), and the arithmetic rules — including the notorious integer-division trap that catches every newcomer exactly once. It also adds the first real piece of the solver: the kinds module and the plate's physical constants. Let's learn how Fortran thinks about numbers.