Case Study 2: Error Handling Across Languages
The Big Picture
One of the recurring themes of this textbook is that the discipline Pascal teaches transfers to every language you will ever use. Exception handling is a perfect example: every major modern language has structured exception handling, and the patterns are remarkably similar. This case study compares how Pascal, Python, Java, C++, and C# handle the same set of error scenarios, so you can see both the universality of the concepts and the syntactic differences.
The Scenario
We will implement the same operation in each language: safely reading an integer from a file, with proper error handling for: 1. File not found 2. Invalid data format (the file contains text, not a number) 3. Resource cleanup (ensuring the file is closed)
Pascal
function ReadIntFromFile(const FileName: String): Integer;
var
F: TextFile;
S: String;
begin
if not FileExists(FileName) then
raise EInOutError.CreateFmt('File not found: %s', [FileName]);
AssignFile(F, FileName);
Reset(F);
try
ReadLn(F, S);
try
Result := StrToInt(Trim(S));
except
on E: EConvertError do
raise EConvertError.CreateFmt(
'Invalid integer in file %s: "%s"', [FileName, S]);
end;
finally
CloseFile(F);
end;
end;
{ Usage }
begin
try
Value := ReadIntFromFile('config.txt');
WriteLn('Value: ', Value);
except
on E: EInOutError do
WriteLn('File error: ', E.Message);
on E: EConvertError do
WriteLn('Format error: ', E.Message);
end;
end.
Pascal characteristics:
- try..except for catching, try..finally for cleanup — separate constructs
- on E: ExceptionType for type-specific handlers
- raise to throw, raise (bare) to re-raise
- Exception classes with Create/CreateFmt constructors
- No throws declaration — exceptions are not checked at compile time
Python
def read_int_from_file(filename):
try:
with open(filename, 'r') as f:
s = f.readline().strip()
try:
return int(s)
except ValueError:
raise ValueError(f'Invalid integer in file {filename}: "{s}"')
except FileNotFoundError:
raise FileNotFoundError(f'File not found: {filename}')
# Usage
try:
value = read_int_from_file('config.txt')
print(f'Value: {value}')
except FileNotFoundError as e:
print(f'File error: {e}')
except ValueError as e:
print(f'Format error: {e}')
Python characteristics:
- try..except..finally — all three in one construct
- with statement provides automatic resource cleanup (context managers)
- except ExceptionType as variable for type-specific handlers
- raise to throw, raise (bare) to re-raise
- No compile-time type checking — exceptions are runtime-only
- Dynamic typing means some errors caught by Pascal's compiler become runtime exceptions in Python
Comparison with Pascal: The with statement in Python serves a similar role to try..finally in Pascal for resource cleanup, but is more concise. Python's dynamic typing means you discover type errors at runtime rather than compile time — a key difference in philosophy. Pascal catches more errors earlier.
Java
public static int readIntFromFile(String filename)
throws IOException, NumberFormatException {
File file = new File(filename);
if (!file.exists()) {
throw new FileNotFoundException("File not found: " + filename);
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String s = reader.readLine().trim();
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
throw new NumberFormatException(
String.format("Invalid integer in file %s: \"%s\"", filename, s));
}
}
}
// Usage
try {
int value = readIntFromFile("config.txt");
System.out.println("Value: " + value);
} catch (FileNotFoundException e) {
System.out.println("File error: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Format error: " + e.getMessage());
} catch (IOException e) {
System.out.println("I/O error: " + e.getMessage());
}
Java characteristics:
- try-catch-finally — similar structure to Pascal
- try-with-resources (try (resource)) for automatic cleanup — Java 7+
- throws clause in method signature — checked exceptions (the compiler forces callers to handle them)
- catch (ExceptionType variable) for handlers
- throw to throw, throw e to re-raise (but this resets the stack trace — use throw without argument in newer Java to preserve it)
Comparison with Pascal: Java's checked exceptions (the throws clause) are the most significant difference. Java forces you to either handle an exception or declare that your method throws it. Pascal does not — exception handling is entirely voluntary, which puts more responsibility on the programmer to document and handle exceptions correctly. Java also uses catch where Pascal uses except, and throw where Pascal uses raise.
C++
#include <fstream>
#include <stdexcept>
#include <string>
int readIntFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("File not found: " + filename);
}
std::string s;
std::getline(file, s);
// file is closed automatically when it goes out of scope (RAII)
try {
return std::stoi(s);
} catch (const std::invalid_argument&) {
throw std::invalid_argument(
"Invalid integer in file " + filename + ": \"" + s + "\"");
}
}
// Usage
try {
int value = readIntFromFile("config.txt");
std::cout << "Value: " << value << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "File error: " << e.what() << std::endl;
} catch (const std::invalid_argument& e) {
std::cout << "Format error: " << e.what() << std::endl;
}
C++ characteristics:
- try-catch — no built-in finally (cleanup is handled via RAII — Resource Acquisition Is Initialization)
- catch (const ExceptionType& variable) for handlers
- throw to throw, throw; (bare) to re-raise
- Can throw any type, not just exception objects (though this is bad practice)
- No checked exceptions — like Pascal, exception handling is voluntary
Comparison with Pascal: C++ lacks finally but compensates with RAII: objects are automatically destroyed when they go out of scope, so destructors handle cleanup. This is similar in spirit to Pascal's interface reference counting, but applies to all stack-allocated objects. Pascal's explicit try..finally is more verbose but also more visible — you can see exactly where cleanup happens.
C# (.NET)
public static int ReadIntFromFile(string filename) {
if (!File.Exists(filename))
throw new FileNotFoundException($"File not found: {filename}");
using (var reader = new StreamReader(filename)) {
string s = reader.ReadLine().Trim();
try {
return int.Parse(s);
} catch (FormatException) {
throw new FormatException(
$"Invalid integer in file {filename}: \"{s}\"");
}
}
}
// Usage
try {
int value = ReadIntFromFile("config.txt");
Console.WriteLine($"Value: {value}");
} catch (FileNotFoundException e) {
Console.WriteLine($"File error: {e.Message}");
} catch (FormatException e) {
Console.WriteLine($"Format error: {e.Message}");
}
C# characteristics:
- try-catch-finally — all three, like Pascal
- using statement for automatic resource cleanup (similar to Python's with and Java's try-with-resources)
- catch (ExceptionType variable) for handlers
- throw to throw, throw; (bare) to re-raise (preserving stack trace)
- No checked exceptions — like Pascal and C++
Comparison with Pascal: C# is syntactically the closest to Pascal of all the languages shown here, which is no coincidence — Anders Hejlsberg, who designed C#, previously designed Delphi's Object Pascal. The using statement is C#'s equivalent of Pascal's try..finally for IDisposable resources. The exception semantics are nearly identical.
Comparison Matrix
| Feature | Pascal | Python | Java | C++ | C# |
|---|---|---|---|---|---|
| Catch syntax | on E: Type |
except Type as e |
catch (Type e) |
catch (Type& e) |
catch (Type e) |
| Cleanup syntax | try..finally |
with / finally |
try-with-resources / finally |
RAII (destructors) | using / finally |
| Throw syntax | raise |
raise |
throw |
throw |
throw |
| Re-raise syntax | raise (bare) |
raise (bare) |
throw |
throw; |
throw; |
| Checked exceptions | No | No | Yes | No | No |
| Custom exceptions | Class hierarchy | Class hierarchy | Class hierarchy | Any type (prefer classes) | Class hierarchy |
The Universal Lesson
The syntax differs, but the concepts are identical:
- Separate normal code from error handling (
trysection vs. handler section). - Guarantee cleanup (
finally, RAII,using,with). - Catch specific exceptions — avoid catching everything.
- Propagate what you cannot handle — let it reach a handler that can.
- Custom exceptions carry context — include relevant data in the exception object.
- Never silently ignore errors — every language community agrees on this.
This is why we emphasize Theme 2: the discipline Pascal teaches transfers to every language. Learn these patterns well in Pascal — with its clear syntax, strong typing, and explicit structure — and you will recognize them instantly when you encounter try-catch in Java, try-except in Python, or RAII in C++.
Unique Pascal Advantage: Compile-Time Safety
One advantage Pascal has over dynamically-typed languages like Python is that many errors are caught at compile time rather than runtime. A misspelled variable name in Python becomes a NameError at runtime. In Pascal, it is a compiler error that never reaches your users. Strong typing means that passing a string where an integer is expected is a compile error in Pascal but a runtime TypeError in Python.
This compile-time safety does not eliminate the need for exception handling — files can still be missing, networks can still fail, and users can still type nonsense. But it means that the exceptions you do encounter in Pascal are more likely to be genuine environmental errors, not programming mistakes that slipped through.
The combination of strong typing (catching bugs early) and exception handling (handling environmental failures gracefully) is what makes Pascal programs robust. It is the same combination that makes Java, C#, and other strongly-typed languages popular for mission-critical software.