Error Handling
Error handling mechanisms vary significantly across languages. Here's how different languages handle errors and exceptions.
Basic Error Handling
- JavaScript
- PHP
- Rust
- Go
- Python
- Zig
- C#
- C++
- C
- Java
- Ruby
- Swift
// Try-catch
try {
riskyOperation();
} catch (error) {
console.error("Error:", error.message);
} finally {
cleanup();
}
// Throwing errors
throw new Error("Something went wrong");
// Custom errors
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
}
}
<?php
// Try-catch
try {
riskyOperation();
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
} finally {
cleanup();
}
// Throwing exceptions
throw new Exception("Something went wrong");
// Custom exceptions
class CustomException extends Exception {
public function __construct($message) {
parent::__construct($message);
}
}
// Result type
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// Using Result
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Option type
fn find_item(items: &[i32], target: i32) -> Option<usize> {
items.iter().position(|&x| x == target)
}
// Error return
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Using errors
result, err := divide(10, 2)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
// Custom error type
type DivisionError struct {
Message string
}
func (e *DivisionError) Error() string {
return e.Message
}
# Try-except
try:
risky_operation()
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
else:
print("No errors")
finally:
cleanup()
# Raising exceptions
raise ValueError("Something went wrong")
# Custom exceptions
class CustomError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
// Error union type
fn divide(a: f64, b: f64) !f64 {
if (b == 0) return error.DivisionByZero;
return a / b;
}
// Using error union
const result = divide(10, 2) catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};
// Error sets
const MathError = error{
DivisionByZero,
Overflow,
};
// Try-catch
try
{
RiskyOperation();
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
finally
{
Cleanup();
}
// Throwing exceptions
throw new ArgumentException("Something went wrong");
// Custom exceptions
public class CustomException : Exception
{
public CustomException(string message) : base(message) { }
}
// Try-catch
try {
riskyOperation();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown error" << std::endl;
}
// Throwing exceptions
throw std::runtime_error("Something went wrong");
// Custom exceptions
class CustomException : public std::exception {
const char* what() const noexcept override {
return "Custom error";
}
};
// No built-in exceptions
// Use return codes and error handling
int divide(double a, double b, double* result) {
if (b == 0) {
return -1; // Error code
}
*result = a / b;
return 0; // Success
}
// Using error codes
double result;
if (divide(10, 2, &result) != 0) {
fprintf(stderr, "Division by zero\n");
return;
}
printf("Result: %f\n", result);
// Try-catch
try {
riskyOperation();
} catch (IllegalArgumentException e) {
System.err.println("Error: " + e.getMessage());
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
} finally {
cleanup();
}
// Throwing exceptions
throw new IllegalArgumentException("Something went wrong");
// Custom exceptions
public class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
# Begin-rescue
begin
risky_operation
rescue ArgumentError => e
puts "Error: #{e.message}"
rescue => e
puts "Unexpected error: #{e.message}"
ensure
cleanup
end
# Raising exceptions
raise ArgumentError, "Something went wrong"
# Custom exceptions
class CustomError < StandardError
def initialize(message)
super(message)
end
end
// Do-catch
do {
try riskyOperation()
} catch let error as CustomError {
print("Error: \(error)")
} catch {
print("Unexpected error: \(error)")
}
// Throwing errors
throw CustomError.somethingWentWrong
// Error protocol
enum CustomError: Error {
case somethingWentWrong
case invalidInput(String)
var localizedDescription: String {
switch self {
case .somethingWentWrong:
return "Something went wrong"
case .invalidInput(let message):
return "Invalid input: \(message)"
}
}
}
Advanced Error Handling Patterns
- JavaScript
- PHP
- Rust
- Go
- Python
- Zig
- C#
- C++
- C
- Java
- Ruby
- Swift
// Promise error handling
fetch('/api/data')
.then(response => response.json())
.catch(error => console.error("Error:", error));
// Async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
return await response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
}
// Error boundaries (React)
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
}
<?php
// Error handling with set_error_handler
set_error_handler(function($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
// Exception chaining
try {
// code
} catch (Exception $e) {
throw new CustomException("New error", 0, $e);
}
// Error propagation with ?
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// Combinators
let result = divide(10, 2)
.map(|x| x * 2)
.map_err(|e| format!("Error: {}", e));
// Unwrap with custom message
let value = divide(10, 2)
.expect("Failed to divide");
// Error wrapping (Go 1.13+)
if err != nil {
return fmt.Errorf("failed to process: %w", err)
}
// Error checking
if errors.Is(err, ErrNotFound) {
// handle not found
}
// Error unwrapping
var targetErr *CustomError
if errors.As(err, &targetErr) {
// handle custom error
}
# Exception chaining
try:
risky_operation()
except ValueError as e:
raise RuntimeError("Failed") from e
# Context managers for cleanup
with open('file.txt') as f:
content = f.read()
# File automatically closed
# Suppressing exceptions
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('file.txt')
// Error propagation
fn read_file(path: []const u8) ![]const u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return try file.reader().readAllAlloc(allocator, 1024);
}
// Catching specific errors
const result = read_file("data.txt") catch |err| switch (err) {
error.FileNotFound => "default content",
else => return err,
};
// Using statement (automatic disposal)
using var file = new FileStream("data.txt", FileMode.Open);
// Automatically disposed
// Exception filters (C# 6+)
try {
ProcessData();
}
catch (Exception ex) when (ex.Message.Contains("timeout"))
{
// Handle timeout
}
// AggregateException
var exceptions = new List<Exception>();
// ... collect exceptions
throw new AggregateException(exceptions);
// RAII (Resource Acquisition Is Initialization)
{
std::ifstream file("data.txt");
// File automatically closed when out of scope
}
// Exception specifications (deprecated, use noexcept)
void function() noexcept {
// Function that doesn't throw
}
// Nested exceptions
try {
// outer code
} catch (...) {
std::throw_with_nested(std::runtime_error("Outer error"));
}
// Error codes with errno
#include <errno.h>
FILE* file = fopen("data.txt", "r");
if (file == NULL) {
perror("Error opening file");
// errno contains error code
}
// setjmp/longjmp (not recommended)
#include <setjmp.h>
jmp_buf env;
if (setjmp(env) == 0) {
// code that might call longjmp
} else {
// error handling
}
// Try-with-resources (automatic close)
try (FileReader file = new FileReader("data.txt")) {
// use file
} // automatically closed
// Multi-catch (Java 7+)
try {
process();
} catch (IOException | SQLException e) {
// handle both
}
// Suppressed exceptions
try (Resource1 r1 = ...; Resource2 r2 = ...) {
// if both throw, one is suppressed
}
# Retry mechanism
retries = 0
begin
risky_operation
rescue => e
retries += 1
retry if retries < 3
raise
end
# Exception hierarchy
begin
# code
rescue StandardError => e
# catches most exceptions
rescue => e
# catches all exceptions
end
// Result type (alternative to throwing)
func fetchData() -> Result<String, NetworkError> {
// return .success("data") or .failure(.timeout)
}
// Using Result
switch fetchData() {
case .success(let data):
print(data)
case .failure(let error):
print("Error: \(error)")
}
// Defer for cleanup
func processFile() {
let file = openFile()
defer { closeFile(file) }
// file will be closed when function exits
}
Key Takeaways
-
Exceptions vs error returns -- JavaScript, Python, PHP, Ruby, Java, C#, C++, and Swift use try/catch-style exceptions: you
throworraiseand optionallycatchdownstream. Go and Zig use explicit error returns: Go'sresult, err := divide(10, 2)and Zig'sconst result = divide(10, 2) catch |err| { ... }. Rust usesResult<T, E>andOption<T>in the type system. C uses return codes (int divide(..., double* result)returning 0 or -1) anderrno. Exceptions allow errors to propagate across call stacks implicitly; error returns force each call site to decide. Choose exceptions when you want centralized handling; choose error returns when you want explicit control flow and fewer surprises. -
Result/Option types -- Rust's
Result<T, E>and Zig's!Terror unions encode errors in the type system. For example,fn divide(a: f64, b: f64) -> Result<f64, String>in Rust forces callers to handleOkandErrviamatch,?, or.unwrap(). Zig'sfn divide(a: f64, b: f64) !f64returns either a value or an error from a compile-time error set. Swift'sResult<String, NetworkError>is optional but common for async code. Go's(value, error)is idiomatic and checked withif err != nil. Result types make "this can fail" visible in signatures. Prefer Rust or Zig when you want compile-time guarantees that errors are handled. -
Compiler-enforced handling -- Rust and Zig require explicit handling of errors: you must use
match,?,try, orcatch, or the code will not compile. For example,let contents = File::open(path)?;in Rust propagates errors; ignoring the?would requireunwrap()orexpect(), which is explicit. In Python, Java, or JavaScript, you can call a throwing function without a try/catch and the error will propagate (or crash) silently until caught. If your priority is eliminating unhandled errors at compile time, choose Rust or Zig; if you prefer flexibility and centralized handlers, use exception-based languages. -
Error propagation and wrapping -- Go 1.13+ supports
fmt.Errorf("failed: %w", err)for wrapping anderrors.Is(err, ErrNotFound)for checking. Rust's?operator propagates errors while preserving the conversion viaFrom. Python usesraise RuntimeError("failed") from efor chaining. Java and C# support exception chaining withinitCause. Wrapping preserves context when errors bubble up. Choose languages with good wrapping support when building layered systems where errors cross many boundaries. -
Resource cleanup -- RAII (C++),
defer(Go, Zig),using(C#),try-with-resources(Java), andwith(Python) ensure cleanup even when errors occur. For example, Zig'sdefer file.close()and C#'susing var file = new FileStream(...)guarantee disposal. C has no built-in mechanism; you must usegoto cleanupor nested conditionals. Prefer languages with structured cleanup when managing files, connections, or locks to avoid leaks on error paths.