Darts’ Null Safety

Syed iMujtaba Nazki
16 min readOct 19, 2020

--

Photo by: Isra Itrat Rafiqi

Did you get the news? Yes, Darts’ Null-Safety … That’s what I am talking about! Recently, it has created a lot of buzz, despite the fact that other languages like Swift, Kotlin, etc. already have their own version of it. But, now, finally, we see it coming to Dart.

Am I happy? … you bet!! Should you be? … hell ya!!

Null Safety is the single most largest addition to Dart language since Dart 2.0. Though, at the point of this writing, it is still in early Tech Preview, even so, it will soon inevitably be the future of Dart Language. So, perhaps now, is a good time to get acquainted with Darts’ Null Safety feature.

Note: It is important to have an idea of the following concepts and terms in order to understand why Dart is adding the Null Safety feature. Skip them, if you are already familiar with them. Otherwise, you’re welcome!

I’m a novice, so what is Compile Time versus Runtime?

Technically, the compile time refers to the time when the source code (the code that you write as a programmer) is converted into an executable code (the code that is ready to be run) and the runtime refers to the time when the executable code starts running.

In simple terms, we can say that the compile time is the time when you are writing the code and runtime is the the time when the code you’ve written is being executed.

Got it, but what about Compile Time Errors versus Runtime Errors?

Compile-time errors are the errors that occur while we are writing the code. For example, remember those red squiggly lines or messages, when you write the wrong syntax. These are actually your complier telling you “excuse me, something, somewhere is wrong!”. It even helps you spot these mistakes. In such cases, the compiler will keep complaining and is not going to allow to run the program until all the errors are removed from the program. However, once you remove all the errors, the complier stops whining and generates the executable file, that can then be run.

In a way, if you think about it, as programmers, it makes our lives way easier. We are able to catch these errors early on and deal with them even before our program begins to run. A delightful user experience, Indeed!

In contrast, the runtime errors are the errors that occur during the execution of a program i.e. after compilation. These errors are not easy to detect as the compiler does not point to these errors, at all. It is simply a bad user experience. I mean, just think about it, you put in a lot and lot of effort into creating your program, debugging it, making it tidy enough, and when its the time for the user to run it, bam! he gets a runtime error. What an unpleasant experience!

As a programmer, it is your responsibility to minimize the runtime errors as much as possible, and a programming language which provides a compiler that strives to be smart enough to detect most of these errors at compile time, is an ideal candidate for you to choose it as your developing platform. Dart, over the years, is certainly living up to this benchmark.

Hmm, not sure about Statically Typed versus Dynamically Typed Languages, what are they?

A language is statically typed if the data-type of a variable is known at compile time. Typically, a statically typed language either requires you to explicitly specify the data-type of each variable or may offer type inference, i.e. the compiler automatically deduces the data-type of a variable, at compile time.

On the contrary, a language is dynamically typed, if you as a programmer do not need to specify the data-type of the variable, instead, it is deduced automatically at runtime, not at compile time¹. Which means, if the interpreter fails to infer the type of a variable at runtime, you’re screwed!

Thus, the main advantage of a statically typed language is that all kinds of checks can be performed by the compiler at compile time, hence, a lot of trivial bugs are caught very early, therefore, minimizing the runtime errors. While as, it may save you sometime and effort by not specifying the data-type of a variable in a dynamically typed language, but dealing with the runtime errors can quickly become a nightmare.

Again, why catch errors at Compile Time?

A lot goes into designing and creating a program. Even the smallest of all programs is an effort of days, weeks or years of hard work. You want your programs to be flawless and provide a delightful user experience, which is only possible, if you strive to tackle most of the common errors beforehand. But, we are only humans, and it’s almost practically impossible to figure out every error on our own without some help, if you may agree! This is exactly where a good programming language equipped with a complier capable enough to catch as many common errors as possible at compile time, comes to our rescue. Dart, over the years, in this and many other areas, has certainly proved it’s worth.

Anyways, what is a null value, after all?

null simply means “nothing”, or we can say it specifies the “absence of a value”. In other words, null may also be thought of as a placeholder to denote a value that is missing, or simply the value that we don’t know. You may also like to think of it as a value which is “undefined”.

In Dart, as per the API documentation, null is a reserved word, which denotes an object that is the sole instance of the Null class. Since, it’s an object, just like any other object in Dart, it too has certain properties, methods, etc. One of its most common method is the noSuchMethod(), which is invoked if you try to access a property or a method that does not exist.

So, remind me, why is null value a problem?

I feel so overwhelmed to quote the British computer scientist Mr. Tony Hoare, who is most famous for his Quicksort algorithm and is also the winner of Turning Prize (the Nobel Prize in Computing). Speaking at a software conference in 2009, he apologized for inventing the null references:

“I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years”

So, again, why is it a problem? Because null is a value that is not a value. Haha, OK! Let me explain it with the help of an example. Consider the following code: —

A typical example of a null problem

In the above code snippet, we have defined an isEmpty() function, which takes a String argument. In Dart, as you may already know, the objects of the String class have a length property, which returns the number of characters in a given string or you can say the length of the string. Now, since the function expects a String object, in the body the isEmpty() function we access its length property and compare it with 0, which tells us whether the string is empty or not. However, when we actually call the isEmpty() function inside the main() function, we supply a null value and not a String value. The isEmpty() function takes the null value and tries to access its length property, which it does not have. Therefore, it throws the following error at runtime and gives us no clue at compile time, i.e. we get the error when we try to run the program and not when we are writing the program: —

Error, because null doesn’t have a length property

We can avoid the above error by including a null-check in the isEmpty() function, as: —

Added the null-check

After adding the null-check, we now get a false value from the isEmpty() function, if we try to pump it with a null value. This takes care of the error, but along the way we also included a lot of code in the isEmpty() function that except for the null-check only requires a single line of code, i.e. return name.length == 0.

Let’s take a step back and think about it for a moment, although there is nothing wrong with the null-check and in some situations it may still be the best solution. However, in this case, returning a false value if a null value is provided to the isEmpty() function is completely meaningless and may eventually yield incorrect results in certain cases, especially, when we begin to use the isEmpty() function in more complex situations, where the result is dependent on the output from the isEmpty() function.

Furthermore, if we don’t put in the null-check, we get a runtime error, which is a hit on the head. The compiler didn’t inform us that we were providing a null value to the isEmpty() function which is expecting a String value, because it had no way to do so without the null-check, and the program eventually crashed during the runtime, because the length property was accessed on a null object, which the null object does not have.

Keeping the above discussion in mind, don’t you think, it would have been so much better, if the Dart compiler had informed us while we were trying to provide a null value to a function that was expecting a String value, and wouldn’t have allowed us to compile and run the app, unless we supplied it with a String value, instead? If that makes sense to you, please allow me to introduce you to the Darts’ Null Safety feature!

Ahh, that makes sense, so how is Dart handling it?

Non-nullable is the default, when you opt-in for the Darts’ Null-Safety feature

When you opt in for Darts’ null safety, all variables by default are non-nullable, unless you explicitly tell Dart that a variable is null. Making all the variables as non-nullable by default ensures that you do not get any null reference errors at runtime. When all the variables are non-nullable by default, Dart catches all the null errors at compile time instead of reporting them at runtime, because runtime errors suck!

I hope you’re able to get the picture, which simply means, that all the variables must be initialized to a value of their type as soon as you declare them.

Nevertheless, if we humans were only perfect, and didn’t forget to initialize a variable to a definite² value in compliance with its type as soon as we declared it, or there weren’t situations where we knew the value of every variable in advance, of course then, Dart didn’t have to go to awful lot of trouble of making all variables as non-nullable by default.

Caveat: Please be aware, that it is still possible to get null reference runtime errors, if you explicitly tell Dart that some of the variables are going to be null.

Alright, so how can I test out the Null-Safety feature in Dart?

Earlier I said, “ when you opt in”, that’s because at the moment i.e. until the Dart’s Null-Safety feature is in Tech Preview, there are only two ways you can test out the Darts’ Null-Safety feature: —

  • You can either configure your development environment by using the Tech Preview SDK, or
  • You can use the DartPad with null safety.

I personally prefer and recommend the DartPad with null safety as a good option until the Null Safety reaches a stable release. This way, you get to test out the null safety feature in “DartPad — with null safety”, get yourself ready for the future, and provide feedback, if any, to the Dart team, without messing up your development environment. Regardless of what approach you use, do not use the Darts’ Null-Safety feature in your production code, since it’s still in Tech Preview. Wait until it’s stable!

Darts’ Null-Safety feature is not yet ready to be used in the Production Code

Great, I got it! Tell me more about the benefits of Darts’ Null-Safety…

The null safety in Dart helps you to avoid a class of bugs that are otherwise often very hard to spot, and as an added bonus it enables a range of performance improvements. I mean think about it, before the Darts’ Null Safety feature the Dart Compiler had to throw in additional null-checks at runtime. However, now since Dart knows in advance that the variables cannot be null, it doesn’t need to add those null-checks at runtime, therefore it can optimize. This means with Darts’ Null-Safety, your programs are going to be smaller and faster.

In other words, we can say, by eliminating the unnecessary null-checks at runtime using null-safety, Dart complier can generate smaller runtime code, therefore, reducing the size of your application. Also, since the Dart compiler by using the null-safety feature does not need to verify whether a receiver is non-null before calling methods on it, the resulting code is faster, and so is your application. Furthermore, a microbenchmark which emulated a typical framework rendering patterns was able to report a 19% performance improvement, by using null safety feature.

Hmm, okay okay! So, do we also get to keep the nullable types?

Darts’ Null-Safety is the answer to avoid as many null-reference errors as possible at runtime and instead catch and provide feedback for these errors at compile-time. However, there are times when you would still want to use the the null value and it may also be necessary in certain situations, for these reasons, don’t worry the null type is not going away! In fact, the Dart team has strived hard enough to maintain backward compatibility. This means your existing code can still call into the code that uses null-safety, and vice versa.

Put differently, way can say, that it is possible for a program to contain both the legacy code and the code that uses the null-safety feature, wherein, the legacy code can call into the code that uses null-safety, and vice versa. This is what Dart calls as the “Mixed Mode”. However, there is one important thing to remember here; Dart will only guarantee complete compile time null-reference error feedback, if your code is fully null-safe. But, in the mixed-mode, as expected, the null-reference errors may still occur at runtime. It is only when your application is fully null safe, you can expect complete compile time null-reference error feedback.

In mixed mode, null-reference errors may still occur at runtime

Cool, but can you at least tell me one situation, where I would still need the nullable type?

There are many situations, where we may still need to use the null type. For example, when you don’t exactly know the value of a variable beforehand and you may be expecting to get that value later on during the course of your applications’ run. Other times, the value of certain variables are simply not known until the runtime. In those cases, and many other, null is still a valid value. But hey! sure, just so you asked, consider the following supposed code:

In the above code snippet, we have initialized the title, description, and body instance members in the constructor of the class Article. However, the views and stars variable members remain uninitialized and depend on the calculateViews() and calculateStars() methods, respectively. These methods contain some logic to set the value of these variables, but only at runtime. Although, not exactly a perfect situation, but rather a close one, where you may still need to initialize a variable to a null type and assign it a definite value later on, i.e. when your application begins to run.

Well, that makes sense! so, how do I specify a nullable value in the non-nullable world?

Making everything nullable by default, guarantees that Dart is able to provide us with null-reference feedback at compile time rather than reporting these errors at runtime. However, to deal with situations where we may still need to use nullable types, Dart has added the following operators and keyword to its inventory: —

Operators and the late keyword for Null-Safety in Dart

Awesome, first tell me about the “?” operator…

The ? operator is typically used in the following two cases: —

  • If you declare a variable and initialize it to a null value, and maybe later in your code, you also want to assign it a null value. To help you remember, just learn the phrase “null now, null later”.
  • If you declare a variable and initialize it to a definite value, but you may also want to assign it a null value later in your code. In order to remember, just memorize the phrase “not null now, but null later”.

Let’s look at the first case with the help of an example. Consider the following code: —

null now, null later

In the above code snippet, we have declared the age variable and initialized it to a null value. Notice, the ? operator after the type of variable. This is how you tell the Dart compiler that you intend to initialize the age variable to a null value and maybe later you will also assign it a null value. If you inspect the above code, you will see that the age variable was initialized to a null value, then assigned a null value again later on, and finally assigned a definite value of 32, before it was printed to the console.

Next, let’s take a look at the second case, again with the help of an example.

not null now, but null later

In the above code snippet, we have initialized the age variable to a definite value of 32 and then we assign it a null value. If you were to remove the ? operator after the type of the age variable, you can neither initialize the age variable to a null value nor assign it a null value later on in your code.

Good job, making-up those phrases, what about the “!” operator?

Typically, the ! operator is used in expressions, to let the Dart compiler know that a nullable variable you may be using in an expression is for sure non-nullable. I know, you may find the above statement a little confusing. So, let’s see an example to clarify things further.

Notice the “!” operator after the dataSource() function

The above code snippet again declares an age variable with a definite value of 32, which as we know without using the ? operator is going to be non-nullable by default.

Pay close attention to the next line, where we set the value of the age variable to the value received from the dataSource() function, followed by the “!” operator. But, if you examine the declaration of the dataSource() function, which clearly indicates that the function may either return an int value or a null value. Notice, the “?” operator after the type declaration of the dataSource() method. Though, the current implementation of the dataSource() doesn’t return a null value, but it may, say if the dataSource() method was getting the values from an external source.

Now, by using the “!” operator in this scenario, we are assuring the Dart compiler that, no matter what, the return value from the dataSource() method is never going to be null. In this case, the Dart compiler expects that you know what you’re doing and it is you who will make sure that the value returned by the dataSource() function is never going to be null. However, if you are not completely sure, that the value returned by the dataSource() function is never going to be null, simply do not use the “!” operator.

If not sure, just don’t use the “!” operator

Bravo! and finally please tell me about the late keyword…

The late keyword is simply inserted before the type of a variable initialized to a nullable value, in order to assure the Dart compiler that the variable will not be used or read before assigning it a definite value. Again, when you use the late keyword, Dart simply trusts you, that you won’t try to read or use the variable that has been initialized to the nullable value, before assigning it a non-nullable value. To make things clear, consider the following code: —

Using the late keyword

If you uncomment the second statement in the main() function in the above code snippet, you are simply inviting trouble and breaking your promise. Why? Because, by using the late keyword you promised the Dart compiler that you will not try to use or read the age variable before you assign it a non-nullable value. Since, we didn’t know the value of the age variable at the time of it’s declaration, we used the late keyword to inform the Dart compiler that we will be assigning it a definite value later on, but by assuring it that the variable will not be read or used, before it is set to a non-nullable value.

So, just remember, in case you’re not sure what value to assign to a variable at the time of its declaration, but you know for sure, that it will be assigned a definite value, before it is read or used, simply precede the type of the variable with the late keyword.

Thank you so much! But one last thing, I keep hearing about the “Sound” word in relation with the Darts’ Null-Safety feature, what is it?

Yeah, you may certainly hear “Darts’ Sound Null-Safety” where the word “sound” refers to the fact, that if an expression in Dart has a static type which does not permit a null value, then no possible execution of that expression can ever evaluate to null.

Put simply, it means, once you opt-in for Darts’ null safety feature, Dart guarantees you that it will provide you with the null feedback at compile time and won’t leave anything for the runtime. However, again, alway remember that mixed-mode and using operators that permit the nullable values, may still result in runtime null-reference errors.

Any further resources, you suggest?

Absolutely, Darts’ documentation has always been the best in class. Following are some of the recommended resources, in case you like to dive deeper into the Darts’ Null-Safety feature: —

Please Support: It takes a lot of effort and time to write these articles. So, if you really like this article, kindly show your support by becoming a patron or buy me a coffee. Please see my bio!

Connect with me: github | pub.dev| twitter | instagram

  1. Dynamically typed languages typically may not have a compiler, rather they may entirely depend on an interpreter.
  2. Definite refers to a non-nullable value.

--

--

Syed iMujtaba Nazki
Syed iMujtaba Nazki

No responses yet