Files
docs-rust/ch09/ch09-03-to-panic-or-not-to-panic.html
2026-06-22 21:27:36 +05:30

268 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>To panic! or Not to panic!</title>
</head>
<body>
<h2 id="to-panic-or-not-to-panic"><a class="header" href="#to-panic-or-not-to-panic">To <code>panic!</code> or Not to <code>panic!</code></a></h2>
<p>So, how do you decide when you should call <code>panic!</code> and when you should return
<code>Result</code>? When code panics, theres no way to recover. You could call <code>panic!</code>
for any error situation, whether theres a possible way to recover or not, but
then youre making the decision that a situation is unrecoverable on behalf of
the calling code. When you choose to return a <code>Result</code> value, you give the
calling code options. The calling code could choose to attempt to recover in a
way thats appropriate for its situation, or it could decide that an <code>Err</code>
value in this case is unrecoverable, so it can call <code>panic!</code> and turn your
recoverable error into an unrecoverable one. Therefore, returning <code>Result</code> is a
good default choice when youre defining a function that might fail.</p>
<p>In situations such as examples, prototype code, and tests, its more
appropriate to write code that panics instead of returning a <code>Result</code>. Lets
explore why, then discuss situations in which the compiler cant tell that
failure is impossible, but you as a human can. The chapter will conclude with
some general guidelines on how to decide whether to panic in library code.</p>
<h3 id="examples-prototype-code-and-tests"><a class="header" href="#examples-prototype-code-and-tests">Examples, Prototype Code, and Tests</a></h3>
<p>When youre writing an example to illustrate some concept, also including
robust error-handling code can make the example less clear. In examples, its
understood that a call to a method like <code>unwrap</code> that could panic is meant as a
placeholder for the way youd want your application to handle errors, which can
differ based on what the rest of your code is doing.</p>
<p>Similarly, the <code>unwrap</code> and <code>expect</code> methods are very handy when youre
prototyping and youre not yet ready to decide how to handle errors. They leave
clear markers in your code for when youre ready to make your program more
robust.</p>
<p>If a method call fails in a test, youd want the whole test to fail, even if
that method isnt the functionality under test. Because <code>panic!</code> is how a test
is marked as a failure, calling <code>unwrap</code> or <code>expect</code> is exactly what should
happen.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="cases-in-which-you-have-more-information-than-the-compiler"></a></p>
<h3 id="when-you-have-more-information-than-the-compiler"><a class="header" href="#when-you-have-more-information-than-the-compiler">When You Have More Information Than the Compiler</a></h3>
<p>It would also be appropriate to call <code>expect</code> when you have some other logic
that ensures that the <code>Result</code> will have an <code>Ok</code> value, but the logic isnt
something the compiler understands. Youll still have a <code>Result</code> value that you
need to handle: Whatever operation youre calling still has the possibility of
failing in general, even though its logically impossible in your particular
situation. If you can ensure by manually inspecting the code that youll never
have an <code>Err</code> variant, its perfectly acceptable to call <code>expect</code> and document
the reason you think youll never have an <code>Err</code> variant in the argument text.
Heres an example:</p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
</span> use std::net::IpAddr;
let home: IpAddr = "127.0.0.1"
.parse()
.expect("Hardcoded IP address should be valid");
<span class="boring">}</span></code></pre>
<p>Were creating an <code>IpAddr</code> instance by parsing a hardcoded string. We can see
that <code>127.0.0.1</code> is a valid IP address, so its acceptable to use <code>expect</code>
here. However, having a hardcoded, valid string doesnt change the return type
of the <code>parse</code> method: We still get a <code>Result</code> value, and the compiler will
still make us handle the <code>Result</code> as if the <code>Err</code> variant is a possibility
because the compiler isnt smart enough to see that this string is always a
valid IP address. If the IP address string came from a user rather than being
hardcoded into the program and therefore <em>did</em> have a possibility of failure,
wed definitely want to handle the <code>Result</code> in a more robust way instead.
Mentioning the assumption that this IP address is hardcoded will prompt us to
change <code>expect</code> to better error-handling code if, in the future, we need to get
the IP address from some other source instead.</p>
<h3 id="guidelines-for-error-handling"><a class="header" href="#guidelines-for-error-handling">Guidelines for Error Handling</a></h3>
<p>Its advisable to have your code panic when its possible that your code could
end up in a bad state. In this context, a <em>bad state</em> is when some assumption,
guarantee, contract, or invariant has been broken, such as when invalid values,
contradictory values, or missing values are passed to your code—plus one or
more of the following:</p>
<ul>
<li>The bad state is something that is unexpected, as opposed to something that
will likely happen occasionally, like a user entering data in the wrong
format.</li>
<li>Your code after this point needs to rely on not being in this bad state,
rather than checking for the problem at every step.</li>
<li>Theres not a good way to encode this information in the types you use. Well
work through an example of what we mean in <a href="../ch18/ch18-03-oo-design-patterns.html#encoding-states-and-behavior-as-types">“Encoding States and Behavior as
Types”</a><!-- ignore --> in Chapter 18.</li>
</ul>
<p>If someone calls your code and passes in values that dont make sense, its
best to return an error if you can so that the user of the library can decide
what they want to do in that case. However, in cases where continuing could be
insecure or harmful, the best choice might be to call <code>panic!</code> and alert the
person using your library to the bug in their code so that they can fix it
during development. Similarly, <code>panic!</code> is often appropriate if youre calling
external code that is out of your control and returns an invalid state that you
have no way of fixing.</p>
<p>However, when failure is expected, its more appropriate to return a <code>Result</code>
than to make a <code>panic!</code> call. Examples include a parser being given malformed
data or an HTTP request returning a status that indicates you have hit a rate
limit. In these cases, returning a <code>Result</code> indicates that failure is an
expected possibility that the calling code must decide how to handle.</p>
<p>When your code performs an operation that could put a user at risk if its
called using invalid values, your code should verify the values are valid first
and panic if the values arent valid. This is mostly for safety reasons:
Attempting to operate on invalid data can expose your code to vulnerabilities.
This is the main reason the standard library will call <code>panic!</code> if you attempt
an out-of-bounds memory access: Trying to access memory that doesnt belong to
the current data structure is a common security problem. Functions often have
<em>contracts</em>: Their behavior is only guaranteed if the inputs meet particular
requirements. Panicking when the contract is violated makes sense because a
contract violation always indicates a caller-side bug, and its not a kind of
error you want the calling code to have to explicitly handle. In fact, theres
no reasonable way for calling code to recover; the calling <em>programmers</em> need
to fix the code. Contracts for a function, especially when a violation will
cause a panic, should be explained in the API documentation for the function.</p>
<p>However, having lots of error checks in all of your functions would be verbose
and annoying. Fortunately, you can use Rusts type system (and thus the type
checking done by the compiler) to do many of the checks for you. If your
function has a particular type as a parameter, you can proceed with your codes
logic knowing that the compiler has already ensured that you have a valid
value. For example, if you have a type rather than an <code>Option</code>, your program
expects to have <em>something</em> rather than <em>nothing</em>. Your code then doesnt have
to handle two cases for the <code>Some</code> and <code>None</code> variants: It will only have one
case for definitely having a value. Code trying to pass nothing to your
function wont even compile, so your function doesnt have to check for that
case at runtime. Another example is using an unsigned integer type such as
<code>u32</code>, which ensures that the parameter is never negative.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="creating-custom-types-for-validation"></a></p>
<h3 id="custom-types-for-validation"><a class="header" href="#custom-types-for-validation">Custom Types for Validation</a></h3>
<p>Lets take the idea of using Rusts type system to ensure that we have a valid
value one step further and look at creating a custom type for validation.
Recall the guessing game in Chapter 2 in which our code asked the user to guess
a number between 1 and 100. We never validated that the users guess was
between those numbers before checking it against our secret number; we only
validated that the guess was positive. In this case, the consequences were not
very dire: Our output of “Too high” or “Too low” would still be correct. But it
would be a useful enhancement to guide the user toward valid guesses and have
different behavior when the user guesses a number thats out of range versus
when the user types, for example, letters instead.</p>
<p>One way to do this would be to parse the guess as an <code>i32</code> instead of only a
<code>u32</code> to allow potentially negative numbers, and then add a check for the
number being in range, like so:</p>
<figure class="listing">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore"><span class="boring">use rand::Rng;
</span><span class="boring">use std::cmp::Ordering;
</span><span class="boring">use std::io;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> println!("Guess the number!");
</span><span class="boring">
</span><span class="boring"> let secret_number = rand::thread_rng().gen_range(1..=100);
</span><span class="boring">
</span> loop {
// --snip--
<span class="boring"> println!("Please input your guess.");
</span><span class="boring">
</span><span class="boring"> let mut guess = String::new();
</span><span class="boring">
</span><span class="boring"> io::stdin()
</span><span class="boring"> .read_line(&amp;mut guess)
</span><span class="boring"> .expect("Failed to read line");
</span><span class="boring">
</span> let guess: i32 = match guess.trim().parse() {
Ok(num) =&gt; num,
Err(_) =&gt; continue,
};
if guess &lt; 1 || guess &gt; 100 {
println!("The secret number will be between 1 and 100.");
continue;
}
match guess.cmp(&amp;secret_number) {
// --snip--
<span class="boring"> Ordering::Less =&gt; println!("Too small!"),
</span><span class="boring"> Ordering::Greater =&gt; println!("Too big!"),
</span><span class="boring"> Ordering::Equal =&gt; {
</span><span class="boring"> println!("You win!");
</span><span class="boring"> break;
</span><span class="boring"> }
</span><span class="boring"> }
</span> }
<span class="boring">}</span></code></pre>
</figure>
<p>The <code>if</code> expression checks whether our value is out of range, tells the user
about the problem, and calls <code>continue</code> to start the next iteration of the loop
and ask for another guess. After the <code>if</code> expression, we can proceed with the
comparisons between <code>guess</code> and the secret number knowing that <code>guess</code> is
between 1 and 100.</p>
<p>However, this is not an ideal solution: If it were absolutely critical that the
program only operated on values between 1 and 100, and it had many functions
with this requirement, having a check like this in every function would be
tedious (and might impact performance).</p>
<p>Instead, we can make a new type in a dedicated module and put the validations
in a function to create an instance of the type rather than repeating the
validations everywhere. That way, its safe for functions to use the new type
in their signatures and confidently use the values they receive. Listing 9-13
shows one way to define a <code>Guess</code> type that will only create an instance of
<code>Guess</code> if the <code>new</code> function receives a value between 1 and 100.</p>
<figure class="listing" id="listing-9-13">
<span class="file-name">Filename: src/guessing_game.rs</span>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -&gt; Guess {
if value &lt; 1 || value &gt; 100 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
pub fn value(&amp;self) -&gt; i32 {
self.value
}
}
<span class="boring">}</span></code></pre>
<figcaption><a href="#listing-9-13">Listing 9-13</a>: A <code>Guess</code> type that will only continue with values between 1 and 100</figcaption>
</figure>
<p>Note that this code in <em>src/guessing_game.rs</em> depends on adding a module
declaration <code>mod guessing_game;</code> in <em>src/lib.rs</em> that we havent shown here.
Within this new modules file, we define a struct named <code>Guess</code> that has a
field named <code>value</code> that holds an <code>i32</code>. This is where the number will be
stored.</p>
<p>Then, we implement an associated function named <code>new</code> on <code>Guess</code> that creates
instances of <code>Guess</code> values. The <code>new</code> function is defined to have one
parameter named <code>value</code> of type <code>i32</code> and to return a <code>Guess</code>. The code in the
body of the <code>new</code> function tests <code>value</code> to make sure its between 1 and 100.
If <code>value</code> doesnt pass this test, we make a <code>panic!</code> call, which will alert
the programmer who is writing the calling code that they have a bug they need
to fix, because creating a <code>Guess</code> with a <code>value</code> outside this range would
violate the contract that <code>Guess::new</code> is relying on. The conditions in which
<code>Guess::new</code> might panic should be discussed in its public-facing API
documentation; well cover documentation conventions indicating the possibility
of a <code>panic!</code> in the API documentation that you create in Chapter 14. If
<code>value</code> does pass the test, we create a new <code>Guess</code> with its <code>value</code> field set
to the <code>value</code> parameter and return the <code>Guess</code>.</p>
<p>Next, we implement a method named <code>value</code> that borrows <code>self</code>, doesnt have any
other parameters, and returns an <code>i32</code>. This kind of method is sometimes called
a <em>getter</em> because its purpose is to get some data from its fields and return
it. This public method is necessary because the <code>value</code> field of the <code>Guess</code>
struct is private. Its important that the <code>value</code> field be private so that
code using the <code>Guess</code> struct is not allowed to set <code>value</code> directly: Code
outside the <code>guessing_game</code> module <em>must</em> use the <code>Guess::new</code> function to
create an instance of <code>Guess</code>, thereby ensuring that theres no way for a
<code>Guess</code> to have a <code>value</code> that hasnt been checked by the conditions in the
<code>Guess::new</code> function.</p>
<p>A function that has a parameter or returns only numbers between 1 and 100 could
then declare in its signature that it takes or returns a <code>Guess</code> rather than an
<code>i32</code> and wouldnt need to do any additional checks in its body.</p>
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
<p>Rusts error-handling features are designed to help you write more robust code.
The <code>panic!</code> macro signals that your program is in a state it cant handle and
lets you tell the process to stop instead of trying to proceed with invalid or
incorrect values. The <code>Result</code> enum uses Rusts type system to indicate that
operations might fail in a way that your code could recover from. You can use
<code>Result</code> to tell code that calls your code that it needs to handle potential
success or failure as well. Using <code>panic!</code> and <code>Result</code> in the appropriate
situations will make your code more reliable in the face of inevitable problems.</p>
<p>Now that youve seen useful ways that the standard library uses generics with
the <code>Option</code> and <code>Result</code> enums, well talk about how generics work and how you
can use them in your code.</p>
</body>
</html>