Two Pascal programs read the same two scores, 7 and 8, and compute the average. One prints mean: 7. The other prints mean: 7.50. Both compile without a complaint, both run without a crash, and neither one is lying about what you wrote. Each program carries its own copy of the average function, and the copies do not match. Fixing one copy leaves the other broken, and nothing warns you that they have drifted apart. A unit fixes this by giving the function exactly one body.
Key takeaways
divdrops the remainder, so(7 + 8) div 2is 7 while(7 + 8) / 2is 7.5.- A unit has two halves:
interfaceis what other programs may call,implementationis the body only the unit itself sees. - A function that lives only in
implementationcompiles inside the unit and fails at the caller withError: Identifier not found. - Free Pascal compiles a unit it finds next to the program and leaves a
.ppubehind; from a subfolder the flag is-Fulib, attached, because-Fu libwith a space does not find it. - The unit name is looked up as a file name, so
unit stats;must live instats.pas.
The remainder that disappeared
Start with the arithmetic, because the bug lives there. Pascal has two division operators, and they answer different questions.
program divide;
var
a, b: integer;
begin
a := 7;
b := 8;
writeln('(a + b) div 2 = ', (a + b) div 2);
writeln('(a + b) / 2 = ', (a + b) / 2:0:1);
writeln('unformatted = ', (a + b) / 2);
end.
$ fpc divide.pas && ./divide
(a + b) div 2 = 7
(a + b) / 2 = 7.5
unformatted = 7.5000000000000000E+000
div is integer division, so it throws the remainder away and (7 + 8) div 2 is 7. / always produces a real number, so the same sum gives 7.5. The last line shows how Pascal prints a raw real: scientific notation, with a leading space. Remember that for later.
Here is the program that printed the short answer. Mean takes integers and uses div, so the 7.5 never exists.
program average;
var
a, b: integer;
function Mean(x, y: integer): integer;
begin
Mean := (x + y) div 2;
end;
begin
a := 7;
b := 8;
writeln('scores: ', a, ' and ', b);
writeln('mean: ', Mean(a, b));
end.
$ fpc average.pas && ./average
scores: 7 and 8
mean: 7
This is the first copy. Any program that pastes in this function inherits the truncation, and any program that pastes in a / version gets the other answer.
A unit has two halves
A unit is a file split by two keywords. Everything under interface is a contract: the names, parameters and return types that other programs may call. Everything under implementation is the body, the code that does the work, visible only inside the unit. Here is stats.pas, which takes over the function.
unit stats;
interface
function Mean(x, y: real): real;
function Format2(n: real): string;
implementation
function Mean(x, y: real): real;
begin
Mean := (x + y) / 2;
end;
function Format2(n: real): string;
begin
Str(n:0:2, Format2);
end;
end.
Mean now takes and returns real and uses /. Each signature appears twice, once per half: the interface line promises, the implementation line delivers. Format2 wraps Str(n:0:2, ...), which writes a real with two decimals into a string, so callers get 7.50 instead of scientific notation. A program opts in with one line, uses stats;, placed right after the program line.
The compiler builds it for you
program average;
uses stats;
var
a, b: integer;
begin
a := 7;
b := 8;
writeln('scores: ', a, ' and ', b);
writeln('mean: ', Format2(Mean(a, b)));
end.
$ fpc average.pas && ./average
scores: 7 and 8
mean: 7.50
There was no separate command for the unit. When the compiler reaches uses stats;, it looks for stats.pas next to the program and compiles it on the way. Afterward, stats.o and stats.ppu sit beside the program. The .ppu file is the compiled form of the unit that Free Pascal leaves behind.
Without a flag, the compiler finds the unit only when it sits next to the program. Move stats.pas into a folder named lib, and the build stops:
$ fpc main.pas
main.pas(3,6) Fatal: Can't find unit stats used by main
Fatal: Compilation aborted
Adding the flag with a space changes nothing: fpc -Fu lib main.pas prints the same two lines. The flag must be attached to its path, so fpc -Fulib main.pas compiles, and the program prints scores: 7 and 8 and mean: 7.50.
The name matters too. The compiler turns uses stats; into a file name. Save unit stats; as stat2.pas, and a program that uses it cannot find it:
$ fpc prog2.pas
prog2.pas(3,6) Fatal: Can't find unit stats used by prog2
Fatal: Compilation aborted
So unit stats; must live in stats.pas. The fix is the file name, not the code.
What the interface gates
The two halves are not decoration. Suppose a unit hidden.pas declares nothing in interface and implements Mean under implementation. The unit compiles. A program uses_hidden.pas that says uses hidden; and calls Mean does not:
$ fpc uses_hidden.pas
uses_hidden.pas(6,21) Error: Identifier not found "Mean"
uses_hidden.pas(8) Fatal: There were 1 errors compiling module, stopping
Fatal: Compilation aborted
Mean exists in the file, and the caller still cannot see it, because implementation is private to the unit. To share a function, put its header in interface. To keep a helper to yourself, leave it out.
Two files, one command
Eyeballing output gets old, so let a second program do the comparing. check_average.pas reads lines from standard input and compares the first two against the expected text.
program check_average;
var
expected: array[1..2] of string;
line: string;
i, passed, failed: integer;
begin
expected[1] := 'scores: 7 and 8';
expected[2] := 'mean: 7.50';
passed := 0;
failed := 0;
i := 0;
while not eof do
begin
readln(line);
i := i + 1;
if i <= 2 then
begin
if line = expected[i] then
begin
writeln('PASS line ', i, ': ', expected[i]);
passed := passed + 1;
end
else
begin
writeln('FAIL line ', i, ': expected "', expected[i], '", got "', line, '"');
failed := failed + 1;
end;
end;
end;
if i < 2 then
begin
writeln('FAIL the program printed ', i, ' lines, expected 2');
failed := failed + 1;
end;
writeln;
writeln(passed, ' of 2 checks passed');
if failed > 0 then
ExitCode := 1;
end.
Build it once with fpc check_average.pas. After that the whole check is one command, ./average | ./check_average, where the pipe feeds what average prints into the checker. Build the starter average.pas from the first section and run it:
$ ./average | ./check_average
PASS line 1: scores: 7 and 8
FAIL line 2: expected "mean: 7.50", got "mean: 7"
1 of 2 checks passed
The checker names the line and shows both strings. Now switch average.pas to the version that uses stats, rebuild, and run the same command:
$ ./average | ./check_average
PASS line 1: scores: 7 and 8
PASS line 2: mean: 7.50
2 of 2 checks passed
Same checker, same command, new answer.
Exercise. In the unit version of average.pas, change the last writeln to writeln('mean: ', Mean(a, b)); and drop Format2. Predict what the line prints, then run the checker. The line becomes mean: 7.5000000000000000E+000, with two spaces after the colon, and it fails line 2. That is why Format2 exists.
When two programs need the same calculation, write it once in a unit and let both of them uses it, because a pasted copy is a bug waiting for the first edit.