In this lesson, you will learn how to design error types that hold references to the data that caused the failure, ensuring your error reporting remains both memory-safe and efficient.
The Challenge of Context
When an operation fails, you often want to return more than just a string message; you want to return the specific part of the input that caused the issue. If you try to store a reference to the input inside your error type, the Rust compiler will stop you because it needs to know for how long that reference remains valid.
By using lifetimes, we can tell the compiler that our error type is "tied" to the input data it is describing.
Defining Lifetimes in Errors
Imagine a parser that fails when it finds an invalid character. Instead of copying the bad segment into a String (which is slow), we can store a reference to the original slice:
struct ParseError<'a> {
invalid_part: &'a str,
reason: &'static str,
}
fn validate_input<'a>(input: &'a str) -> Result<(), ParseError<'a>> {
if input.contains('!') {
Err(ParseError {
invalid_part: &input[0..1], // Storing a slice of the input
reason: "Exclamation marks are forbidden",
})
} else {
Ok(())
}
}
By adding the lifetime parameter 'a to ParseError, we satisfy the borrow checker. We are explicitly stating: "This error cannot outlive the string we are validating." This approach is powerful because it avoids heap allocations entirely. Your error is essentially just a pointer into your existing data.
Propagation with Constraints
When you propagate these errors using the ? operator, you must ensure that your function signatures correctly propagate those lifetime constraints. If you return an error, the function's return type must declare that the error contains a reference from the input. This pattern keeps your API transparent—anyone calling validate_input knows immediately that the returned error is borrowing from their original input string.
Key Takeaway: By binding your error types to the lifetime of the input data, you can create zero-copy error reporting that is both high-performance and strictly type-safe.