CrackedRuby logo

CrackedRuby

begin/rescue/ensure/end

Overview

Ruby implements exception handling through begin/rescue/ensure/end blocks that catch and process exceptions while ensuring cleanup code executes regardless of success or failure. The rescue clause catches exceptions of specified types, ensure runs cleanup code unconditionally, and end terminates the block structure.

Ruby's exception system centers on the Exception class hierarchy. All rescuable exceptions inherit from StandardError, while system-level exceptions like SystemExit and Interrupt inherit directly from Exception. The rescue mechanism defaults to catching StandardError and its subclasses unless explicitly configured otherwise.

The basic structure separates normal execution flow in the begin section from error handling in rescue clauses. Multiple rescue clauses can handle different exception types, with Ruby evaluating them in order until finding a match. The ensure clause executes after all other processing completes, whether the block exits normally or through an exception.

begin
  # Normal execution code
  result = risky_operation
rescue StandardError => e
  # Exception handling
  handle_error(e)
ensure
  # Cleanup code
  cleanup_resources
end

Ruby provides syntactic sugar for method-level exception handling without explicit begin blocks. Method definitions can include rescue and ensure clauses directly, treating the entire method body as the begin section.

def process_data
  perform_calculations
rescue ArgumentError => e
  log_error(e)
  nil
ensure
  close_connections
end

The retry keyword restarts execution from the beginning of the begin block, allowing recovery attempts after handling exceptions. Ruby limits retry attempts to prevent infinite loops, though developers must implement their own retry counting for production use.

Basic Usage

Exception handling requires matching the rescue clause to the expected exception types. Ruby evaluates rescue clauses from top to bottom, executing the first matching clause and skipping subsequent ones. Specific exception types should precede general ones to ensure proper matching.

begin
  data = JSON.parse(input_string)
  process_data(data)
rescue JSON::ParserError => e
  puts "Invalid JSON format: #{e.message}"
  return nil
rescue StandardError => e
  puts "Processing error: #{e.message}"
  return nil
end

The ensure clause executes regardless of how the block terminates. This guarantees resource cleanup even when exceptions occur or early returns execute. File handles, database connections, and network sockets commonly require ensure-based cleanup.

begin
  file = File.open('data.txt', 'r')
  content = file.read
  process_content(content)
rescue IOError => e
  puts "File access error: #{e.message}"
ensure
  file&.close
end

Rescue clauses can capture exceptions in variables using the => variable syntax. The exception object provides access to the message, backtrace, and other diagnostic information. Ruby assigns the caught exception to the specified variable for inspection within the rescue block.

begin
  unsafe_operation
rescue RuntimeError => error
  puts "Error occurred: #{error.message}"
  puts "Backtrace: #{error.backtrace.join("\n")}"
end

Multiple exception types can be handled by a single rescue clause using array syntax. This approach reduces code duplication when identical handling applies to several exception types.

begin
  network_request
rescue [Timeout::Error, Net::ReadTimeout, Net::OpenTimeout] => e
  puts "Network timeout: #{e.message}"
  retry_with_backoff
end

The else clause executes only when no exceptions occur in the begin block. This provides a clean separation between normal continuation logic and exception handling paths.

begin
  result = calculate_value
rescue ZeroDivisionError
  puts "Division by zero detected"
  result = 0
else
  puts "Calculation successful: #{result}"
ensure
  cleanup_calculations
end

Error Handling & Debugging

Exception objects carry extensive diagnostic information beyond the basic message. The backtrace method returns an array of strings showing the call stack at the exception point, while cause provides access to wrapped or chained exceptions that triggered the current error.

begin
  nested_operation
rescue StandardError => e
  puts "Primary error: #{e.message}"
  puts "Error class: #{e.class}"
  puts "Backtrace:"
  e.backtrace.each_with_index do |line, index|
    puts "  #{index}: #{line}"
  end

  if e.cause
    puts "Underlying cause: #{e.cause.message}"
  end
end

Custom exception classes enable domain-specific error handling and provide structured information about application-specific failures. Inheriting from StandardError ensures these exceptions integrate properly with rescue mechanisms.

class ValidationError < StandardError
  attr_reader :field, :value, :constraint

  def initialize(field, value, constraint)
    @field = field
    @value = value
    @constraint = constraint
    super("#{field} value '#{value}' violates constraint: #{constraint}")
  end
end

begin
  validate_user_input(user_data)
rescue ValidationError => e
  puts "Validation failed for #{e.field}"
  puts "Invalid value: #{e.value}"
  puts "Constraint: #{e.constraint}"
end

Exception filtering based on message content or other attributes provides fine-grained error handling. The rescue clause can include conditional logic to handle specific error scenarios differently.

begin
  database_operation
rescue ActiveRecord::RecordInvalid => e
  case e.message
  when /email.*taken/
    handle_duplicate_email
  when /password.*too short/
    handle_weak_password
  else
    handle_generic_validation_error(e)
  end
end

Logging strategies for exceptions should capture sufficient context for debugging while avoiding sensitive information exposure. Structured logging with consistent exception formatting aids troubleshooting across application components.

def log_exception(exception, context = {})
  error_data = {
    error_class: exception.class.name,
    error_message: exception.message,
    backtrace: exception.backtrace,
    context: context,
    timestamp: Time.now.utc.iso8601
  }

  logger.error(error_data.to_json)
end

begin
  process_payment(amount, card)
rescue PaymentError => e
  log_exception(e, { amount: amount, card_type: card.type })
  raise
end

Advanced Usage

Exception handling blocks can be nested to create layered error recovery strategies. Inner rescue clauses handle specific failures while outer clauses provide fallback handling for broader categories of errors.

begin
  begin
    primary_service_call
  rescue ServiceUnavailableError
    fallback_service_call
  end
rescue NetworkError => e
  puts "All network operations failed: #{e.message}"
  use_cached_data
rescue StandardError => e
  puts "Unexpected error: #{e.message}"
  safe_degraded_mode
end

The retry mechanism supports sophisticated recovery patterns with exponential backoff and maximum attempt limits. Implementing retry counters and delay strategies prevents overwhelming failing services while providing reasonable recovery opportunities.

def resilient_api_call(max_retries: 3, base_delay: 1)
  retries = 0

  begin
    make_api_request
  rescue Net::TimeoutError, Net::ReadTimeout => e
    retries += 1

    if retries <= max_retries
      delay = base_delay * (2 ** (retries - 1))
      puts "Request failed, retrying in #{delay} seconds (attempt #{retries})"
      sleep(delay)
      retry
    else
      puts "Max retries exceeded, giving up"
      raise
    end
  end
end

Exception handling integrates with Ruby's block and iterator patterns. Methods can rescue exceptions from yielded blocks while maintaining clean separation between the calling context and block execution.

def with_error_handling(operation_name)
  begin
    yield
  rescue StandardError => e
    puts "Error in #{operation_name}: #{e.message}"
    log_exception(e, operation: operation_name)
    nil
  end
end

result = with_error_handling("data processing") do
  complex_data_transformation(input)
end

Module-based exception handling provides reusable error management strategies across multiple classes. Mixing in exception handling modules creates consistent error behavior while maintaining code organization.

module ExceptionLogging
  private

  def with_exception_logging(operation)
    begin
      yield
    rescue StandardError => e
      self.class.logger.error(
        "#{self.class.name}##{operation} failed: #{e.message}"
      )
      raise
    end
  end
end

class DataProcessor
  include ExceptionLogging

  def process_batch(items)
    with_exception_logging(__method__) do
      items.map { |item| transform_item(item) }
    end
  end
end

Common Pitfalls

Rescuing Exception instead of StandardError catches system-level exceptions like SystemExit and Interrupt, preventing proper program termination. This interferes with process signals and can make applications difficult to stop cleanly.

# Problematic - catches system exceptions
begin
  long_running_operation
rescue Exception => e  # Don't do this
  puts "Error: #{e.message}"
end

# Correct - only catches application exceptions
begin
  long_running_operation
rescue StandardError => e
  puts "Error: #{e.message}"
end

Empty rescue clauses that swallow exceptions without logging or handling create silent failures that are extremely difficult to debug. Always log caught exceptions or provide alternative handling logic.

# Problematic - silent failure
begin
  risky_operation
rescue
  # Silent failure - debugging nightmare
end

# Better - log the exception
begin
  risky_operation
rescue StandardError => e
  logger.error("Operation failed: #{e.message}")
  logger.debug(e.backtrace.join("\n"))
  nil
end

Rescue clauses that are too broad can mask unrelated errors and create confusing debugging scenarios. Specific exception types provide clearer error handling and prevent catching unexpected failures.

# Problematic - too broad
begin
  user = User.find(id)
  user.update_attributes(params)
rescue StandardError
  render json: { error: "Something went wrong" }
end

# Better - specific exception handling
begin
  user = User.find(id)
  user.update_attributes!(params)
rescue ActiveRecord::RecordNotFound
  render json: { error: "User not found" }, status: 404
rescue ActiveRecord::RecordInvalid => e
  render json: { error: e.message }, status: 422
end

Infinite retry loops occur when retry conditions never change between attempts. Always implement retry limits and ensure the conditions causing failures can potentially resolve.

# Problematic - potential infinite loop
begin
  connect_to_database
rescue ConnectionError
  retry  # Will retry forever if database is down
end

# Better - limited retries with backoff
retries = 0
begin
  connect_to_database
rescue ConnectionError => e
  retries += 1
  if retries < 5
    sleep(retries * 2)
    retry
  else
    raise e
  end
end

Resource leaks occur when ensure clauses don't properly handle partially initialized resources. Check for nil values and use safe navigation when cleaning up resources that might not have been fully created.

# Problematic - potential nil reference
begin
  connection = establish_connection
  file = File.open(filename)
  process_data(connection, file)
ensure
  connection.close  # Fails if connection was never established
  file.close       # Fails if file was never opened
end

# Better - safe cleanup
begin
  connection = establish_connection
  file = File.open(filename)
  process_data(connection, file)
ensure
  connection&.close
  file&.close
end

Reference

Exception Hierarchy

Exception Class Parent Purpose
Exception Object Root exception class
SystemExit Exception Process termination
Interrupt Exception User interruption (Ctrl+C)
StandardError Exception Application exceptions
RuntimeError StandardError Generic runtime failures
ArgumentError StandardError Invalid argument values
TypeError StandardError Type-related errors
NameError StandardError Undefined names/constants
NoMethodError NameError Undefined method calls
IOError StandardError Input/output failures
SystemCallError StandardError System call failures

Syntax Reference

Structure Syntax Description
Basic rescue begin; code; rescue; handler; end Catches StandardError
Typed rescue begin; code; rescue Type => e; handler; end Catches specific type
Multiple types begin; code; rescue Type1, Type2 => e; handler; end Catches multiple types
Multiple clauses begin; code; rescue Type1; handle1; rescue Type2; handle2; end Sequential type matching
With ensure begin; code; rescue; handler; ensure; cleanup; end Guaranteed cleanup
With else begin; code; rescue; handler; else; success; end Success-only execution
Method rescue def method; code; rescue; handler; end Method-level handling

Control Flow Keywords

Keyword Context Effect
retry rescue clause Restarts begin block execution
raise anywhere Throws exception (re-raise in rescue)
return rescue/ensure Exits method with value
break rescue/ensure in block Exits block iteration
next rescue/ensure in block Continues to next iteration

Exception Object Methods

Method Returns Description
#message String Exception message text
#backtrace Array<String> Call stack trace
#backtrace_locations Array<Thread::Backtrace::Location> Structured backtrace
#cause Exception or nil Wrapped/chained exception
#class Class Exception class object
#inspect String Debug representation
#set_backtrace(bt) Array Sets custom backtrace

Raise Method Signatures

Signature Effect
raise Re-raises current exception
raise(message) Raises RuntimeError with message
raise(ExceptionClass) Raises exception of specified class
raise(ExceptionClass, message) Raises exception with custom message
raise(exception_object) Raises pre-constructed exception

Common Exception Types

Exception Triggered By Typical Handling
ArgumentError Invalid method arguments Validate inputs, provide defaults
TypeError Wrong object types Type checking, conversion
NoMethodError Undefined method calls Check object responds, provide alternatives
NameError Undefined variables/constants Check existence, lazy loading
IOError File/stream operations Retry, alternative sources
JSON::ParserError Invalid JSON parsing Validate format, fallback data
Net::TimeoutError Network timeouts Retry with backoff, cached responses
ActiveRecord::RecordNotFound Missing database records Handle missing data gracefully