Part 3: Error Handling and Result Types - Robust Production Code

Introduction

While building my WAF scanner, I learned that Rust doesn't let you ignore errors. No catching exceptions in a try-catch block and moving on. No silent None returns that you forget to check. The compiler forces you to handle every error explicitly.

This initially felt tedious, but it caught so many bugs before they reached production. A network timeout? Must handle it. JSON parsing failure? Must handle it. Invalid user input? Must handle it. This article shows how I built robust error handling into simple-waf-scannerarrow-up-right.

Why Rust's Error Handling is Different

The Problem with Exceptions

Python/Java style:

def fetch_data(url):
    response = requests.get(url)  # Might raise exception
    return response.json()         # Might raise another exception

# Easy to forget error handling
data = fetch_data("https://api.com")  # Crashes if network error

Go style:

data, err := fetchData(url)
if err != nil {
    // Easy to ignore errors
}
// Forgot to check err? Silent bugs!

Rust style:

The Result Type

Basic Result

The Option Type for Missing Values

Real Example: HTTP Request Errors

From my scanner's HTTP client:

The ? Operator

Before and After

Without ? - verbose:

With ? - clean:

How ? Works

Custom Error Types

Simple String Errors

Enum-Based Errors

Better approach - structured errors:

Real Error Type from My Scanner

Complete implementation:

Using the Custom Error

Error Handling Patterns

Pattern 1: Propagate with ?

Pattern 2: Match for Different Handling

Pattern 3: Convert Error to Option

Pattern 4: Collect Results

Pattern 5: Continue on Errors

Option Combinators

Useful Option Methods

Real Example: WAF Detection

Result Combinators

Useful Result Methods

Error Context with anyhow

For applications (not libraries), anyhow provides easy error handling:

Logging Errors

Using log crate

Initialize Logging

Testing Error Handling

Unit Tests

Real-World Example: Complete Scan Function

Putting it all together:

Key Takeaways

  1. No exceptions: Errors are values (Result/Option)

  2. Compiler enforces handling: Can't ignore errors

  3. ? operator: Clean error propagation

  4. Custom error types: Structured, informative errors

  5. From trait: Automatic error conversion

  6. Option for missing data: No null pointer errors

  7. Match for exhaustive handling: All cases covered

  8. Combinators: Clean functional error handling

  9. Logging: Track errors in production

  10. Test error paths: Verify error handling works

Common Patterns Summary

Next in Series

In Part 4: Async Programming with Tokio, we'll explore how to test hundreds of payloads concurrently using async/await and the Tokio runtime. We'll see how Rust enables fearless concurrency without data races.


Based on implementing robust error handling in simple-waf-scanner, ensuring failures are explicit and recoverable.

Last updated