---
title: "12.5 When to panic and when to return Result"
description: "Guidelines for choosing between panic and Result: expected versus exceptional, contracts, library versus application code, and when unwrap is fine."
url: "https://learnrust.net/chapter-12/when-to-panic/"
last_updated: "2026-06-13"
---

# 12.5 When to panic and when to return Result

You now have both tools. `panic!` for unrecoverable failures (lesson [12.1](https://learnrust.net/chapter-12/panic-and-unrecoverable-errors/)), `Result` for recoverable ones (lesson [12.2](https://learnrust.net/chapter-12/result/)). The remaining skill is judgment: given a particular failure, which should it be? This lesson gives you the guidelines that experienced Rust programmers use, because choosing well is what makes the difference between a program that fails gracefully and one that crashes in a user's face.

## The core question: expected or exceptional?

The single most useful test is this: **is this failure something a correct program should anticipate, or is it a sign that something is already broken?**

If the failure is a normal, expected part of operation, return a `Result`. Bad user input, a missing file, a network hiccup, a number that doesn't parse: these happen during the ordinary life of a working program. The caller can and should decide how to respond, so hand them a `Result` and let them. Panicking over expected failures, as lesson [12.4](https://learnrust.net/chapter-12/handling-invalid-input/) showed with the guessing game, turns routine events into crashes.

If the failure means an assumption your code is built on has been violated, panic. An index past the end of an array you just sized, a value the type system should have made impossible, an invariant your own code is supposed to maintain but didn't: these are bugs. Continuing past a bug risks corrupting data or producing wrong answers silently, and a panic stops it cleanly and loudly (lesson [1.7](https://learnrust.net/chapter-1/reading-compiler-errors/)'s "fail at compile time or fail loud" philosophy, applied at runtime). A `Result` here would just be asking the caller to handle your bug, which they can't.

> **Key insight**
>
> Return `Result` when the *caller* is in a better position than you to decide what to do about the failure. Panic when there's nothing reasonable to decide, because the failure means the program's assumptions are already broken. "Could a well-written caller recover from this?" If yes, give them a `Result`. If no, panic.

## Guideline: validate at the boundary, trust within

A practical pattern follows from the test above. Data from outside your program (user input, files, network) is *untrusted*: validate it, with `Result`, at the boundary where it enters. Once it's validated, the rest of your code can *trust* it, and code that operates on already-validated data may reasonably panic if its assumptions break, because by then a violation is a bug, not bad input.

This is why the guessing game's `read_guess` returns a `Result` (lesson [12.4](https://learnrust.net/chapter-12/handling-invalid-input/)): it's at the boundary, handling untrusted typing. But a function deep inside a program that receives an already-validated `1..=100` value could `assert!` that invariant and panic if it's somehow false, because at that point a value outside the range means an earlier bug, not a user mistake.

## Guideline: contracts

A function often has a **contract**: conditions the caller must meet for the call to make sense. "The divisor must not be zero." "The index must be in bounds." When a contract is violated, that's the caller's bug, and panicking is the standard response, because a `Result` would imply the violation is a normal outcome to handle rather than a mistake to fix. The standard library does exactly this: indexing a vector out of bounds panics, it doesn't return a `Result`, because a valid program never does it. Document the contract, `assert!` it if you like (lesson [12.1](https://learnrust.net/chapter-12/panic-and-unrecoverable-errors/)), and panic when it's broken.

The contrast: if the "bad" condition is something callers can't always prevent (a file that might or might not exist), it isn't a contract violation, it's an expected outcome, so it's a `Result`.

## Guideline: library code leans toward Result

Who's calling matters. In **application code**, the program you control end to end, you can sometimes panic on a failure you've decided you don't want to handle, because you're the only one affected. In **library code**, code other people call, strongly prefer `Result`. A library that panics takes the decision away from its users; it crashes their program over a situation they might have wanted to recover from. Returning `Result` hands control back to the caller, who knows their context and you don't. As a rule, the more reusable the code, the more it should return errors rather than panic.

## When unwrap and expect are fine

Given all this, when is the `unwrap`/`expect` panic-shortcut acceptable? A few clear cases:

Examples, prototypes, and exploratory code, where robust error handling would obscure the point and a crash is a fine way to learn something failed. Tests, where a panic *is* how a test reports failure (chapter 14), so `unwrap` in a test is idiomatic, not sloppy. And cases where you have information the compiler lacks: if you just wrote `"42".parse::<i32>()` with a literal you know is valid, `unwrap` is reasonable because `Err` genuinely cannot happen. In that last case, prefer `expect` with a message explaining *why* it can't fail, so the next reader (and the panic, if you're wrong) understands your reasoning.

> **Best practice**
>
> In code that should keep running, default to `Result` and reserve panics for broken invariants. Use `unwrap`/`expect` freely in tests and quick experiments, and in production only where `Err` is truly impossible, documented with `expect("reason it can't fail")`. When in doubt, return a `Result`: it's easy for a caller to `unwrap` a `Result` they don't want to handle, but impossible for them to recover from a panic you chose for them.

## Quiz time

**Question #1**

For each, choose panic or `Result`, and say why:

a) A function that reads a config file that may not exist.
b) A function that's documented to require a non-empty slice and indexes element 0.
c) Parsing a command-line argument the user provided.

<details class="solution">
<summary>Show solution</summary>

a) `Result`: a missing file is expected and the caller may want to fall back to defaults. b) Panic: the non-empty requirement is a contract; an empty slice is the caller's bug, and indexing `[0]` panics anyway. c) `Result`: user-provided input is untrusted and bad arguments are expected, so validate at the boundary and let the program report the problem.

</details>

**Question #2**

Why should library code lean toward `Result` even for failures the author might be tempted to panic on?

<details class="solution">
<summary>Show solution</summary>

Because a library doesn't know its callers' context, and a panic crashes the caller's whole program, removing their chance to recover. Returning `Result` hands the decision back to the caller, who can `unwrap` it if they truly don't care or handle it if they do. Panicking takes a choice that isn't the library's to make.

</details>

**Question #3**

You write `let port: u16 = "8080".parse().expect("hardcoded port is valid");`. Is `expect` justified here? What does the message accomplish?

<details class="solution">
<summary>Show solution</summary>

Yes: the string is a literal you know parses, so `Err` is genuinely impossible, which is one of the legitimate `unwrap`/`expect` cases. The message documents *why* you believe it can't fail, so a future reader understands the reasoning, and if the literal is later changed to something invalid, the panic explains the broken assumption instead of just dumping a parse error.

</details>

You've been using `String` as the error type in these examples, which is fine for small programs but limited: a `String` error can't be inspected, only printed. The last lesson of the chapter shows the sturdier approach, custom error types built from the enums of chapter 11, plus the `Box<dyn Error>` escape hatch for when you just want errors to flow.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
