In Dart, how does rethrow differ from throw e inside a catch, and when do you need Error.throwWithStackTrace?
answer
- which stack trace survives
- throw e restarts the trace
- rethrow only inside catch
- throwWithStackTrace returns Never
- translate the exception, keep the trace
basics
~20 sInside a Dart catch, rethrow re-throws the caught object with its original stack trace, while throw e starts a new trace at the catch. Error.throwWithStackTrace (Dart 2.16+) throws any object with a trace you supply, for translating exceptions or throwing outside a catch.
solid answer
~40 s`rethrow` is a statement that re-throws the object the enclosing catch clause caught, keeping the original stack trace, so the next handler still sees where the failure started. `throw e` throws the same object as if for the first time, so the trace a caller's `catch (e, st)` receives points at your catch block and the real origin is lost — the `use_rethrow_when_possible` lint in the recommended set flags it. `rethrow` only compiles lexically inside a catch clause and only re-throws the same object. `Error.throwWithStackTrace(error, stackTrace)`, added in Dart 2.16 and typed `Never`, fills both gaps: it throws any object — for example a new `ImportException` wrapping the low-level one — with a `StackTrace` you pass in, and it works outside a catch, such as when an error was stored and is re-raised later.
code
dart · 17 linesclass RowException implements Exception {
const RowException(this.row, this.message);
final int row;
final String message;
@override
String toString() => 'RowException(row $row): $message';
}
DateTime parseDate(int row, String cell) {
try {
return DateTime.parse(cell);
} on FormatException catch (e, st) {
// New, more useful exception; original trace still points into DateTime.parse.
Error.throwWithStackTrace(RowException(row, e.message), st);
}
}go deeper
Recall that rethrow keeps the original stack trace and throw e does not, and that rethrow only works inside a catch.
Explain what each tool does to the object and the trace, and when Error.throwWithStackTrace replaces rethrow, including its Never return type.
Show how you translate low-level exceptions into domain types at a layer boundary while keeping the original trace for whoever reads the logs.
Set a convention for where exceptions get translated and how the cause and trace travel, so logs from every layer lead back to the real throw site.
## Three ways to throw again When a catch clause decides it cannot fully handle what it caught, it has three tools. They differ in **which object** is thrown and **which stack trace** travels with it. | Tool | Object thrown | Stack trace the next handler sees | Where it is allowed | |---|---|---|---| | `rethrow;` | the caught object | the **original** trace | only inside a `catch` clause | | `throw e;` | the caught object | a **new** trace from this line | anywhere | | `Error.throwWithStackTrace(x, st)` | any non-null object `x` | the trace **you pass** | anywhere | ## rethrow: partial handling The common pattern is **handle a little, then let it go**: log, count, clean up, and pass the failure on. ```dart try { importFile(path); } on FileSystemException catch (e) { print('import of $path failed: ${e.message}'); rethrow; // callers still see the original trace } ``` Key facts: - `rethrow` is a **statement**, not an expression, and takes no operand. - It must appear **inside a catch clause**; anywhere else the analyzer reports `rethrow_outside_catch`. A `finally` block does not count. - It re-throws **exactly the object that was caught**, so it cannot change the type. ## Why throw e is a trap `throw e` looks equivalent, but a throw records the stack at the point of the throw. The handler above you receives a trace that starts in your catch block, and the frames that show where the failure really happened are gone. Effective Dart's rule is **use `rethrow` to rethrow a caught exception**, and the `use_rethrow_when_possible` lint, part of the recommended and flutter sets, reports `throw e` inside a catch that could have been `rethrow`. One nuance for `Error` subclasses: a class that **extends** `Error` has its `stackTrace` field set only the **first** time it is thrown, so that field keeps the original location even after `throw e`. That does not help handlers that only look at the `st` parameter of their catch, and most of them do. ## Error.throwWithStackTrace: throw something new, keep the old trace Added in **Dart 2.16**, `static Never throwWithStackTrace(Object error, StackTrace stackTrace)` behaves like `throw error` would if the current stack trace were `stackTrace`. Its return type is `Never`, so the analyzer knows code after it is unreachable, and it can end a function whose return type is non-nullable. It solves two problems `rethrow` cannot: 1. **Translating an exception.** A layer that catches a low-level `FormatException` often wants to throw its own domain exception carrying context — the row, the file name — without losing where the parse failed: ```dart } on FormatException catch (e, st) { Error.throwWithStackTrace(RowException(row, 'date', e.message), st); } ``` 2. **Re-throwing outside a catch.** When code caught a failure earlier, stored the object and its trace, finished some other work, and now needs to fail with the original trace. Details worth knowing: - If the object **extends `Error`** and has never been thrown, its `stackTrace` field is set to the trace you pass. - The SDK does **not guarantee object identity** of the trace: a handler may receive a different `StackTrace` object with the same `toString()` contents. - Both arguments must be non-null. ## Choosing between them - Same object, inside the catch: **`rethrow`**. - Different object, or no longer inside the catch: **`Error.throwWithStackTrace`** with the trace captured by `catch (e, st)`. - Plain `throw e` of a caught object: almost never. Throwing a fresh object with a fresh trace is right only when the new throw site genuinely is where the failure should be reported from.
- In Dart, why can a function returning DateTime end with Error.throwWithStackTrace in its catch clause and no return?`Error.throwWithStackTrace` is declared to return `Never`, so flow analysis knows the call never completes normally. The catch clause therefore cannot fall off its end, and the function needs no `return` there to satisfy its non-nullable return type.
- In Dart, can rethrow be used inside a closure declared in a catch block, such as a callback passed to a logger?No. `rethrow` must be lexically inside the catch clause of the same function; a closure body is a separate function, so the analyzer reports `rethrow_outside_catch`. Capture `e` and `st` and use `Error.throwWithStackTrace(e, st)` inside the closure instead.
saying these in an interview costs you the question
- throw e and rethrow are identical because the same object is thrown.
- rethrow can re-throw a different exception object than the one caught.
- rethrow works in a finally block after the catch has run.
- Error.throwWithStackTrace only accepts objects that extend Error.
- Translating an exception into a domain type always loses the original trace.