Why Senior Rust Developers Never Use unwrap()
Hey, when you’re writing Rust, do you find yourself adding .unwrap() everywhere?
I get it, I really do. It feels so good - no compiler errors, the code runs, done.
But have you noticed that every time you submit a PR, someone always leaves a passive-aggressive comment: “Can you handle this unwrap?”
I used to think these people were just being pedantic. The data is obviously there, how could it be None?
Then my production server crashed once, and I learned my lesson.
Your Code Actually Takes Two Paths
Look at this diagram. Every piece of code you write actually takes one of these two paths:
User Input
|
v
┌──────────────┐
│ Your Code │
└──────┬───────┘
|
┌────┴────┐
| |
Success Failure
| |
v v
Normal .unwrap()
Return |
| v
v Panic!
App App
Continues Dies
When writing code, we always think about the left path - data exists, format is correct, everything goes smoothly.
But production environment? That guy specializes in taking the right path just to see you fail.
So What Exactly Is unwrap?
Let me give you an analogy.
You ordered takeout, the delivery guy knocks and says “Your food’s here.” unwrap is like you don’t even open your eyes, just say “Leave it at the door” and turn away.
Normally, no problem. But what if the delivery guy got the wrong order? What if there was no order at all?
You wouldn’t know until you’re starving and go to get your food, only to find either someone else’s meal or nothing at all.
How do experienced developers do it? They’d ask first: “Which restaurant? Hot pot? Mild spicy? OK, leave it there.” Same logic applies to code.
Takeout delivery: Confirm before receiving to avoid mistakes
A Real Disaster Scene
This code probably looks familiar:
fn process_upload(file_path: &str) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(file_path)?;
let lines: Vec<&str> = contents.lines().collect();
let header = lines.first().unwrap(); // File must have content, right?
for line in lines.iter().skip(1) {
let fields: Vec<&str> = line.split(',').collect();
let name = fields.get(0).unwrap(); // Must have first column, right?
let email = fields.get(1).unwrap(); // Must have second column, right?
let age = fields.get(2).unwrap().parse::<u32>().unwrap(); // Must be a number, right?
save_user(name, email, age)?;
}
Ok(())
}
Works fine locally. First day in production? Crash. Someone uploads an empty file - crash. Someone’s CSV is missing a column - another crash. Some dude writes “twenty-five” in the age column - you guessed it, another crash. Five unwraps, five ways to die.
Unhandled unwraps are like time bombs in your code
So How Do the Pros Write It?
Same job, see how the experts do it:
fn process_upload(file_path: &str) -> Result<ProcessResult, ProcessError> {
let contents = fs::read_to_string(file_path)
.map_err(|e| ProcessError::FileRead(e.to_string()))?;
let lines: Vec<&str> = contents.lines().collect();
let header = lines.first()
.ok_or(ProcessError::EmptyFile)?; // Empty file? I'll tell you it's empty
let mut processed = 0;
let mut errors = Vec::new();
for (line_num, line) in lines.iter().skip(1).enumerate() {
let fields: Vec<&str> = line.split(',').collect();
if fields.len() < 3 {
// Not enough fields? Record it, process the next line
errors.push(format!("Line {}: insufficient fields", line_num + 2));
continue;
}
let age = match fields[2].trim().parse::<u32>() {
Ok(a) => a,
Err(_) => {
// Age is wrong? Record it, keep going
errors.push(format!("Line {}: invalid age format", line_num + 2));
continue;
}
};
save_user(fields[0], fields[1], age)?;
processed += 1;
}
Ok(ProcessResult { processed, errors })
}
See the difference? When problems occur, instead of crashing, it records them and continues. Like a kitchen where today’s fish isn’t fresh - they don’t close the restaurant, they tell customers “Fish is sold out, how about braised pork?”
Graceful error handling: Log problems, continue serving
So What Do I Do with Option/Result?
Simple, ask yourself three questions:
First, is it really impossible for this to fail?
If it’s a hardcoded regex, it probably won’t fail. But if it’s user input, file reading, network requests, don’t dream - anything can happen. Option and Result in Rust are there to remind you: this value might not exist. Option represents something that might have a value, and unwrap is the forceful way to get it regardless.
Second, how do I want to handle errors?
- Have a fallback? Use
unwrap_or(default_value) - Need to report up? Use
ok_or(error)with? - Complex situation? Write a proper
match
Third, can I accept the program crashing?
Test code crashing is fine, doesn’t matter. Production code? You dare let it crash, get ready for midnight phone calls.
Common Alternative Patterns
Give a default value:
let port = env_port.unwrap_or(8080); // Not configured? Use 8080
let config = get_config().unwrap_or_default(); // Nothing? Use default
Convert to error and pass up:
let user = find_user(id).ok_or(ApiError::NotFound)?; // Option to Result, not found? Tell the caller
Handle each case:
match find_user(id) { // Perfect handling of Option's two cases
Some(u) => do_something(u),
None => {
log::warn!("User {} not found", id);
return Ok(default_response());
}
}
So Can unwrap Never Be Used?
Actually, there are two situations where you can:
Test code is fine:
#[test]
fn test_parse() {
let result = parse("valid input").unwrap(); // It's a test, crashing just proves there's a problem
assert_eq!(result, expected);
}
Use expect for hardcoded things:
let re = Regex::new(r"^\d+$").expect("This regex I wrote myself can't be wrong");
Note it’s expect not unwrap. expect can include a description, so if it does crash, at least you know why.
A True Story
I once took over a project with 200+ unwraps.
Midnight emergency calls are every developer’s nightmare
Spent two days fixing them one by one. After deployment, production crashes dropped to zero. Not that there were no errors anymore, but errors became logs, became searchable information, not midnight emergency calls anymore.
Final Words
Go language gets criticized for writing if err != nil until your hands hurt, but at least it forces you to handle every possible error.
Rust gives you freedom - you can unwrap everything, or handle things properly. Most people choose the former, then pay the price in production.
Code review: The moment you discover and fix unwraps
Go search your project for how many unwraps you have, find the ones on critical paths, think about how to replace them.
Don’t wait for production to explode before you regret it.
If this was helpful, give it a like, share it with your colleagues who still unwrap everything, and follow Dream Animal Coding for more real-world programming tips.
One less unwrap, one less midnight emergency call. See you next time.