|
| 1 | +--- |
| 2 | +marp: true |
| 3 | +author: Wojciech Przytuła |
| 4 | +backgroundColor: black |
| 5 | +color: grey |
| 6 | +transition: fade |
| 7 | +theme: gaia |
| 8 | +style: | |
| 9 | + pre { |
| 10 | + background-color: black; |
| 11 | + border-style: solid; |
| 12 | + border-color: grey; |
| 13 | + }, |
| 14 | +
|
| 15 | + code { |
| 16 | + background-color: black; |
| 17 | + } |
| 18 | +--- |
| 19 | + |
| 20 | +# Don't panic |
| 21 | + |
| 22 | +...or how should your Rust program behave when faced a critical error. |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## A situation which unfortunately happens too often... |
| 27 | + |
| 28 | +```rust |
| 29 | +// This function returns Order, so we are forced to return |
| 30 | +// an Order even for incorrect inputs. |
| 31 | +fn create_order(num_dishes: usize) -> Order { |
| 32 | + if num_dishes < Order::MAXIMUM_NUM_DISHES { |
| 33 | + Ok(Order::new(num_dishes)) |
| 34 | + } else { |
| 35 | + // ??? Order::INVALID ??? |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | + |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## LET'S PANIC! |
| 45 | + |
| 46 | +```rust |
| 47 | +// This function returns Order, so we are forced to return |
| 48 | +// an Order even for incorrect inputs. |
| 49 | +fn create_order(num_dishes: usize) -> Order { |
| 50 | + if num_dishes < Order::MAXIMUM_NUM_DISHES { |
| 51 | + Ok(Order::new(num_dishes)) |
| 52 | + } else { |
| 53 | + panic!("Too many dishes for a single Order") |
| 54 | + // This either unwinds the stack or aborts the program immediately. |
| 55 | + // (configurable in Cargo.toml of your project) |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | + |
| 60 | +``` |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## Hmm, maybe let's reconsider this... |
| 65 | + |
| 66 | +```rust |
| 67 | +/// Error signifying that there are too many dishes to fit in an Order. |
| 68 | +struct TooManyDishes; |
| 69 | + |
| 70 | +// This function returns Result, so that we are not forced to return |
| 71 | +// an Order for incorrect inputs - we just return Err. |
| 72 | +fn create_order(num_dishes: usize) -> Result<Order, TooManyDishes> { |
| 73 | + if num_dishes < Order::MAXIMUM_NUM_DISHES { |
| 74 | + Ok(Order::new(num_dishes)) |
| 75 | + } else { |
| 76 | + Err(TooManyDishes) |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | + |
| 81 | +``` |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Another common case - `Option`/`Result` |
| 86 | + |
| 87 | +```rust |
| 88 | +struct DivisionByZero; |
| 89 | + |
| 90 | +fn div(dividend: i32, divisor: i32) -> Result<i32, DivisionByZero> { |
| 91 | + if divisor == 0 { |
| 92 | + // It is idiomatic to return errors after failed checks early in an imperative way, using explicit `return`. |
| 93 | + return Err(DivisionByZero) |
| 94 | + } |
| 95 | + |
| 96 | + // It is idiomatic to have the "happy path" (the no-errors scenario) linear and using functional syntax. |
| 97 | + Ok(dividend / divisor) |
| 98 | +} |
| 99 | +fn main() { |
| 100 | + let res: Result<i32, DivisionByZero> = div(2137, 42); |
| 101 | + |
| 102 | + // We are 100% sure division by 42 can't fail, so let's use this shorthand. |
| 103 | + let quotient = res.unwrap(); |
| 104 | + |
| 105 | + // This is equivalent to: |
| 106 | + let quotient = match res { |
| 107 | + Ok(x) => x, |
| 108 | + Err(err) => panic!("called `Result::unwrap()` on an `Err` value: {:?}", err), |
| 109 | + } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +### Let's encode more guarantees in the type system! |
| 116 | + |
| 117 | +```rust |
| 118 | +use std::num::NonZeroI32; |
| 119 | + |
| 120 | +fn div(dividend: i32, divisor: NonZeroI32) -> i32 { |
| 121 | + dividend / divisor // Nothing can get broken here! |
| 122 | +} |
| 123 | +fn main() { |
| 124 | + // let quotient = div(2137, 42); // This would not type check, because 42 is not NonZeroI32. |
| 125 | + |
| 126 | + // We have to create a NonZeroI32 instance: |
| 127 | + let non_zero_divisor_opt: Option<NonZeroI32> = NonZeroI32::new(42); |
| 128 | + |
| 129 | + // We are 100% sure 42 is not 0, so let's use this shorthand. |
| 130 | + let non_zero_divisor = non_zero_divisor_opt.unwrap(); |
| 131 | + |
| 132 | + // This is equivalent to: |
| 133 | + let non_zero_divisor = match non_zero_divisor_opt { |
| 134 | + Some(x) => x, |
| 135 | + None => panic!("called `Option::unwrap()` on a `None` value"), |
| 136 | + } |
| 137 | + |
| 138 | + let quotient = div(2137, non_zero_divisor); |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +### But wait, we ended up with an `unwrap()` anyway. Did we then improve at all? |
| 145 | + |
| 146 | +Actually, yes. Now, the function (here: `div()`) with some (possibly complex) logic **only accepts** (so that the compiler verifies that in compile-time) valid arguments. This way, the function's code can be simpler (no invalid-input-related error handling). Also, it's easier to convince yourself that your number is nonzero that to make sure that it upholds all guarantees required in the docs of the function containing logic. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +## To `panic` or not to `panic` |
| 151 | + |
| 152 | +Don't panic: |
| 153 | +- if the failure is caused by bad user input - you don't want to open up a highway for DoS attackers, do you? |
| 154 | +- if the failure only affects some task and not the program in general, e.g. when the server returned bad data for a request of one of the users; others are unaffected; |
| 155 | +- if the failure is recoverable (there is a reasonable action to be done in such situation, e.g. give user the default value when the server can't be queried for the actual value); |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## To `panic` or not to `panic` |
| 160 | + |
| 161 | +Do panic: |
| 162 | +- if the failure is for sure caused by a bug in your code. It makes sense to inform the whole world that you wrote deficient software, by yelling at them. More seriously, this shortens the feedback loop and bugs are fixed earlier, instead of silently damaging production; |
| 163 | +- if the failure is not recoverable (there is no hope, the program is broken, *R.I.P.*, only the famous *restart* could help here); |
| 164 | + |
| 165 | +--- |
0 commit comments