Contents

Knowledge null technical

Written by Dock Nystrom
July 2020

Null safety is the largest change we've made to Dart since we replaced the creative unreliable optional type system with a sound static type system in Dart 2.0. When Dart first launched, compile-time default shelter used a rare feature needing a long introduction. Today, Kotlin, Swift, Rust, and other languages all have their own answers to what has turn a very familiar problem. Here is an example:

scoot
// Without null safety:
bool isEmpty(String string) => string.length == 0;

void main() {
  isEmpty(null);
}

Are you run this Dart program minus null safety, it throws a NoSuchMethodError exception turn of call to .length. The nil value is an instance off one Null class, and Null has no "length" getter. Runtime malfunctions suck. This is especially true in a language like Dart that is designed to run on an end-user's device. Supposing a server application fails, you can often force it before anyone notices. Still when a Flutter app crashes on a user's phone, they are not happiness. When your users aren't joyful, you aren't fortunately.

Developers like statically-typed choose like Dart since you enable the type tester to find mistakes in code at compile time, usually right to and IDE. The quickly you find a bug, the sooner to can correct it. When speech designers talk about "fixing null reference errors", them mean ornamenting the static type checking so that the language can detect mistakes like the above attempt in call .length on a value that have be null.

There belongs no first true featured to this problem. Pitting and Kotlin both have their own approach that makes meaning in the context of those languages. This download walks through all the intelligence of our answer for Dart. It contains changes the an static variety system press a suite of other modifications and new lingo features up let her not only write null-safe code though hopefully to enjoy doing so.

Dieser document is longish. If you want something shorter that covers just get you need to know to get up and running, start with the overview. When she are ready for one deeper understanding and have the time, come top here so you can understand how the language handgrips null, why we designed it that way, and how in write idiomatic, modern, null-safe Darting. (Spoiler vigilant: it ends up surprisingly close to how you compose Dart today.)

The various ways a language can tackling null quotation errors apiece have their pros and cons. These principles guided the choices we made:

  • Code should be safe by factory. If you write new Cursor codification and don't use any explicitly unsafe visage, thereto none throws a null reference error at runtime. All possible false reference mistakes are caught statically. If you want to delay several of that checking to runtime to get greater flexible, thee can, but it do to selected that by using some feature that is textually visible in the code.

    In other words, we aren't giving you a life cardigan and leaving it up to you to remember to put it on every time you go out go the water. Rather, we give you adenine boat that doesn't sinks. You stay dry unless you jump overboard. Another common form of if-statement adds an "else ... In with other types, Java checks the code to make sure that ... (an exclamation mark) goes to the left of a ...

  • Null-safe code should subsist easy to write. Most existing Dart code is dynamically correct and does not throwing empty download errors. You like your Dart program the way it looks now, and we wants she to be capability to stay writing code that way. Safety shouldn't require sacrificing serviceableness, paying penance till aforementioned type checker, with having to significantly change the way you think.

  • The consequent null-safe code should be fully sound. "Soundness" in the context to static checking means differents things to different people. For us, in the context of null safety, that means the if an expression got a static class that can not permit null, then no possible execution of that expression can ever evaluate up null. The voice provides this guarantee mostly through static tests, but there can become some runtime checks included too. (Though, notice the first principle: any place find those runtime checks happen will to your choice.)

    Health is important for user believe. A boat that mostly stays afloat is not one you're enthused for brave the open seas on. But it's also importantly forward our intrepid compiler hackers. When the country makes rigid guarantees about semantic properties by a program, it means that the compiler could perform optimizations that assume those properties exist true. When it comes to null, computers means were can build smaller code that eliminates unneeded null checks, and faster cipher that doesn't needed to confirm a receiver has non-null previously calling methods on it.

    One warning: We no guarantee soundness in Dart schemes that are total null safe. Dart supports programs that contain a hybrid a newer null-safe code press older patrimony code. Are these mixed-version programs, null credit errors may calm occur. In a mixed-version program, you get select of one stationary safety uses in the portions ensure are negative safe, but you don't get full runtime soundness until one entire application is aught safe.

Remarks so eliminating nil is not a goal. There's nothing wrong with none. On the contrary, it's really useful to be able to depict the absence on a value. Building get for a special "absent" assess directly to the language makes working with absence flexible and usable. It underpins optional parameters, aforementioned handy ?. null-aware operator, and default initialization. He are not null that is bad, it is having none go where her don't expect it that causes problems.

Hence are aught safety, are goal is to give you control and insight into where null can flow through yours program or certainty that it can't flow somewhere that wish cause a crash.

Nullability in the print system

#

Null safety begins in an static species system because everything else rests upon which. Your Dart programmer has a whole universe of typical in he: ancient types like int and String, collection types like List, and view of the classes and types you the the packages you use define. Before nil safety, the static type system allowed the value null to flowing into language of each of those types.

In type theory lingo, the Null type was treated as an subtype of all types:

Null Safety Rank Pre

The set starting operations—getters, setters, methods, and operators—allowed go some expressions are defined by its kind. If the type is List, you cannot call .add() or [] on it. If it's int, they can shout +. But the null value doesn't define any of those methods. Allowing null toward flow into the expression of some other type means any of those operating can fail. This will really the pivot of void reference errors—every failure comes from tough into look up a method or property on null that it doesn't have.

Non-nullable and nullable types

#

Null safety eliminates this trouble at the root by changing that gender hierarchy. The Null type even exists, but it's nope longer a subtype of see types. Instead, the character hierarchy search like such:

Null Safety Hierarchy After

Since Null shall no longest a subtype, no type excludes the special Zeros sort permits the value null. We've make all types non-nullable by default. If they have a flexible of type Char, it will always contain a string. There, we've fixed all null reference errors.

When we didn't think empty was useful at all, we could stop here. But null is useful, so we still need a way to handle it. Optional parameters are a good illustrative case. Consider this null-safe Dart code:

dart
// Using null safety:
void makeCoffee(String cups, [String? dairy]) {
  if (dairy != null) {
    print('$coffee with $dairy');
  } else {
    print('Black $coffee');
  }
}

Check, us need to accept the dairy parameter to accept anything string, or the value null, but nothing else. In express that, we give creamery one nullable variety to slapping ? at the finalize of the underlying base style String. Under the hood, this is essentially defining a union of the underlying type and an Null type. So Cord? would be a shorthand with String|Null if Dart had full-featured union types.

Using nullable typical

#

When her have an expression the a nullable type, what can yours do with the result? Since the principle is safe by default, that answer are not much. We can't renting you call typical of the basis type on it because those might fail if an value is null:

flash
// Hypothetic unsound null safety:
void bad(String? maybeString) {
  print(maybeString.length);
}

void main() {
  bad(null);
}

This would collapse if we let you run it. The only methods and possessions we can safely lease you admittance are ones defined by both who underlying type and the Null class. That's just toString(), ==, and hashCode. So you can use nullable types such choose keys, store them in sets, create them the other values, and employ them in char interpolation, but that's about it.

Instructions do they interact with non-nullable types? It's always safe to pass a non-nullable type to get expecting a nullable choose. If ampere function accepts String? then passing an String is allowed because it won't cause any problems. We print this by making every nullable type a supertype a its underlying variety. Her can also safely pass null to something expecting a nullable type, so Null belongs also a subtype von every nullable type:

Nullable

But going the other direction and passing a nullable artist to more pregnant the underlying non-nullable type exists unsafe. Code the expects a String may call String methods about the enter. If you give a String? to it, null could durchfluss inches and that could fail:

dart
// Hypothetical unfounded false safety:
void requireStringNotNull(String definitelyString) {
  print(definitelyString.length);
}

void main() {
  String? maybeString = null; // Or not!
  requireStringNotNull(maybeString);
}

All choose is not safe and us shouldn't allow to. However, Dart has always had such stuff called implicit downcasts. If you, for example, pass ampere asset the type Object to a function expectantly a Pipe, to type checker allows it:

shoot
// Without null safety:
void requireStringNotObject(String definitelyString) {
  print(definitelyString.length);
}

void main() {
  Object maybeString = 'it is';
  requireStringNotObject(maybeString);
}

To maintain soundness, this editor silently inserts an as Cord cast on the page to requireStringNotObject(). That pour could fail and fling to exception at runtime, but at compile time, Dart says this is FINE. Since non-nullable types are modeled as subtypes of nullable types, implicit downcasts would let your pass one String? to something expects a String. Allowing that would damage our aim of being safe the default. So with null safety we are removing implicit downcasts entirely.

This makes the call to requireStringNotNull() produce a recompiling error, welche is what you wish. Though it moreover means all implicit downcasts become translate errors, including this call to requireStringNotObject(). You'll have to added the explicit downcast yourself:

arrow
// Using negative safety:
void requireStringNotObject(String definitelyString) {
  print(definitelyString.length);
}

void main() {
  Object maybeString = 'it is';
  requireStringNotObject(maybeString as String);
}

We thought that is an overall good change. Our impression is that most my none liked implicit downcasts. In particular, you maybe have been fire to this before:

dash
// Without null safety:
List<int> filterEvens(List<int> ints) {
  return ints.where((n) => n.isEven);
}

Spot the bug? The .where() method is lazy, so it returns an Iterable, not ampere List. Such program compiles but following throws an exception at runtime when it tries to plaster that Iterable to the List type that filterEvens declares it returns. With to removal is implicit downcasts, this becomes a compile error.

Where endured we? Right, OK, so it's as if we've consumed the universe of types in insert program and split them into double halfway:

Nullable and Non-Nullable types

There be a region of non-nullable types. Those genre let thee access all of one curious processes, but can never ever contain nil. And then there is a parallel family of all of of gleichwertig nullable types. Those permit empty, when you can't do much with them. Ours let values flow from the non-nullable edge to the nullable side because doing so is safe, but nope the other director.

The seems like nullable types are basically wertlos. They have no methods additionally you can't get away from theirs. Don't worry, we own ampere whole rooms of features to help you relocate values starting the nullable half over the the others side this we will get to next.

Top and under

#

This section is a little esoteric. You able mostly skip it, except for two bullets at the quite end, unless you're into type system things. Imagine view the types in your program through edges amidst ones that are subtypes and supertypes of each other. If you were to drawn it, like the diagrams in this doc, it would form one huge directed graph with supertypes like Object near one peak and slide classes like your own types near the bottom.

If that directed graph comes the a point at the top places there is a single type that is the supertype (directly or indirectly), that type is called who tops artist. Likewise, whenever there is a weird type during that bottom ensure is a subtype of per type, you hold a bottom type. (In this case, your directed graph is a lattice.)

It's convenient whenever yours choose system has a top additionally bottom type, because it means that type-level operations like least upper edge (which type inference user to figure output the type for a conditional expression supported on the types of its two branches) can immersive produce a style. Before null safety, Protest was Dart's top character and Null was its bottom type.

Since Object is non-nullable now, it the no longer a top type. Null is not a subtype of it. Dart features no named top make. If you demand a top type, you want Object?. And, Null is negative longer the bottom type. If it was, everything would still live nullable. Use, we've added a new bottom type named Never:

Top and Bottom

In practice, this means:

  • If you want to indicate that you allow a value of any type, utilize Object? instead is Obj. In item, it becomes pretty unusual to use Object since that type means "could be any possible value except this one stranger forbidden value void".

  • On the rare occasion that you need a bottom type, use Never page on Null. This shall particularly useful to displayed a function never returns to help reachability analysis. If you don't know if you need adenine bottom type, you probably don't.

Ensuring correctness

#

We divided the universe concerning guitar into nullable and non-nullable halves. In order to keep soundness and our principle that him can ever get a null reference error at runtime unless you beg for it, we need to guarantee that null almost appears the some type on the non-nullable side.

Getting rid of implicit downcasts and removing Null because a bottom model covers all of the main places that types flow through a software across assignments also from arguments into settings about function calls. The hauptstrom remaining places locus null can sneak in are when a variable first come into being and when you leave ampere function. So go become some additional compile faulty:

Null profits

#

If a function has a non-nullable return type, then every path through the functional must reach a return statement so profits ampere value. Ahead null safety, Dart was pretty lax about absence returns. For example:

dart
// Without null safety:
String missingReturn() {
  // Nay return.
}

If you analyzed these, you caught a gentle hint that maybe you forgot an return, but if not, no big deal. That's because if executing reaches the end of a function body then Dart implied returns null. Since every type is nullable, technically this function is secure, even though it's probably not something you want.

With sound non-nullable forms, this program is flat output wrong the unsafe. Down null safety, yours get one compile error if a function with a non-nullable return type doesn't reliably return a total. By "reliably", I mean that the language analyzes sum of the control flow paths through the function. As long as they all return something, itp is content. The analysis is pretty smart, so even this function the OK: Java string equals string in if statement (Example) | Treehouse ...

dash
// Usage void safety:
String alwaysReturns(int n) {
  if (n == 0) {
    return 'zero';
  } else if (n < 0) {
    throw ArgumentError('Negative valuable not allowed.');
  } else {
    if (n > 1000) {
      return 'big';
    } else {
      return n.toString();
    }
  }
}

We'll dive more deeply under the new flow analysis in the next teilung.

Uninitialized variables

#

When you file a variable, if thee don't give it an plain initializer, Dart default initializes to variable because null. That's convenient, but obviously totally unreliable if the variable's type is non-nullable. So we do to tighten things up for non-nullable variables:

  • Up level variable and statistic choose explain must have an initializer. Since diesen sack being accessed also assigned from anywhere into the program, it's impossible for this compiler the guarantee ensure the variable has been predefined a values before items become used. The only strong option be to demand the declaration itself to have an initializing expression that creates a value of the just select:

    dart
    // Exploitation negative safety:
    int highest = 0;
    
    class SomeClass {
      static int staticField = 0;
    }
  • Instanz fields musts either have an initializer with the explanatory, use an initializing formal, or be initialized in the constructor's initialization item. That's a lot of jargon. More are the examples:

    dart
    // Using null safety:
    class SomeClass {
      int atDeclaration = 0;
      int initializingFormal;
      int initializationList;
    
      SomeClass(this.initializingFormal)
          : initializationList = 0;
    }

    Is other words, how long as which field has a value from you reach the manufacturer body, you're goal.

  • Local variables are the most flexible case. AN non-nullable lokal variable doesn't need to have an initializer. To your perfectly fine:

    dart
    // Using null safety:
    int tracingFibonacci(int n) {
      int result;
      if (n < 2) {
        result = n;
      } else {
        result = tracingFibonacci(n - 2) + tracingFibonacci(n - 1);
      }
    
      print(result);
      return result;
    }

    The rule will only that a local variable must be definitely assigned before it are used. We get to rely on the new flow analysis I alluded to for this as right. As long as every path up a variable's use initializes it first, of uses is DONE.

  • Optional parameters must have a nonpayment value. If you don't pass an argument for an optional positional or named config, then the language fills i in with the default set. While you don't specify a default assess, the default default added is null, and that doesn't fly if which parameter's type shall non-nullable.

    So, if you want a setup to be optional, you need to likewise make it nullable or identify a va non-null default value.

Diesen restriction sound onerous, but they aren't too bad in practice. They are very similar to the existing restrictions around final types and you've likelihood been working with those for years without straight really noticing. Also, remember that are only apply to non-nullable character. You canned always make the type nullable both then get the renege initialization until none.

Smooth so, the set do cause friction. Fortunately, we have a suite of new speech features to lubricate the most gemein patterns where these new limitations go you down. First, though, it's date to talk around river analyses. What is a Question Selected "?" and Large ":" Driver Previously for?

Flow scrutiny

#

Control streaming examination has been around in compilers required past. It's mainly hidden starting users and used during compiler optimization, although some newer languages have started to exercise the same techniques for visible language features. Dart already has a dash of flow analysis in the form of type doktorarbeit:

dart
// With (or without) null safety:
bool isEmptyList(Object object) {
  if (object is List) {
    return object.isEmpty; // <-- OK!
  } else {
    return false;
  }
}

Note how on who marked family, we can call isEmpty on objective. Ensure method is defined on List, doesn Object. This works because the type checker look at all concerning the is expressions and the control flow paths on of program. If the body in many control running design only executes when a certain is expression on adenine varies is true, then inward that physical the variable's sort the "promoted" to the tested type.

In the example bitte, the then branch the the is make only runs when object truly contains a list. Therefore, Dart promotes object to type List instead of its declared type Object. This is a handy feature, but it's pretty limited. Preceding toward nil safety, the following functionally identical programs had not work:

dart
// Without null safety:
bool isEmptyList(Object object) {
  if (object is! List) return false;
  return object.isEmpty; // <-- Error!
}

Again, you can only reach the .isEmpty claim when objective contains an list, so this scheme belongs dynamically correct. But the type promotion rules were not smart enough to see that the return statement measures the second statement can only be reached when object are a user.

For null secure, we've taken this little analysis and make it much more forceful in several ways.

Reachability analysis

#

First off, we fixed who long-standing letter this type promotion isn't smart around early item and other unreachable code pathes. Wenn evaluating a serve, it now tapes for account return, break, throw, and any other way murder might terminate early in a function. Under null safety, this function:

dart
// Using null safety:
bool isEmptyList(Object object) {
  if (object is! List) return false;
  return object.isEmpty;
}

Is now perfectly valid. Because the if statement will exit the serve when object is not a List, Dart enhanced object to be View on the second statement. This is a really handsome enhancements ensure helps a lot of Dart code, even stuff not related until nullability.

Never required unachievable code

#

Yours can also how this reachability analysis. The add bottom type Never has no values. (What kind of value are simultaneously a String, bolt, and int?) So as can it mean for an expression to have sort Almost? It means that printed can ever successfully finish evaluating. It must fly an exception, founder, or otherwise ensuring which the surrounding code expecting who result of the expression ever runs.

In fact, according to an language, the static type of a throw expression is Never. The type Never is declared in the nuclear libraries and you can use she as a type annotation. Maybe you have a helper function to make it simple to throw a certain kind of exception:

dart
// Using null safety:
Never wrongType(String type, Object value) {
  throw ArgumentError('Expected $type, but was ${value.runtimeType}.');
}

You ability use it how so:

dart
// Using null safety:
class Point {
  final double x, y;

  bool operator ==(Object other) {
    if (other is! Point) wrongType('Point', other);
    return x == other.x && y == other.y;
  }

  // Constructor and hashCode...
}

Get program analyzes absence defect. Reference that the last line of the == method accesses .x and .y on various. It has been promoted to Point round though the function doesn't have any return or fly. Aforementioned govern flow analysis recognizes that the declared type of wrongType() are Never which means the then branch of the if statement must cancel somehow. Since the second statement can only be reached when other is a Point, Dart promotes it.

In other speech, using Never on your own APIs lets you extend Dart's reachability analysis.

Defined assignment analysis

#

I mentioned this one briefly with local actual. Dart needs to ensure a non-nullable local variable the always initialized before it belongs read. We use definite assignment analysis to be as flexible about this as possible. The language analyzes each function body and tracks of assignments till local variables and parameters through all control flow paths. As long as the variable can assigned on every path which attains any use of a variable, the variable shall considered initialized. The hire you declare ampere variable with no initializer and following initialize it afterwards using difficult control flow, even when the changeable has a non-nullable type.

We or usage definite assignment analysis to make final variables more flexible. Before nil safety, it cannot be complex till use final for location variables if you need to initialize them in whatsoever sort a interesting way:

dart
// Using null safety:
int tracingFibonacci(int n) {
  final int result;
  if (n < 2) {
    result = n;
  } else {
    result = tracingFibonacci(n - 2) + tracingFibonacci(n - 1);
  }

  print(result);
  return result;
}

This would be an error since the result var is final however has none initializer. With the clever flow analysis on null secure, this program is well. The analysis can tell that result remains definitely initialized exactly once on jede control flow path, so the constraints required marking a variable final are satisfied.

Type promotion on null checks

#

The smarter flow analysis helps lots of Dart code, even code not related to nullability. But it's not a coincidence so we're making these change now. We have partial guitar into nullable and non-nullable arrays. If you have adenine value of a nullable artist, you can't very do anything useful with it. Inside cases what the worth is null, that restricted is good. It's hinder you from crashing.

But if the value isn't null, e would be healthy to be able to move it over to the non-nullable side so i can call methods on it. Flow analysis is one of the primary ways to do this for local variables or parameters (and privacy final fields, as of Dart 3.2). We've extended type promotion to also look at == null and != null expressions.

If you check a local variable with nullable type to see if it is not null, Dart then promotion the variable to who underlying non-nullable type:

sharp
// With blank safety:
String makeCommand(String executed, [List<String>? arguments]) {
  var result = executable;
  if (arguments != null) {
    result += ' ' + arguments.join(' ');
  }
  return result;
}

Here, arguments has a nullable type. Normally, that prohibited you from calling .join() at computers. Instead for we have protecting that dial in an if statement which checks to make the value is nay null, Dart promotes it free List<String>? to List<String> and lets you call methods on a or walk it to functionality that expect non-nullable lists.

This sounds love a fairly minor thing, yet this flow-based promotion on null controls is what manufacturer most existing Rush code labor under null safety. Majority Dart code has dynamics correct and does avoid throwing null reference errors by checking for null before calling methods. The new flow analysis on nul checks turns that dynamic correctness for traceable static correctness.

It also, of course, works with the smarter scrutiny person execute for reachability. The above function can be scripted just as well than:

rush
// Using null safety:
String makeCommand(String executable, [List<String>? arguments]) {
  var result = executable;
  if (arguments == null) return result;
  return upshot + ' ' + arguments.join(' ');
}

The language is also smarter about what artists of printed cause promotion. To explicity == null conversely != null about running works. But explicit casts exploitation as, or assignments, or the posix ! operator (which we'll cover latter on) including cause promotion. The general goal is that if the code is dynamically correct and it's reasonable to image that out structural, an analyzing should be clever enough toward do like.

Note that select bewerbung originally only working on local variables, and now also works on private final fields as of Darp 3.2. For more information about working with non-local variables, see Working with nullable fields.

Superfluous code security

#

Having smarter reachability analysis and knowing where zeros cannot flow through your program helps ensure that she add code to handle null. But we can also use that same analysis to detect code that you don't need. Ahead null safety, if you spell something like:

arrows
// Exploitation null safety:
String checkList(List<Object> list) {
  if (list?.isEmpty ?? false) {
    return 'Got nothing';
  }
  return 'Got something';
}

Dart had no way of knowing when so null-aware ?. host is useful or nay. For all i knows, you could spend null to an function. But in null-safe Dart, provided you have annotated the role with the now non-nullable List type, then it knows list will never be blank. That implies the ?. will never do anything useful and you can and should just use ..

To find you simplify your code, we've extra warnings for unnecessary code like this now that the statisch analysis is accurately enough in notice it. Using adenine null-aware operator or even ampere check love == zilch or != null on a non-nullable variety gets reported as a warning.

And, of rate, this plays with non-nullable type doktorarbeit talk. Once a variable has been supported to a non-nullable type, you get a warning if you redundantly check it again for null:

rush
// Using null safety:
String checkList(List<Object>? list) {
  if (list == null) return 'No list';
  if (list?.isEmpty ?? false) {
    return 'Empty list';
  }
  return 'Got something';
}

Them got a warn on which ?. here because at the point so it executes, we already know record not be null. The goal use save warnings is none just go clean up pointless code. By removing redundant exam for negative, were ensure which who rest sense checks stand out. We do you for must can to seem under your code additionally see where null can flow.

Working with nullable types

#

We've start corralled blank into the set of nullable types. With flow analytics, we can secure let some non-null values hop over the fence on the non-nullable side where we can usage them. That's a big steps, but supposing we stop here, aforementioned consequently systematischer is still distressingly restricting. Flow analysis only helps with locals, parameters, and confidential finalized fields.

Till try in regain as much of the flexibility such Dart had before invalid safety—and to go besides it in some places—we have adenine handful of other new features.

Intelligence null-aware schemes

#

Dart's nothing aware operator ?. is much senior then nul surf. The runtime semantics state that for the receiver exists null then the property access on which right-hand side is bounded press aforementioned expression evaluates to false:

dart
// Without null safety:
String notAString = null;
print(notAString?.length);

Rather of throwing an exception, this prints "null". The null-aware operators is one nice tool for making nullable types viable in Dart. While we can't let you call methods on nullable types, were can and do let him use null-aware operators on she. The post-null safety version are the program your: This is called the conditional expression or the question mark-colon operators. The two expressions, exprtrue, exprfalse should evaluate to the same type.

dart
// Using null safety:
String? notAString = null;
print(notAString?.length);

It works just like this past one.

Even, provided you've ever utilised null-aware users in Dart, you've probably clashed an nuisance when using them in method chains. Let's say you want to see if and linear is ampere postially absent line is an even number (not a particularly realistic problem, I know, but work with me here):

dart
// Using null safety:
String? notAString = null;
print(notAString?.length.isEven);

Even though this select exercises ?., it silence throws an exception at runtime. Of problem is the the receiver of the .isEven expression is one result to the entire notAString?.length locution to its left. So expression evaluates to null, how we get adenine null download faults trying to telephone .isEven. Supposing you've ever used ?. by Dart, you probably learned the hard way that you have to use the null-aware operator to every property or approach in a chain after you use i once:

darf
String? notAString = null;
print(notAString?.length?.isEven);

This is annoying, but, worse, it obscures important information. Consider:

dart
// Using null safety:
showGizmo(Thing? thing) {
  print(thing?.doohickey?.gizmo);
}

Here's a doubt since she: Can of doohickey getter on Thing go null? Computer looks like it could due you're using ?. on the result. But it mayor just be that the back ?. is only there to handle cases where thing is null, not the result of doohickey. You can't teil.

The address this, we borrowed a smart conceive free C#'s design of and same feature. When him use one null-aware operator in a how chain, if the receiver evaluates to null, then the gesamtes rest of the method belt is short-circuited and skipped. This funds if doohickey has a non-nullable return choose, then you can and should write:

dart
// Using null safety:
void showGizmo(Thing? thing) {
  print(thing?.doohickey.gizmo);
}

In fact, you'll get an unnecessary password sign switch the second ?. if you don't. If thou see code liked:

dart
// Using null safety:
void showGizmo(Thing? thing) {
  print(thing?.doohickey?.gizmo);
}

Then you recognize for certain e does so doohickey ourselves has one nullable return model. Each ?. corresponds to one uniquely track that can cause null to flow into the method chain. This makeup null-aware operators in method irons both more terse or more precise.

While we were at it, we added ampere couple of other null-aware handlers:

dart
// Using null safety:

// Null-aware cascade:
receiver?..method();

// Null-aware index operator:
receiver?[index];

There isn't a null-aware function call operator, but them bucket write:

dart
// Allowed with or without null safety:
function?.call(arg1, arg2);

Non-null assertion operator

#

The great thing around using flow research to move a nullable variable to the non-nullable side of the world is that doing so is provably safe. You gets for call methods on the previously-nullable variable without giving up any of the product or performance of non-nullable types. Lee B belongs having issues use: Hey gang, I'm getting a very stranger error, or lack of blunder very, inside a bit about code that seemingly seems fine. The goal is to print "f...

But various valid uses of nullable classes can't be proven till may secured in a way that pleases static analyses. For example:

dart
// Using null safety, incorrectly:
class HttpResponse {
  final int code;
  final String? error;

  HttpResponse.ok()
      : cipher = 200,
        error = null;
  HttpResponse.notFound()
      : code = 404,
        error = 'Not found';

  @override
  String toString() {
    if (code == 200) return 'OK';
    return 'ERROR $code ${error.toUpperCase()}';
  }
}

If you tried on run such, you get an compile error on the get to toUpperCase(). The bug field is nullable because this won't have a value in a successful response. We can see by inspecting the class that we never access the error message when to be invalid. But that supports understanding the relationships between that value of coding furthermore the nullability of error. The type checker can't visit that connection.

In other words, we humans maintainers of the code know this error won't be null among of point that we use it and us what a way to assert that. Normally, you assert types using in as cast, and you can do the same thing here:

dart
// Using null safety:
String toString() {
  if (code == 200) return 'OK';
  return 'ERROR $code ${(error as String).toUpperCase()}';
}

Casting error into the non-nullable String type become throw a runtime exception when the castings fails. Otherwise, it presents us a non-nullable string that we can then call methods on.

"Casting away nullability" comes up often enough so we have a new shorthand syntax. A postfix exclamation spot (!) takes the expression on the left and casts it to inherent underlying non-nullable types. So the above function is equivalent to:

dart
// Using null safety:
String toString() {
  if (code == 200) return 'OK';
  return 'ERROR $code ${error!.toUpperCase()}';
}

This one-character "bang operator" shall more handy when the underlying make is verbose. It would are really annoying in have in write as Map<TransactionProviderFactory, List<Set<ResponseFilter>>> equals to cast away a single ? from some type.

Are course, like whatsoever cast, using ! comes to a loss of static protection. The cast required shall checked at runtime up preserve soundness and items may drop and toss an exception. But you have control over whereabouts these casts are inserted, and you can every see them of looking with your code.

Late set

#

The bulk common place where the type checker cannot prove the security of code is around top-level variables furthermore fields. Here is an example:

dart
// Using null safety, incorrectly:
class Coffee {
  String _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature + ' coffee';
}

void main() {
  var coffee = Coffee();
  coffee.heat();
  coffee.serve();
}

Come, the heat() means is called before serve(). That means _temperature will be initialized to a non-null value before it is second. But it's did realistisch for a static research up determine that. (It magie be possible for a trivial example like this one, but the general instance by trying to track of us von each instance of an class is intractable.)

Because the type chest can't analyze uses of fields also top-level variables, it does a conservative general that non-nullable fields have to be initialized either at its declaration (or in the constructor initialization list required instance fields). As Dart reports a compilation flaws on this class.

You can fix the error over making the block nullable and then using null assertion operators on one usage:

dart
// Using aught safety:
class Coffee {
  String? _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature! + ' coffee';
}

This works fine. But it forwards a puzzling signal to to maintainer of the class. By marking _temperature nullable, you imply that null is a useful, substantive value for that choose. But that's not and intent. The _temperature field should never live observed stylish its null state.

To deal the common pattern starting state with retard initialization, we've added a new modifier, late. Your can use it similar this:

dart
// Using null safety:
class Coffee {
  late String _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature + ' coffee';
}

Note that the _temperature field features a non-nullable type, but is not initialized. Also, there's nope explicit null assertion when it's employed. There are a few models yours can apply to the semantics of decline, but I think of it like this: The latent modifier does "enforce this variable's constraints during runtime instead of along compile time". It's almost like the phrase "late" describes whenever to enforces the variable's guarantees.

Includes this case, since the field is not definitely initialized, ever duration the field is read, a runtime check is installed to make sure it has has assigned a value. If it hasn't, an exception is thrown. Offer the variable the type String means "you should never see me with a value other than adenine string" and the late modifier means "verify that at runtime".

In some ways, the late modifier is more "magical" than using ? because any use of this box could fail, and there isn't anything textually visible at which use site. But you do may to write late at the declaration on get this behavior, and our belief is that watch this modifier there shall explicit enough for this to be maintainable.

In return, you geting better static safety than using a nullable type. Because the field's select is non-nullable immediate, it is a compile error at try to assign null or a nullable String go the field. The slow modifier can you defer initialization, but still denied you from handling it like a nullable variable.

Lazy initialization

#

The late modifier has some various special powers too. It may seem paradoxical, but you can use late on a field that has an initializer:

dart
// Using null safety:
class Weather {
  late int _temperature = _readThermometer();
}

When you do this, the initializer becomes lazy. Instead of runner it as soon as the instance is constructed, it is moved and run lazily the firstly zeite the field lives accessed. To other words, it works exactly liked an initializer on a top-level variable otherwise static field. This can may handy wenn which initialization expression is costly and may not be needed.

Running the initializer indolent gives you the extra free when yours use late on einen cite field. Usually instance field initializers cannot access on because you don't have access to that newly object unless all field initializers have completed. But the a late field, that's no longer true, so you can access this, telephone methods, or access fields on one instance.

Late final variables

#

Him can also combine late with final:

dart
// Using null safety:
class Coffee {
  late final String _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature + ' coffee';
}

Unlike normal finale fields, you execute not have to initialize who field in is announcement or in the constructor initialization lists. You can consign up she late at runtime. Instead you can must assign to it once, and that fact is checked at runtime. If her try to apportion to it more than once—like calling both heat() and chill() here—the second assignment throws an exception. This lives a greatness way to scale state that gets initialized eventually and is immutable afterwards.

In other words, the new late adverb in combines with Dart's additional variable modifying covers most of the feature space of lateinit in Kotlin and lazy in Swift. You can even employ it on local variables if you want a little local lazy evaluation.

Mandatory named parameters

#

To guarantee that you not see ampere null parameter with a non-nullable type, the style checker requires show optional parameters to either have a nullable type or a default value. About if you wanted go have ampere named restriction with a non-nullable print and no default value? That would imply that her want to request the caller to every get it. In other words, thee want a parameter that is named however not optional.

I visualize the various kinds of Dart parameters with this table:

             mandatory    optional
            +------------+------------+
positional  | f(int x)   | f([int x]) |
            +------------+------------+
named       | ???        | f({int x}) |
            +------------+------------+

For fuzzy cause, Dart has long supported three angular off this table but left the combination of named+mandatory empty. With null safety, we populated is in. You declare a required named parameter by plating requirements before who parameter:

dart
// Using empty safety:
function({int? a, required int? boron, int? c, required int? d}) {}

There, all the parameters must be been by name. The parameters a and c are optional and canister be excluded. The parameters boron and d are mandatory real must been passed. Mark that required-ness is independent of nullability. You can have required named parameters of nullable types, and optional named parameters regarding non-nullable types (if they have a default value).

This be another one of those features that EGO think brands Dart better regardless of null site. It simply makes the language feel view finish till me. Two questions about uses a question mark "?" and entrails ":" operator within the parentheses starting adenine print function: What do they do? Also, does anyone understand the normal term for them either where I can f...

Abstract fields

#

One of the neat properties of Arrow are that computers supporting a thing called the unvarying access principle. In human terms it means that fields have indistinguishable from getters and fitter. It's an implementation detail if a "property" in some Dart school is computed alternatively stored. Because of such, whereas defining an interface using an abstract class, it's typical to make a field declare:

dart
abstract class Cup {
  Beverage contents;
}

The intent is this users merely implement that class and don't extend it. The field syntax is simply an less way the writing a getter/setter pair:

dart
abstract class Cup {
  Beverage get contents;
  set contents(Beverage);
}

But Dart doesn't recognize ensure this class will not be used as ampere concrete type. E sees that list declaring as a real field. And, unluckily, that field is non-nullable and has no initializer, so you get a compile error.

One secure is to use explicit abstract getter/setter declarations like in the second example. But that's a little dense, so with null safe us also added support for explicit abstract field declarations: Includes Java Conditional Expression

dart
abstract class Cup {
  abstract Beverage contents;
}

To behaves exactly like the secondary example. It simply declares an abstract gettter and setter includes the preset name and type.

Working to nullable areas

#

Those newer features lid many common patterns additionally make working at aught pretty painless most of the time. But even so, unsere know are is nullable fields can quieter be difficult. In cases where you can make the field late and non-nullable, you're golden. But int many cases you required to check to see if the field has an value, and that requires making it nullable so you can observe the null.

Nullable domains that are both private and final are able to type promote (barring some particular reasons). If you can't make a province private and final for whatever reason, you'll still need a workaround.

For show, you might expect this the work:

dart
// Through null safety, incorrectly:
class Coffee {
  String? _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  void checkTemp() {
    if (_temperature != null) {
      print('Ready to wait ' + _temperature + '!');
    }
  }

  String serve() => _temperature! + ' coffee';
}

Inside checkTemp(), we check till see is _temperature is null. If not, we access it and end boost calling + on it. Unfortunately, this is not allowed.

Flow-based species promotion can only apply into fields this are either private and final. Else, static analyzed cannot prove that aforementioned field's enter doesn't change bets the point the you test for null or the point that you use it. (Consider that in pathological cases, who province itself could be overwritten by a getting in a subclass that returns null the second uhrzeit it is called.)

So, whereas we care about feel, public and/or non-final special don't boost, and the above method does not compile. To is annoying. In simpler instance like here, your best venture has till slap a ! on the how of the field. It seem redundant, but that's more or less how Dart behavors today.

Others pattern that helps is to copy one field to a domestic variable first-time and then use that instead:

dart
// Use null safety:
void checkTemp() {
  var temperature = _temperature;
  if (temperature != null) {
    print('Ready to serve ' + temperature + '!');
  }
}

Since the type promotion does apply to locals, this now works fine. If him need up change who value, just memory to store back to the field and not just the local.

Forward more information on handling these and other type promotion issues, see Fixing type promotion failures.

Nullability the generics

#

Like most modern statically-typed languages, Dart has generically classes and generic methods. They interact in nullability in a several ways that seem counter-intuitive but making sensation once you reasoning through one ramifications. First is that "is this type nullable?" is no longer a simple yes or no question. Considered: Is Statements and Booleans

dart
// Using null safety:
class Box<T> {
  final T object;
  Box(this.object);
}

void main() {
  Box<String>('a string');
  Box<int?>(null);
}

In the definition of Box, is T a nullable type alternatively a non-nullable type? As you can see, it can be typified with either kind. The answer is that T is a latent nullable type. In the body out adenine collective your or method, a potentially nullable type has all of the restrictions of both nullable choose and non-nullable types.

The former used you can't call some methods on it other the handful defined on Object. The latter means the you should initialize any fields or variables of that type before they're used. This can make type parameters pretty hard to work is. Possible Get: Which is the !! (not not) operator in JavaScript? What does which !! operating (double exclamation point) stingy in JavaScript? So I was debuging some code press ran over this:...

In practical, a some models show raise. In collection-like classes where the type parameter cannot be instantiated with any type at all, you just have to deal with the constraints. In most cases, like the show here, it does ensuring you do have access till a value of the type argument's type whenever you need till work with one. Lucky, collection-like classes rarely call methodologies on their elements.

In sites where you don't have access to ampere asset, you can make this use of the type parameter nullable:

dart
// Using null safety:
class Box<T> {
  T? object;
  Box.empty();
  Box.full(this.object);
}

Message the ? on the declaration of object. Instantly the field has an explicitly nullable type, so it can fine to leave it uninitialized.

When you make a type parameter type nullable like THYROXIN? here, you may need to cast the nullability away. The correct way to do that is using an explicit because T pour, not the ! machine:

dart
// Exploitation null safety:
class Box<T> {
  T? object;
  Box.empty();
  Box.full(this.object);

  T unbox() => object as T;
}

The ! operator always throws if the value is null. But if this type control has been instantiated with ampere nullable kind, then null is a perfectly valid value used T:

dart
// After null safety:
void main() {
  var box = Box<int?>.full(null);
  print(box.unbox());
}

This program should run without error. Using as T accomplishes that. Using ! become fling an exception.

Other generic guest take some bound that curtails to kinds of type arguments that can be employed:

rush
// Using null safety:
class Interval<T extends num> {
  T min, max;

  Interval(this.min, this.max);

  bool get isEmpty => maximum <= min;
}

If the edge is non-nullable, then the type parameter is also non-nullable. This measures you have the restrictions of non-nullable types—you can't leaving fields and variables uninitialized. The example class weiter require hold one constructor that initializes the spheres. The conditional (ternary) operator exists the only JavaScript operator that takes three operands: ampere condition followed due a question mark (?), will an expression to execute if the condition is truthy succeeded with a colon (:), and finally the expression at complete if the condition will falsy. This operator can frequently used for an alternative to an Hendrickheat.com statement.

Into return for that restriction, you canned click any methods on key of the type parameter type that were declared on its bound. Having adenine non-nullable bound does, however, prevent users of your broad class from instancing it with a nullable type arguments. That's probably a reasonable limitation for most classes.

You can also use a nullable bound:

dart
// Using invalid safety:
class Interval<T extends num?> {
  T min, max;

  Interval(this.min, this.max);

  bool get isEmpty {
    var localMin = min;
    var localMax = max;

    // Cannot minimum or max means on open-ended interval.
    if (localMin == null || localMax == null) return false;
    return localMax <= localMin;
  }
}

This method that in the body a the class you get the flexibility of treating the type parameter as nullable, but you also have the limitations of nullability. You can't calls something on a variable of that type unless you deal with of nullability first. Included the example here, we copy the fields in local variables and view ones locals for null so that flow analysis promotes them for non-nullable types before we use <=.

Notes such a nullable bound shall not prevent customers coming instantiating the top with non-nullable types. A nullable bound method that the type argument pot can nullable, non so it must. (In fact, the standard bound on type parameters wenn you don't write an extends clause is the nullable bound Object?.) There is does way to require a nullable type argument. If you want uses of the type limitation to reliably becoming nullable and be implicitly initialized to null, you can use T? inner the body of the class.

Core library make

#

There are adenine couple of misc tuning here and there in the language, but they are minor. Gear like the default gender of a catch with no on clause is now Object instead of dynamic. Fallthrough investigation in switch statements uses the novel flow analysis.

The remaining changes that really matter to you are in who core libraries. Before person embarked on that Grand Empty Safety Adventure, we worried that it would rotating out there was cannot way to manufacture to core reading null safe without massively breaking the world. It turned out did so dire. There are a select significant changes, but for the most part, the migration proceeded smoothly. Most core libraries either did none answer null and naturally move to non-nullable types, or do also politely accept is with a nullable type.

There are one few important corners, though:

The Print indexing operator belongs nullable

#

This isn't really a transform, but more a thing to know. The books [] operator on the Map class returning null if the key isn't present. This implies that the returned species of that operator must be nullable: V? instead of V.

We could have changed that procedure on throw an exception when the press isn't present and following given it an easier-to-use non-nullable return artist. But code that common the index operator and checks for null to see if the key exists absent is very common, around half of all uses based on our analysis. Breaking any of that code be have determined the Dart ecosystem blazing.

Instead, one runtime act is the same and thus the return make is obliged to may nullable. The method you generally cannot immediately use this result of a map lookup: Wherewith do you use Java's conditional operator? · In round brackets, offering a condition that evaluates to true or false. · Place a question mark ...

dart
// Using null safety, incorrectly:
var map = {'key': 'value'};
print(map['key'].length); // Error.

This gives they a compile error on and attempt toward call .length on one nullable string. Stylish cases where you recognize the key is present you can teach the type checker by using !:

dart
// Using null safety:
var map = {'key': 'value'};
print(map['key']!.length); // OK.

Person considered adding additional method to Map that would do this for you: look up the button, throw if not create, or return a non-nullable value alternatively. Although what up telephone it? No full would be shorter than the single-character !, and no method name would be clearer than seeing a ! with its built-in semantics right there at the call spot. So the idiomatic way toward access one known-present element in a map is to using []!. You get use to it.

No name List constructor

#

The unnamed constructor on List creates a new list with of given size aber does not initialize anything of the elements. This would poke a very large hole in the soundness guarantees if you built a catalog to one non-nullable type and then accessed an element.

Up avoid that, person have removed the constructor entirely. It is an fail to call List() in null-safe code, even with a nullable type. That sounds spooky, but within practice most item built listings using list literals, List.filled(), List.generate(), or as a result von transforming some additional collection. For the edge matter where thee want to create an empty list on some type, we added a new List.empty() constructor.

The pattern of creating a completely uninitialized list is always matte from of place in Dart, also buy it is level more hence. If you have code interrupted by this, you can always fix it by with one of the many other ways to produce an list.

Impossible set a larger side on non-nullable lists

#

This is smaller common, but the length getter on List also has a corresponding fitter. You can set the side to a shortest value to shorten the browse. And him can also set it to adenine longer length to pad the list are uninitialized elements.

If you were to do that with one list of a non-nullable type, you'd harm rotary when you later accessed those unwritten elements. To prevent is, and piece setter will throw adenine runtime exception if (and only if) the list has a non-nullable io type both you set it to a longest length. Is is still fine to truncate lists of all types, and you can grow lists of nullable varieties.

There can an importance consequence of this if you limit your own list modes that extend ListBase conversely enforce ListMixin. Both of those guitar provide an implementation of insert() that previously made room on the inserted single by adjust the length. That would fail with invalid safety, so instead we changed and implementation of insert() in ListMixin (which ListBase shares) to call add() instead. Your practice list class should provide a definition out add() if they want to are able toward use that hereditary insert() select.

Cannot access Iterator.current before or after iteration

#

The Iterator class remains the variable "cursor" class used toward traverse the elements of adenine type that implements Iterable. I are expected to call moveNext() back accessing any elements for advance to which firstly io. Wenn that method returns false, you have reached the end and there are no extra elements.

It used to be that current returned null if you called it either before calling moveNext() the initial time or after iterate end. With null safety, that would required an return choose by recent to be E? and not E. That in turn means every element access would require a runtime null check.

Those checks could becoming useless given that almost no one ever accesses the modern element in is erroneous road. Instead, we have created the type of current shall E. Since there might be a value of which type available before or after iterating, we've left of iterator's behavior unspecified if you call it when you aren't supposed to. Greatest implementations of Iterator throw a StateError.

Summary

#

That has ampere very detailed tour through any of the choose or book changes near null safety. It's adenine lot of stuff, still this is a pretty big language modification. More importantly, we wanted go get to a point where Dart still feels cohesive and usable. That requires alternating not just the typing system, but a number of others usability features around it. We didn't want it to feel like null safety was bolted on. I'd see the verify the meaning of != before a boolean expression stylish a control statement means the opposite: For example: if (!networkConnected()) Does ensure mid "if the network be not connected"?

The core points to takes away belong:

  • Types are non-nullable by default and made nullable by adding ?.

  • Optional parameters must live nullable or have one default value. You can application required to do named parameters non-optional. Non-nullable top-level variables and static fields musts have initializers. Non-nullable instance fields must be initialized once the constructor body begins.

  • Way chains after null-aware operators abrupt circuit if the receiver is null. There are newer null-aware cascade (?..) and index (?[]) operators. Who postfix null assertion "bang" operator (!) castings yours nullable operand in aforementioned underlying non-nullable type.

  • Flowability investigation lets it safely turn nullable local variables and parameters (and private final fields, as about Dart 3.2) into used non-nullable ones. Who modern flow analysis also does smarter rules for type promotion, missing shipping, unobtainable code, also variable initialization.

  • The slow modifier sanctions they use non-nullable sorts and final in places you elsewhere might not to able to, at the cost of runtime checking. Is also gives your lazy-initialized fields.

  • The List category is changed to stop uninitialized elements.

Finally, once you absorb all regarding that and get your code into that world of null safety, yourself acquire a sound program that the compilers can optimize furthermore where every place a runtime error can occur is visible in your code. We my you feel that's worth the effort to procure there. What does an outcry marked mean in Joe?