Hello World & Structure
Hello, World
program Hello;
begin
writeln('Hello, World!');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
BEGIN
WriteString("Hello, World!"); WriteLn;
END Example. Modula-2 replaces
program with MODULE. There is no built-in writeln — text I/O is imported from STextIO. The module body closes with END ModuleName. (period, not semicolon).Compiling
{ fpc hello.pas }
{ ./hello } (* gm2 -fiso hello.mod -o hello *)
(* ./hello *) GNU Modula-2 (
gm2) is the most widely available modern compiler. The -fiso flag selects ISO dialect, which this cheatsheet targets. Comments in Modula-2 use (* ... *) instead of Pascal's { ... }.Printing integers
program Demo;
begin
writeln(42);
writeln(-7);
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt;
BEGIN
WriteInt(42, 0); WriteLn;
WriteInt(-7, 0); WriteLn;
END Example. Modula-2 splits I/O across modules by type.
SWholeIO handles whole-number I/O; the second argument to WriteInt is the minimum field width — 0 means no padding.Printing real numbers
program Demo;
begin
writeln(3.14:8:2);
writeln(2.718:1:4);
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SRealIO IMPORT WriteReal;
BEGIN
WriteReal(3.14, 8); WriteLn;
WriteReal(2.718, 8); WriteLn;
END Example. SRealIO.WriteReal takes the value and a total field width. Unlike Pascal's :fore:aft format notation, Modula-2 has no built-in decimal-place control — you get whatever the default float representation produces.Variables & Types
Variable declarations
program Demo;
var
count : Integer;
ratio : Real;
letter: Char;
flag : Boolean;
begin
count := 10;
ratio := 3.14;
letter := 'A';
flag := True;
writeln(count, ' ', ratio:4:2);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn, WriteChar;
FROM SWholeIO IMPORT WriteInt;
FROM SRealIO IMPORT WriteReal;
VAR
count : INTEGER;
ratio : REAL;
letter: CHAR;
flag : BOOLEAN;
BEGIN
count := 10;
ratio := 3.14;
letter := 'A';
flag := TRUE;
WriteInt(count, 0); WriteLn;
WriteReal(ratio, 6); WriteLn;
WriteChar(letter); WriteLn;
END Example. Modula-2 uses all-uppercase reserved words:
VAR, INTEGER, REAL, CHAR, BOOLEAN. The rest of the variable-declaration syntax is identical to Pascal.Built-in types
program Demo;
var
n : Integer; { signed whole number }
r : Real; { floating point }
flag: Boolean; { True / False }
ch : Char; { single character }
{ no unsigned integer type in standard Pascal }
begin
n := -42;
writeln(n);
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt, WriteCard;
VAR
signed : INTEGER; (* signed whole number *)
unsigned: CARDINAL; (* unsigned: 0..MAX(CARDINAL) *)
ratio : REAL;
flag : BOOLEAN;
letter : CHAR;
BEGIN
signed := -42;
unsigned := 100;
WriteInt(signed, 0); WriteLn;
WriteCard(unsigned, 0); WriteLn;
END Example. CARDINAL is an unsigned whole-number type with no Pascal equivalent. Modula-2 strictly distinguishes signed (INTEGER) from unsigned (CARDINAL); mixing them requires an explicit conversion.Constants
program Demo;
const
MaxSize = 100;
Pi = 3.14159;
Greeting = 'Hello';
begin
writeln(MaxSize);
writeln(Greeting);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
CONST
MaxSize = 100;
Pi = 3.14159;
Greeting = "Hello";
BEGIN
WriteInt(MaxSize, 0); WriteLn;
WriteString(Greeting); WriteLn;
END Example. The
CONST block syntax is identical to Pascal's except for the uppercase keyword. String literals use double quotes in Modula-2; single quotes are for CHAR literals.Type declarations
program Demo;
type
Color = (Red, Green, Blue);
Score = 0..100;
IntArr = array[1..10] of Integer;
var
hue : Color;
grade: Score;
begin
hue := Green;
grade := 87;
writeln(Ord(hue));
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt;
TYPE
Color = (Red, Green, Blue);
Score = [0..100];
IntArr = ARRAY [1..10] OF INTEGER;
VAR
hue : Color;
grade: Score;
BEGIN
hue := Green;
grade := 87;
WriteInt(ORD(hue), 0); WriteLn;
WriteInt(grade, 0); WriteLn;
END Example. Modula-2 subrange types use square brackets:
[0..100] instead of Pascal's bare 0..100. Enumeration types use the same parenthesized syntax. ORD is uppercase.Enumeration types
program Demo;
type
Day = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
var
today: Day;
begin
today := Wed;
if today = Wed then
writeln('Wednesday');
writeln(Ord(today));
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
TYPE
Day = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
VAR
today: Day;
BEGIN
today := Wed;
IF today = Wed THEN
WriteString("Wednesday"); WriteLn;
END;
WriteInt(ORD(today), 0); WriteLn;
END Example. Enumeration types are syntactically identical in both languages.
ORD returns the zero-based ordinal position of an enum value — ORD(Mon) = 0, ORD(Wed) = 2.Strings
String representation
program Demo;
var
name : AnsiString; { dynamic length }
shortstr: String[20]; { max 20 chars }
begin
name := 'Alice';
writeln(name, ' ', Length(name));
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
FROM Strings IMPORT Length;
VAR
name: ARRAY [0..29] OF CHAR;
BEGIN
name := "Alice";
WriteString(name); WriteLn;
WriteInt(Length(name), 0); WriteLn;
END Example. Modula-2 has no built-in string type. All strings are
ARRAY [0..N] OF CHAR, null-terminated. The Strings module provides operations. Direct assignment (name := "Alice") fills the array from the left and null-terminates it.String length
program Demo;
var name: AnsiString;
begin
name := 'Ada Lovelace';
writeln(Length(name));
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt;
FROM Strings IMPORT Length;
VAR
name: ARRAY [0..29] OF CHAR;
BEGIN
name := "Ada Lovelace";
WriteInt(Length(name), 0); WriteLn;
END Example. The
Strings module's Length function counts characters up to the first null byte, just like Pascal's Length for AnsiString.String concatenation
program Demo;
var
first, last, full: AnsiString;
begin
first := 'Ada';
last := 'Lovelace';
full := first + ' ' + last;
writeln(full);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM Strings IMPORT ConCat;
VAR
first : ARRAY [0..19] OF CHAR;
last : ARRAY [0..19] OF CHAR;
full : ARRAY [0..49] OF CHAR;
BEGIN
first := "Ada";
last := "Lovelace";
ConCat("Ada ", last, full);
WriteString(full); WriteLn;
END Example. Strings.ConCat (note the capital C) takes two source strings and writes their concatenation into a destination array. There is no + operator for strings. Each destination array must be large enough to hold the result.String comparison
program Demo;
var name: AnsiString;
begin
name := 'Alice';
if name = 'Alice' then
writeln('Equal');
if name > 'Adam' then
writeln('After Adam');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM Strings IMPORT CompareStr;
VAR
name: ARRAY [0..19] OF CHAR;
BEGIN
name := "Alice";
IF CompareStr(name, "Alice") = 0 THEN
WriteString("Equal"); WriteLn;
END;
IF CompareStr(name, "Adam") > 0 THEN
WriteString("After Adam"); WriteLn;
END;
END Example. Strings cannot be compared with
= or > because they are arrays, not scalar values. Strings.CompareStr returns 0 for equal, a negative number if the first is less, and a positive number if greater.Control Flow & Loops
If / elsif
program Demo;
var score: Integer;
begin
score := 75;
if score >= 90 then
writeln('A')
else if score >= 80 then
writeln('B')
else if score >= 70 then
writeln('C')
else
writeln('F');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
VAR score: INTEGER;
BEGIN
score := 75;
IF score >= 90 THEN
WriteString("A"); WriteLn;
ELSIF score >= 80 THEN
WriteString("B"); WriteLn;
ELSIF score >= 70 THEN
WriteString("C"); WriteLn;
ELSE
WriteString("F"); WriteLn;
END;
END Example. Modula-2 uses
ELSIF (like Ada) and closes the whole if-chain with a single END;. There is no begin/end pair inside branches — the END terminates the entire structure.Case statement
program Demo;
var month: Integer;
begin
month := 6;
case month of
12, 1, 2: writeln('Winter');
3..5: writeln('Spring');
6..8: writeln('Summer');
9..11: writeln('Autumn');
end;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
VAR month: INTEGER;
BEGIN
month := 6;
CASE month OF
12, 1, 2 : WriteString("Winter"); WriteLn; |
3..5 : WriteString("Spring"); WriteLn; |
6..8 : WriteString("Summer"); WriteLn; |
9..11 : WriteString("Autumn"); WriteLn;
END;
END Example. Modula-2 CASE branches are separated by
| instead of Pascal's semicolons, and the block closes with END instead of end;. The ELSE clause (default) uses the same keyword as Pascal.For loop
program Demo;
var i: Integer;
begin
for i := 1 to 5 do
write(i, ' ');
writeln;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
VAR i: INTEGER;
BEGIN
FOR i := 1 TO 5 DO
WriteInt(i, 0);
WriteString(" ");
END;
WriteLn;
END Example. The
FOR loop syntax is nearly identical to Pascal's — just uppercase keywords and END instead of begin/end closing the body.While loop
program Demo;
var count: Integer;
begin
count := 1;
while count <= 5 do
begin
write(count, ' ');
Inc(count);
end;
writeln;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
VAR count: INTEGER;
BEGIN
count := 1;
WHILE count <= 5 DO
WriteInt(count, 0);
WriteString(" ");
INC(count);
END;
WriteLn;
END Example. Modula-2 replaces Pascal's
begin/end pair inside while with just END. INC and DEC are built-in procedures (uppercase) that increment and decrement a variable.Repeat-until
program Demo;
var count: Integer;
begin
count := 1;
repeat
write(count, ' ');
Inc(count);
until count > 5;
writeln;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
VAR count: INTEGER;
BEGIN
count := 1;
REPEAT
WriteInt(count, 0);
WriteString(" ");
INC(count);
UNTIL count > 5;
WriteLn;
END Example. The
REPEAT...UNTIL loop is one of the most direct Pascal-to-Modula-2 translations — only the capitalisation changes. Both execute the body at least once and test the condition at the end.Loop with mid-body exit
{ Free Pascal: use break }
program Demo;
var i: Integer;
begin
i := 0;
while True do
begin
Inc(i);
if i >= 3 then break;
write(i, ' ');
end;
writeln;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
VAR i: INTEGER;
BEGIN
i := 0;
LOOP
INC(i);
IF i >= 3 THEN EXIT; END;
WriteInt(i, 0);
WriteString(" ");
END;
WriteLn;
END Example. Modula-2's
LOOP...END is an infinite loop; EXIT breaks out of it. This is the idiom for loops that need to exit from the middle — equivalent to Free Pascal's break. The EXIT may appear anywhere in the body.Procedures & Functions
Procedures
program Demo;
procedure Greet(name: String);
begin
writeln('Hello, ', name, '!');
end;
begin
Greet('Alice');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
PROCEDURE Greet(name: ARRAY OF CHAR);
BEGIN
WriteString("Hello, ");
WriteString(name);
WriteString("!"); WriteLn;
END Greet;
BEGIN
Greet("Alice");
END Example. Modula-2 procedures look almost identical to Pascal's: uppercase keywords, the procedure name repeated after
END. The string parameter type is ARRAY OF CHAR (an open array), which accepts arrays of any size.Functions (procedures with return type)
program Demo;
function Square(n: Integer): Integer;
begin
result := n * n;
end;
begin
writeln(Square(7));
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt;
PROCEDURE Square(n: INTEGER): INTEGER;
BEGIN
RETURN n * n;
END Square;
BEGIN
WriteInt(Square(7), 0); WriteLn;
END Example. Modula-2 has no separate
function keyword. A function is just a PROCEDURE with a return type after the closing ). RETURN value sends back the result — Pascal's result := becomes an explicit RETURN statement.VAR parameters
program Demo;
procedure Swap(var a, b: Integer);
var temp: Integer;
begin
temp := a;
a := b;
b := temp;
end;
var x, y: Integer;
begin
x := 5; y := 10;
Swap(x, y);
writeln(x, ' ', y);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
PROCEDURE Swap(VAR a, b: INTEGER);
VAR temp: INTEGER;
BEGIN
temp := a;
a := b;
b := temp;
END Swap;
VAR x, y: INTEGER;
BEGIN
x := 5; y := 10;
Swap(x, y);
WriteInt(x, 0); WriteString(" ");
WriteInt(y, 0); WriteLn;
END Example. VAR parameter syntax is identical in both languages — only the capitalisation differs. VAR passes by reference, letting the procedure modify the caller's variable.Nested procedures
program Demo;
procedure Outer;
procedure Inner(n: Integer);
begin
writeln('Inner: ', n);
end;
begin
Inner(42);
end;
begin
Outer;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
PROCEDURE Outer;
PROCEDURE Inner(n: INTEGER);
BEGIN
WriteString("Inner: ");
WriteInt(n, 0); WriteLn;
END Inner;
BEGIN
Inner(42);
END Outer;
BEGIN
Outer;
END Example. Like Pascal, Modula-2 supports fully nested procedures with access to the outer procedure's local variables. The inner procedure is declared in the outer procedure's local declaration section (before
BEGIN).Modules
Module structure
{ Pascal unit: one file }
unit Geometry;
interface
function Area(radius: Real): Real;
implementation
uses Math;
function Area(radius: Real): Real;
begin
result := Pi * radius * radius;
end;
end. (* geometry.def — definition module *)
DEFINITION MODULE Geometry;
PROCEDURE Area(radius: REAL): REAL;
END Geometry.
(* geometry.mod — implementation module *)
IMPLEMENTATION MODULE Geometry;
FROM MathLib0 IMPORT pi;
PROCEDURE Area(radius: REAL): REAL;
BEGIN
RETURN pi * radius * radius;
END Area;
END Geometry. Modula-2 splits every module into a definition file (public interface) and an implementation file (private details) — the compiler enforces the separation. This maps to Pascal's unit
interface/implementation sections, but in two separate files instead of one.Qualified vs unqualified imports
{ Pascal: uses makes all names directly visible }
program Demo;
uses SysUtils;
begin
writeln(IntToStr(42)); { unqualified }
end. (* Unqualified: FROM Module IMPORT Name — matches Pascal uses *)
(* FROM STextIO IMPORT WriteString, WriteLn; *)
(* WriteString("hello"); *)
(* Qualified: IMPORT Module — names need Module.Name prefix *)
(* IMPORT STextIO; *)
(* STextIO.WriteString("hello"); *)
MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn; (* unqualified *)
BEGIN
WriteString("hello"); WriteLn;
END Example. FROM Module IMPORT Name brings names into scope directly — like Pascal's uses. IMPORT Module keeps names qualified with Module.Name. Qualified imports are safer when two modules export the same name.Records & Pointers
Record types
program Demo;
type
Point = record
x, y: Integer;
end;
var origin: Point;
begin
origin.x := 3;
origin.y := 4;
writeln(origin.x, ' ', origin.y);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
TYPE
Point = RECORD
x, y: INTEGER;
END;
VAR origin: Point;
BEGIN
origin.x := 3;
origin.y := 4;
WriteInt(origin.x, 0); WriteString(" ");
WriteInt(origin.y, 0); WriteLn;
END Example. Record syntax is nearly identical in both languages — only uppercase keywords and a closing
END (instead of Pascal's end;). Field access uses the same dot notation.Record assignment
program Demo;
type
Point = record x, y: Integer; end;
var p1, p2: Point;
begin
p1.x := 3; p1.y := 4;
p2 := p1; { copies all fields }
p2.x := 99;
writeln(p1.x, ' ', p2.x); { 3 99 }
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
TYPE
Point = RECORD x, y: INTEGER; END;
VAR p1, p2: Point;
BEGIN
p1.x := 3; p1.y := 4;
p2 := p1; (* copies all fields *)
p2.x := 99;
WriteInt(p1.x, 0); WriteString(" ");
WriteInt(p2.x, 0); WriteLn; (* 3 99 *)
END Example. Both Modula-2 and Pascal copy all record fields with a single
:= assignment. This is a value copy — modifying p2 after the assignment does not affect p1.Pointer types
program Demo;
type
IntPtr = ^Integer;
var ptr: IntPtr;
begin
New(ptr);
ptr^ := 42;
writeln(ptr^);
Dispose(ptr);
end. MODULE Example;
FROM STextIO IMPORT WriteLn;
FROM SWholeIO IMPORT WriteInt;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
TYPE
IntPtr = POINTER TO INTEGER;
VAR ptr: IntPtr;
BEGIN
NEW(ptr);
ptr^ := 42;
WriteInt(ptr^, 0); WriteLn;
DISPOSE(ptr);
END Example. Modula-2 uses
POINTER TO T instead of Pascal's ^T. The ^ dereference operator is the same. Crucially, Storage must be imported for NEW/DISPOSE to work — without it, the compiler reports "ALLOCATE procedure not found for NEW substitution".Linked list node
program Demo;
type
NodePtr = ^Node;
Node = record { note: var can't be named "node" -- }
value: Integer; { Pascal is case-insensitive! }
next : NodePtr;
end;
var head, second: NodePtr;
begin
New(head);
head^.value := 1;
head^.next := nil;
New(second);
second^.value := 2;
second^.next := head;
writeln(second^.value, ' -> ', head^.value);
Dispose(second);
Dispose(head);
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
TYPE
NodePtr = POINTER TO Node;
Node = RECORD
value: INTEGER;
next : NodePtr;
END;
VAR head, node: NodePtr;
BEGIN
NEW(head);
head^.value := 1;
head^.next := NIL;
NEW(node);
node^.value := 2;
node^.next := head;
WriteInt(node^.value, 0);
WriteString(" -> ");
WriteInt(head^.value, 0); WriteLn;
DISPOSE(node);
DISPOSE(head);
END Example. NIL is uppercase in Modula-2. Forward pointer references work the same way as in Pascal — you can declare NodePtr = POINTER TO Node before Node is fully defined.Sets
Set types
program Demo;
type
Fruit = (Apple, Banana, Cherry);
FruitSet = set of Fruit;
var basket: FruitSet;
begin
basket := [Apple, Cherry];
if Apple in basket then
writeln('Has apple');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
TYPE
Fruit = (Apple, Banana, Cherry);
FruitSet = SET OF Fruit;
VAR basket: FruitSet;
BEGIN
basket := FruitSet{Apple, Cherry};
IF Apple IN basket THEN
WriteString("Has apple"); WriteLn;
END;
END Example. Set syntax is nearly identical — only uppercase keywords and the type-prefixed constructor
FruitSet{Apple, Cherry} differ from Pascal's bare [Apple, Cherry]. The IN membership operator is the same.Set operations
program Demo;
type
Fruit = (Apple, Banana, Cherry);
FruitSet = set of Fruit;
var a, b, combined: FruitSet;
begin
a := [Apple, Banana];
b := [Banana, Cherry];
combined := a + b; { union }
if Cherry in combined then writeln('Has cherry');
combined := a - b; { difference }
if not (Banana in combined) then writeln('No banana');
combined := a * b; { intersection }
if Banana in combined then writeln('Banana in both');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
TYPE
Fruit = (Apple, Banana, Cherry);
FruitSet = SET OF Fruit;
VAR a, b, combined: FruitSet;
BEGIN
a := FruitSet{Apple, Banana};
b := FruitSet{Banana, Cherry};
combined := a + b; (* union *)
IF Cherry IN combined THEN WriteString("Has cherry"); WriteLn; END;
combined := a - b; (* difference *)
IF NOT (Banana IN combined) THEN WriteString("No banana"); WriteLn; END;
combined := a * b; (* intersection *)
IF Banana IN combined THEN WriteString("Banana in both"); WriteLn; END;
END Example. Set union (
+), difference (-), and intersection (*) use the same operators in both languages. This is one of the areas where Modula-2 and Pascal are virtually identical.Adding and removing elements
program Demo;
type
Fruit = (Apple, Banana, Cherry);
FruitSet = set of Fruit;
var basket: FruitSet;
begin
basket := [];
Include(basket, Apple);
Include(basket, Banana);
Exclude(basket, Apple);
if Banana in basket then writeln('Has banana');
if not (Apple in basket) then writeln('No apple');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
TYPE
Fruit = (Apple, Banana, Cherry);
FruitSet = SET OF Fruit;
VAR basket: FruitSet;
BEGIN
basket := FruitSet{};
INCL(basket, Apple);
INCL(basket, Banana);
EXCL(basket, Apple);
IF Banana IN basket THEN WriteString("Has banana"); WriteLn; END;
IF NOT (Apple IN basket) THEN WriteString("No apple"); WriteLn; END;
END Example. Modula-2's built-in
INCL and EXCL procedures add and remove elements from a set variable. Pascal's Include/Exclude procedures (Free Pascal) serve the same purpose. The empty set is FruitSet{} instead of Pascal's [].Error Handling
No exception handling
program Demo;
uses SysUtils;
begin
try
raise Exception.Create('something went wrong');
except
on E: Exception do
writeln('Caught: ', E.Message);
end;
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
(* ISO Modula-2 has no exception handling mechanism. *)
(* Errors are communicated via return codes or VAR *)
(* parameters. HALT terminates on unrecoverable *)
(* errors. There is no try/except/finally. *)
BEGIN
WriteString("No exceptions in Modula-2"); WriteLn;
END Example. Standard ISO Modula-2 has no exception handling at all. Programs must handle errors through return codes,
BOOLEAN out-parameters, or HALT. This is a significant departure from modern Pascal (FPC) and requires a different design mindset.Error handling with return codes
program Demo;
uses SysUtils;
function SafeDivide(a, b: Integer; out quotient: Integer): Boolean;
begin
if b = 0 then
Exit(False);
quotient := a div b;
Exit(True);
end;
var quotient: Integer;
begin
if SafeDivide(10, 3, quotient) then
writeln(quotient)
else
writeln('Error: div by zero');
end. MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
FROM SWholeIO IMPORT WriteInt;
PROCEDURE SafeDivide(a, b: INTEGER; VAR quotient: INTEGER): BOOLEAN;
BEGIN
IF b = 0 THEN RETURN FALSE; END;
quotient := a DIV b;
RETURN TRUE;
END SafeDivide;
VAR quotient: INTEGER;
BEGIN
IF SafeDivide(10, 3, quotient) THEN
WriteInt(quotient, 0); WriteLn;
ELSE
WriteString("Error: div by zero"); WriteLn;
END;
END Example. The standard Modula-2 error-handling pattern is to return
BOOLEAN and pass the result via a VAR parameter. This is the same approach used in Go and C. Integer division uses DIV (not /), which is the same as Pascal.Terminating on fatal error
program Demo;
begin
writeln('About to halt');
Halt(1);
writeln('Never reached');
end. (* HALT from SYSTEM is not available in all Modula-2 *)
(* implementations on Compiler Explorer. The concept *)
(* is shown in comments; the runnable example *)
(* demonstrates normal termination instead. *)
MODULE Example;
FROM STextIO IMPORT WriteString, WriteLn;
BEGIN
WriteString("Demonstrating normal exit"); WriteLn;
(* HALT; would terminate abnormally here *)
END Example. In ISO Modula-2,
HALT is a standard environment procedure that terminates the program immediately — typically imported from SYSTEM. Unlike Pascal's Halt(n), ISO HALT takes no exit code argument. Reserve it for truly unrecoverable situations; structured programs prefer returning error codes.