6

Dart provides an Error class and an Exception class for throwing exceptions. Why doesn't the Error constructor take a message parameter like the Exception constructor does?

From the Error class docs:

Error objects thrown in the case of a program failure.

An Error object represents a program failure that the programmer should have avoided.

From the Exception class docs:

An Exception is intended to convey information to the user about a failure, so that the error can be addressed programmatically. It is intended to be caught, and it should contain useful data fields.

An earlier SO answer cites a post confirming this distinction.

I like this distinction. That tells me that many of my checks should throw instances of Error.

I'm happy to subclass Error with the type of error, but I still need to report values that induced the error. Why is it that Dart makes it easy for me to do this with Exception but not with Error?

I realize that I can solve the problem by declaring an app-wide error class:

class AppError extends Error {
  String message;

  AppError([this.message]);

  String toString() {
    return message == null ? runtimeType : "$runtimeType: $message";
  }
}

This difference between Error and Exception has me thinking that I'm misunderstanding Error or using it improperly. What's the proper way to throw non-user errors in Dart?

Joe Lapp
  • 2,435
  • 3
  • 30
  • 42
  • 2
    Possibly it was to discourage directly using `Error` (and you probably shouldn't). Consider using `AssertionError` via `assert(condition, message)` or `StateError` instead. – jamesdlin Aug 21 '19 at 00:32
  • @jamesdlin, aha! I bet that's it. Thank you so much! – Joe Lapp Aug 21 '19 at 00:49
  • Google shows a total of 42 hits for 'dart "throw StateError"', which doesn't give me much confidence, but this examples page is one of those hits, suggesting that `StateError` is indeed the throwable to use. (`assert()` has limited use because it can't protect state in production.) https://dart.dev/samples#exceptions – Joe Lapp Aug 21 '19 at 01:54
  • 1
    Fair point about `assert()` in release code, although you could throw `AssertionError` directly (or create your own `assert`-like function to do it). Regarding `StateError`, the low hit count might be misleading since `StateError` is often thrown via other mechanisms (e.g. the [quiver.check](https://pub.dev/documentation/quiver/latest/quiver.check/quiver.check-library.html) functions). – jamesdlin Aug 21 '19 at 02:16
  • @jamesdlin, if you'd combine your two comments into an answer, I'd be happy to mark it my preferred answer. – Joe Lapp Sep 07 '19 at 15:08

2 Answers2

13

If anything, it's a mistake that Exception takes a message. You should never throw a plain new Exception("message").

The difference between Error and Exception (or really, any non-Error thrown object) is that Error represents a programming failure.

Your programs should not throw an Error as part for normal operation. You should not catch a specific Error and react to it. (It's fine for frameworks to catch all thrown objects and log them, in order to keep running, but they shouldn't react specifically to individual errors). That means that it doesn't actually matter which class you use to represent an error. The only purpose of an error is to be shown to the programmer, so they can fix their bug. Dart has a number of useful Error subclasses which cover most of the situations, and which ensures a fairly consistent formatting of the error messages. There is nothing inherently requiring you to use a RangeError for an out-of-bounds error, it's just a very convenient helper class, it helps the reader understand what's going on, and we do recommend that you use it.

Use ArgumentError when an argument to a function does not satisfy the extra (non-type) requirements of it (say, a list must be non-empty). For number arguments, there is a RangeError for when the number is not in an expected range. For a number intended as an index (like a list.operator[] argument), there's even a specialized IndexError. They're all there to ensure that you get consistent error messages.

If an operation cannot be performed right now, because the object is not in a state which supports it, use StateError.

If an operation cannot be performed by the current object ever, use UnsupportedError.

In practice, StateError, UnsupportedError and ArgumentError (and its subclasses) cover the wast majority of runtime errors thrown by user code. Simply throwing an Error("message") is less informative than using one of these, and inventing a new error type is very rarely worth it.

An exception, on the other hand, is intended to be caught and handled. It's a message to the caller, at the same level of significance as a return value, and it should contain enough information for the caller to catch the specific documented exception that the function might throw, and react to that specific exceptional situation. That's why an exception should, at the very least, have a unique type that allows you to recognize and catch it specifically, and if possible, it should contain any information needed to handle the exceptional situation.

Take FormatException which is thrown by various parse functions. It's specific to the operation. It contains available information to help you figure out where the problem is (the input and a position related to the error, mainly for showing to the user since you can't be expected to fix the input.)

lrn
  • 64,680
  • 7
  • 105
  • 121
  • Thank you. I have an additional question: [in the documentation](https://dart.dev/guides/libraries/futures-error-handling), we can see that code: `throw("some arbitrary error");`. Is it a bad practice to just throw a string? And does this mean that there is something magic that wraps `String` in an `Exception`? – Paleo Jul 30 '21 at 14:18
  • 1
    No, there is *no* magic which wraps anything. You can throw strings. And integers. Any object except `null` can be thrown, but since `String` is not an `Error`, throwing one is bad for reporting errors, and since it doesn't represent a specific exceptional situation, throwing one is bad for reporting exceptions. So, don't throw strings. – lrn Jul 30 '21 at 19:46
1

See https://github.com/dart-lang/sdk/blob/master/sdk/lib/core/errors.dart

Dart has several predefined Errors that you can instantly use.

Thomas
  • 8,397
  • 7
  • 29
  • 39