Result & Error Handling β What Fills the begin/rescue Void
Rust handles errors as return values instead of exceptions
Ruby's error handling is exception-based. Something goes wrong, raise. Catch it somewhere with rescue. Miss it and the program dies.
# Ruby
def read_config
File.read("config.toml")
rescue Errno::ENOENT => e
puts "file not found: #{e.message}"
nil
end
Rust has no exceptions. Functions that can fail return Result<T, E>.
// Rust
fn read_config() -> Result<String, std::io::Error> {
std::fs::read_to_string("config.toml")
}
Using Result
match read_config() {
Ok(content) => println!("{}", content),
Err(e) => println!("file not found: {}", e),
}
Like Option, use match to branch. Ok(T) for success, Err(E) for failure.
The ? operator β maps to Ruby's exception propagation
In Ruby, unrescued exceptions propagate upward. In Rust, ? propagates errors to the calling function.
# Ruby β automatic propagation
def load_app
config = File.read("config.toml") # exception bubbles up
parse(config)
end
// Rust β explicit propagation with ?
fn load_app() -> Result<App, Box<dyn std::error::Error>> {
let config = std::fs::read_to_string("config.toml")?; // returns Err here
let app = parse(&config)?;
Ok(app)
}
? means "if Err, return immediately; if Ok, extract the value and continue." Ruby's auto-propagation made explicit.
unwrap and expect
let config = read_config().unwrap(); // panics on Err
let config = read_config().expect("config required"); // panic with message
Common pattern: use unwrap during prototyping, replace with proper error handling later. Like Ruby's temporary rescue => e; raise e.
map_err β error conversion
fn load_config() -> Result<Config, AppError> {
let content = std::fs::read_to_string("config.toml")
.map_err(|e| AppError::Io(e))?; // io::Error β AppError
parse_config(&content)
.map_err(|e| AppError::Parse(e))?;
Ok(config)
}
Like Ruby's rescue IOError => e; raise AppError, e.message pattern.
The decisive difference from Ruby
Error possibility is visible in the type. A function returning Result<T, E> declares "this function can fail" in its signature. In Ruby, you can't know which methods throw exceptions without reading docs.
Key Points
Result<T, E> is Ok(value) or Err(error) β type-safe exception replacement
? operator propagates errors β Ruby auto-propagation made explicit
unwrap is for prototyping β use match/? in production
Error possibility is visible in the function signature
Pros
- ✓ Which functions can fail is obvious from types alone
- ✓ No more production crashes from forgotten rescue
Cons
- ✗ Defining error types is verbose β thiserror/anyhow crates help
- ✗ Overusing ? can make it hard to trace where errors originated