Null safety in Dart

Hello, Habr! I present to your attention the translation of the article "Announcing sound null safety" by Filip Hracek with my comments:



Null safety - safe work with empty links. Further in the text, for brevity and due to the stability of the term, the English name null, null safety will be used. And the translation "zero security" leads to completely opposite thoughts.

sound - in this context (sound null safety) can be translated as "reliable".

If you have suggestions for improving the translation or found errors - write in a personal, we will try to fix it.
An important step has come for the Dart team with their presentation of a technical preview of the null safety developments. Null safety avoids a class of bugs that are often difficult to detect, and as a bonus it provides a number of performance improvements. At the moment, we have released a preliminary technical preview and are waiting for your feedback.



In this article, we will reveal the plans of the Dart team to deploy null safety, as well as explain what is behind the term Sound null safety, and how this approach differs from other programming languages.

The described version was presented on June 10, 2020.

Why null safety?



Dart is a type-safe language. This means that when you get a variable of some type, the compiler can guarantee that it belongs to it. But type safety alone does not guarantee that a variable is not null.



Null errors are common. A search on GitHub finds thousands of reports (issues) caused by null values ​​in Dart code, and even more commits trying to solve these problems.



Try to spot the nulling link issue in the following example:



void printLengths(List<File> files) {
  for (var file in files) {
    print(file.lengthSync());
  }
}


This function will certainly fail if called with a zeroed parameter, but there is a second case to consider:



void main() {
  // Error case 1: passing a null to files.
  printLengths(null);
 
  // Error case 2: passing list of files, containing a null item.
  printLengths([File('filename1'), File('filename2'), null]);
}


Null safety fixes this problem:



image



With null safety, you can rely on your code with more confidence. There will be no more annoying errors of accessing a nullified variable at runtime. Only static errors at compilation time.

To be completely honest, the current implementation still leaves several opportunities to catch null errors at the time of execution, more on them later.

Sound (reliable) null safety



Dart's implementation of null safety is sound. If we analyze it using the above example, it means that the Dart compiler is 100% sure that the array of files and the elements in it cannot be null. When the Dart compiler analyzes your code and determines that the variable is not null, then this variable will always have a value: if you check your executable code in the debugger, you will see that there is simply no possibility of zeroing at runtime. There are non-"reliable" implementations in which you still need to perform checks for the existence of a value at runtime. Unlike other languages, Dart shares implementation reliability with Swift.

, , , null safety . Swift, Kotlin Dart. .
This robust implementation of null safety in Dart has another nice consequence: it means your programs can be smaller and faster. Since Dart really makes sure that variables can never be nullified, Dart can optimize the compilation result. For example, the AOT compiler can generate smaller and faster native code because it doesn't need to add checks for empty references.



We have seen some very promising preliminary results. For example, we saw a 19% performance improvement in a microbenchmark that emulates typical rendering patterns in the Flutter framework.



Basic principles



Before proceeding with the detailed design of null safety, the Dart team defined three basic principles:



Non-nullability by default. / ** This can often be seen as an abbreviation for NNBD in the documentation ** / If you don't explicitly tell Dart that a variable can be nullified, it will consider it non-nullable. We chose this as the default because we found that in the API, a nonzero value is by far the most common. / ** This is probably a rework of the current Flutter API ** / .



Phased applicability... We understand that there must be a possibility of a gradual transition to null safety step by step. In fact, you need to have nullable and null safety code in the same project. For this, we plan to provide tools to help with code migration.



Complete reliability (sound). As mentioned above, null safety in Dart is safe. Once you convert your entire project and your dependencies to null safety, you get all the reliability benefits.



Declaring variables with null safety



The basic syntax is simple enough. Below is an example of declaring various variables. Note that non-nullable variables are used by default, so they look the same, but their value cannot be nullified.



// In null-safe Dart, none of these can ever be null.
var i = 42;
final b = Foo();
String m = '';


Dart will make sure that you never assign null to any of the above variables. If you try to execute i = null even a thousand lines later, you will get a static analysis error and red squiggly lines - your program will refuse to compile.



If you want your variable to be nullable, you can use '?' like this:



// These are all nullable variables.
int? j = 1;  // Can be null later.
final Foo? c = getFoo();  // Maybe the function returns null.
String? n;  // Is null at first. Can be null at any later time, too


The variables listed above behave exactly the same as all the variables in the current version of Dart.



'?' 'can also be used in other places:



// In function parameters.
void boogie(int? count) {
  // It's possible that count is null.
}
// In function return values.
Foo? getFoo() {
  // Can return null instead of Foo.
}
// Also: generics, typedefs, type checks, etc.
// And any combination of the above.


But again, I wish you almost never have to use '?'. The vast majority of your variables will be non-nullable.



Making it easier to use null safety



The Dart team is working hard to make null safety as easy to use as possible. For example, take a look at this code, which uses if to test for null:



void honk(int? loudness) {
  if (loudness == null) {
    // No loudness specified, notify the developer
    // with maximum loudness.
    _playSound('error.wav', volume: 11);
    return;
  }
  // Loudness is non-null, let's just clamp it to acceptable levels.
  _playSound('honk.wav', volume: loudness.clamp(0, 11));
}


Note that Dart is smart enough to realize that loudness cannot be null by the time we pass the if statement. And so Dart allows us to call the clamp () method without unnecessary dancing with a tambourine. This convenience is provided by so-called flow analysis: the Dart parser looks at your code as if it were running it, automatically figuring out more information about your code.

Flow analysis, Dart, , , . null safety, :
foo(dynamic str) {
  if (str is String) {
    // dynamic   length,   
    //      String
    print(str.length);  
  }
}


, Dart , null, :
int sign(int x) {
  // The result is non-nullable.
  int result;
  if (x >= 0) {
    result = 1;
  } else {
    result = -1;
  }
  // By this point, Dart knows the result cannot be null.
  return result;
}


- (, result = -1;), Dart , result β€” , .
Flow analysis only works inside functions. If you have a global variable or class field, then Dart cannot guarantee that a value will be assigned to it. Dart cannot model the flow of execution of your entire application. For this reason, you can use the new late keyword if you know that the variable will be initialized when it is first accessed, but you cannot initialize it when it is declared.



class Goo {
  late Viscosity v;
 
  Goo(Material m) {
    v = m.computeViscosity();
  }
}


Note that v cannot be zeroed out, although initially it is irrelevant. Dart thinks that you will not try to read v until it is assigned a nonzero value and your code compiles without errors.

β€” .



, . , . Dart , , , , , Kotlin .



β€” Swift- , , force unwrap Dart.

void main() {
  String? t;
  print(t!.length);
}


( late β€˜!’) .



late , - Dart. β€˜required’ . β€˜@required’, .
class Temp {
  String str;
  Temp({required this.str});
  
  //   
  Temp.alt({strAtr}) : this.str = strAtr;
}




The Dart team has been working for over a year to bring null safety to a technical preview. This is the biggest language change since the release of the second version. However, this is a change that does not break backward compatibility. Existing code can call code with null safety and vice versa. Even after a full release, null safety will be an additional option that you can use when you're ready. Your existing code will continue to work unchanged.



The core Dart libraries were recently updated with null safety. As an illustrative example of backward compatibility, the replacement of existing core libraries went without a single failed test and without errors in test applications running on Dart and Flutter test environments. Even updating the core libraries for many of Google's internal clients went off without a hitch. We plan to redesign all our packages and applications to use null safety after release, we hope you will do the same. But you can do it at your own pace, batch by batch, app by app.

These words, yes to Swift developers, especially the 3rd version ...

But even here everything is not so rosy, the developers themselves say that when combining null safety code and "old" code in one project, they cannot guarantee soundness type systems.

Further action plan



We plan to roll out null safety gradually in three phases:



  1. Technical preview. It was launched when the original article was published (06/10/2020) and is available on the dev-branch. It is worth paying attention to the section "Start now". It is still very unstable and subject to change, so we do not recommend using it in production code yet. But we'd love to hear from you and give us feedback!
  2. Beta version. Null safety will become available on the Dart beta branch and will no longer hide behind an experimental flag. The implementation will be close to the expected final version. It will be possible to start migrating your packages and plugins to pub.dev, but it is not recommended to publish this change as a stable release yet.
  3. Stable version. Null safety will be available to everyone. You will be prompted to publish your updated packages and plugins as stable versions. It is also worth migrating your applications at this stage.


If all goes according to plan, we will release a stable version of Dart with null safety by the end of the year. From time to time we will add tools to help you move to null safety. Among them:



  • A migration tool to help automate the many steps of updating existing packages and applications;
  • pub.dev, null safety;
  • 'pub outdated' , null safety.




The fastest way to try null safety today is with nullsafety.dartpad.dev - the version of DartPad with null safety enabled. Open the Learn with Snippets drop-down list to find a series of tutorials covering the new syntax and the basics of null safety.



image



You can also experiment with null safety in small console applications (we haven't updated larger frameworks like Flutter yet). First, you need to download the Dart SDK from the dev branch, then you can download this example console application . The README file contains instructions for running the application with the experimental null safety feature enabled. The other files in the example provide run configurations that will enable debugging in VS Code and Android Studio.



You can also read the documentation (more will appear in the future):





UPD: In the comments, I suggested this link Understanding null safety


We are very excited to be able to implement null safety in Dart. Reliable, secure handling of empty links will become the hallmark of Dart to help you write the most reliable and productive code. We hope you take some time to experiment with the current version of null safety and leave your feedback in our bug tracker. Have a nice code!

Thanks for reading to the end. The translation was a bit late, but hopefully the comments have been useful to the material and it hasn't become just a one-to-one translation. I want to add that this feature is a really useful step forward for the entire Flutter ecosystem. I'm looking forward to using it already on live apps. In the meantime, as they say in foreign language, stay tuned!



All Articles