-1

Can someone simply explain to me how null assertion (!) works and when to use it?

rebix
  • 78
  • 5
  • 2
    Null assertion (`!`) tells the compiler that the operand will never be null; use it when dart tells you it could be null but you know that it can't ever be. There's an example of it in the docs: https://dart.dev/null-safety/understanding-null-safety#null-assertion-operator – Alex Meuer Oct 05 '21 at 15:56

1 Answers1

1

The ! operator can be used after any expression, e!.

That evaluates the expression e to value v, then checks whether v is the null value. If it is null, an error is thrown. If not, then e! also evaluates to v.

The static type of an expression e! is (basically) the static type of e with any trailing ?s remove. So, if e has type int?, the type of e! is int. You should not use e! unless e can be null (the type of e is potentially nullable).

The ! operator is dynamically checked. It can throw at runtime, and there is no static check which can guarantee that it won't. It's like using a value with type dynamic in that all the responsibility of preventing it from throwing is on the author, the compiler can't help you, and you need good tests to ensure that it won't throw when it's not supposed to. It's called an assertion because it should never throw in production code.

So, use e! when you know (for some reason not obvious to the compiler, perhaps because of some invariant guaranteeing that the value is not null while something else is true) that e is not null.

Example:

abstract class Box<T extends Object> {
  bool hasValue;
  T? get value;
}
...
  Box<int> box = ...;
  if (box.hasValue) {
    var value = box.value!;
    ... use value ...
  }

If you are repeatedly using ! on the same expression, do consider whether it's more efficient to read it into a local variable just once. Also, if (like this Box example) the value being null is equivalent to the other test you just did, maybe just check that directly:

  Box<int> box = ...;
  var value = box.value;
  if (value != null) {
    ... use value ...
  }

This code, with an explicit != null check on a local variable, is statically guaranteed to not throw because the value is null. The code using ! above relies on the author to maintain whichever invariant allowed them to write the !, and if something changes, the code might just start throwing at runtime. You can't tell whether it's safe just by looking at the code locally.

Use ! sparingly, just like the dynamic type and late declarations, because they're ways to side-step the compiler's static checking and ensure it that "this is going to be fine". That's a great feature when you need it, but it's a risk if you use it unnecessarily.

lrn
  • 64,680
  • 7
  • 105
  • 121