Vector Range Checking and Exception Handling
For Vec, an out-of-range access throws an exception, and that exception is something the program can catch.
Try it: try / except / else / finally
How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.
How it works
- Statements in try run until one raises an exception; the rest of try is skipped.
- Python checks the except clauses in order and runs the first one whose type matches.
- If nothing was raised, the else block runs instead.
- finally runs in every case.
Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.
Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.
Loading the simulation…
A Request for Element Seven
Suppose a Vec holds five elements, numbered 0 through 4. Asking for element 2 is routine. Asking for element 7 is a mistake, because there is no element there. This article answers two questions: what the program does at the moment it makes that bad request, and what you can do about it in code.
What do you think happens?
You have a Vec of five elements and you ask for the element at index 7. Does the access quietly return some arbitrary value, or does something else happen?
Reveal answer
It throws an exception.
For Vec, an access outside the valid range does not return an arbitrary value. It throws an exception, which signals that something has gone wrong and that normal execution cannot simply continue along its planned path. The important point is that this exception is something the program can catch.
Two Paths From One Access
Think of an access as a fork in the road. If the index is within range, the access completes and the program moves on with the value it retrieved. If the index is out of range, the access throws an exception and control leaves the ordinary sequence of statements. From that point the program looks for a handler, which is a catch block prepared to deal with the exception.
Notice that the handler is not part of the access itself. It is a separate piece of code that the program reaches only because the exception was thrown. Both paths begin from the same event, the access. They differ only in whether the index was valid.
Which Indexes Are Valid
For a Vec with n elements, the valid indexes run from 0 up to n minus 1, so that $0 \leq i \leq n-1$. For the five-element Vec in the opening example, the valid indexes are 0, 1, 2, 3 and 4. Any other index, such as 5 or 7, is outside the range and triggers the exception path described above.
Where Execution Goes: try and catch
A try-block wraps the code that might throw. A catch handler placed after it says what to do when that code throws. The handler runs only when the code in the try-block throws. If nothing in the try-block throws, the handler is skipped entirely.
The example below is an original illustration of the pattern. It is not the exact code from the source, which names the pattern but leaves the details of the Vec type to the reader. It assumes, as the source states, that subscripting a Vec with an out-of-range index throws an exception.
int main()
{
Vec v(5); // five elements, indexes 0 through 4
try {
int x = v[7]; // out-of-range access: throws
cout << x << "\n"; // skipped when the access throws
}
catch (...) { // handler: runs only if the try-block threw
cout << "index 7 is not valid\n";
}
cout << "program continues here\n";
return 0;
}- 1Line 1 declares the Vec with five elements, so the valid indexes are 0 through 4.
- 2Line 3 opens the try-block, which wraps the code that may throw.
- 3Line 4 subscripts index 7. Because 7 is out of range, the access throws and control leaves the try-block.
- 4Line 5 is skipped, because the throw interrupted the try-block before it could run.
- 5Line 7 is the catch handler. It runs only because the try-block threw, and it deals with the problem in a controlled way.
- 6Line 9 runs after the handler finishes. The program has continued past the bad access rather than crashing at that point.
- Vec v(5) creates a container whose valid indexes are 0 through 4
- The try keyword marks the code that may throw
- v[7] is outside the valid range, so the access throws
- The catch handler runs only because the try-block threw
- The final statement runs because the handler dealt with the exception
Tracing a Handled Exception
Following the handled access step by step
A Vec has five elements. The try-block asks for index 7, has a statement after the access, and a catch handler follows. What happens, in order?
- 1Enter the try-block
Execution begins the protected code. No handler has run yet.
- 2Perform the access v[7]
Index 7 is outside the valid range 0 through 4, so the access throws an exception instead of returning an ordinary value.
- 3Skip the rest of the try-block
Control leaves the ordinary sequence, so the statement that prints the value never runs.
- 4Run the handler
Because the code in the try-block threw, the catch handler is the code prepared to respond, and it runs.
- 5Resume after the handler
The statement following the try-catch construct runs, so the program keeps going past the problem.
The bad access is handled. The program does not crash at the point of the access, and it continues with the statements after the handler.
When No Handler Is Waiting
A thrown exception does not disappear simply because no one wrote a handler for it. If the program does not catch an exception, the program terminates in a well-defined manner. The intent of that phrase matters. The program does not keep doing work in some unpredictable state, and it does not fail in an undefined way. It ends in an orderly fashion that the language defines.
Read the diagram from the bottom up. The access inside the helper throws. If the helper has no handler, the exception leaves the helper and travels to the function that called it. If that caller has no handler either, it keeps moving up toward main(). Once it reaches the top with no handler found, the program ends in its orderly, language-defined way.
Wrapping main() in a try-block
The source offers one practical way to reduce surprises from uncaught exceptions: make the body of main() a try-block. Any exception that escapes the rest of the program then reaches a handler attached to main(), so the program can report the problem in a controlled way rather than relying on the default outcome. This is useful because an exception thrown deep inside a helper function still reaches the try-block in main() on its way out.
- The whole body of
main()sits inside the try-block - An exception from
helper()propagates up to this handler - The handler reports the problem instead of relying on the default outcome
Mistakes With Vec Access and Handlers
Common mistake Assuming an out-of-range access returns an ordinary value
What it looks likeReading v[7] on a five-element Vec and expecting some default number back
Why it failsFor Vec, an out-of-range access throws an exception. It does not quietly produce an arbitrary value.
Do this insteadTreat an out-of-range index as an exception event, and handle it in a try-block and catch handler where the risk exists.
Common mistake Expecting the statements after the bad access in the try-block to run
What it looks likePlacing a print statement right after v[7] inside the try-block and expecting it to show the value
Why it failsWhen the access throws, control leaves the ordinary sequence, so the rest of the try-block is skipped.
Do this insteadPut only the code that depends on a valid access after that access, and let the handler deal with the failure.
Common mistake Believing a try-block alone makes a bad index safe
What it looks likeWrapping main() in a try-block and assuming every index problem is now handled gracefully
Why it failsThe source says the try-block in
main()reduces surprises, but it does not replace checking indexes where they are used.Do this insteadKeep the index checks close to where the access happens and use main()'s try-block as a final safety net.
Common mistake Thinking an uncaught exception is harmless because the program simply stops
What it looks likeLeaving a helper's access unprotected and assuming the program will recover on its own
Why it failsAn uncaught exception ends the program in a well-defined but final way. Work in progress is not completed.
Do this insteadHandle the exception where you can respond sensibly, or at least place a handler in main() to report it in a controlled way.
Practice: Tracing the Exception Path
A Vec holds six elements. A try-block contains three statements in order: first a read of index 2, then a read of index 6, then a print of the value just read. A catch handler follows the try-block. Which statements run, which are skipped, and does the program continue after the handler?
Hints
- Valid indexes for six elements run from 0 through 5, so index 6 is out of range.
- The read of index 2 completes normally, because it is inside the valid range.
- Once the read of index 6 throws, the statements after it in the try-block do not run.
Key Takeaways
- For Vec, an out-of-range access throws an exception rather than returning an ordinary value, and the program can catch that exception.
- A try-block marks the code that may throw, and a catch handler runs only when that code throws.
- A handled exception lets the program continue after the handler, while an uncaught exception ends the program in a well-defined, orderly way.
- Making the body of
main()a try-block reduces surprises from uncaught exceptions, but it does not replace checking indexes where they are used.