This Dart official video states that Dart's so-called "sound null safety" is better than Kotlin's null safety design, because it can optimise the code based on whether a variable is declared nullable, and other languages (I assume this refers to languages including Kotlin) have to do runtime checks to ensure null safety.
So, what extra optimization does Dart do?
How does it interoperate with legacy codebases that are not null-safe (written before null safety) while ensuring null safety?
Dart sound null safety
The benefit of sound null safety in Dart is that the compiler can make use of a non-nullable type, which can elimate null checks. Therefore, the compiler will generate fewer instructions (which results in smaller binaries and faster runtime).
Example
Take the following function:
These are the instructions the compiler generated before sound null safety:
As you can see, there are explicit instructions for checking null in the compiled code.
And this is what the same function looks like compiled with sound null safety:
Now, these additional instructions are no longer needed.
Note that the actual instructions generated starting with Dart 2.12 for the example function are the following (there were further optimizations):
See Dart and the performance benefits of sound types by Vijay Menon (Engineering Lead, Dart) for reference.
Interoperability with legacy Dart code bases
It does not.
Well, that is not the whole truth. If you want to use Dart
>=2.12.0
with any codebase that was written before Dart 2.12 (and with that before null safety), you cannot make use of sound null safety. You can, however, interoperate with these legacy codebases by passing a compiler flag that disables sound null safety. That would be--no-sound-null-safety
(see my previous answer for more details).This means that all benefits of sound null safety are lost when interacting with legacy codebases. This is also why the Dart team encourages all package authors to migrate their code to null safety.
Comparison to Kotlin
Kotlin simply does not have the additional compiler optimizations that Dart achieves with unboxed values thanks to sound null safety.
Keep in mind that Kotlin always allows interoperability with Java, which does not have any concept of null safety. I would imagine that this is a reason why Kotlin will never be able to have sound null safety in the same way that Dart code that interoperates with legacy codebases does not. That is as long as Kotlin code is compiled for the JVM with Java interoperability.
NNBD
If we are not concerned about the compiled code but only about the developer experience, Kotlin and Dart handle null safety identically. That is both languages are non-nullable by default (NNBD).
This means that when writing code in Dart 2.12+ or in Kotlin, all types are assumed to be non-nullable unless you explicitly mark them as nullable.
The only way to get a null pointer exception is by programmer error in both languages, i.e. using the bang operator
!
in Dart and double bang operator in Kotlin!!
, i.e. a not-null assertion by the developer.Note that when using null assertions, additional runtime checks have to be added to the compiled code to preserve soundness in Dart. These checks always exists for Kotlin code compiled for the JVM as it is not sound to begin with.
This can also happen when interoperating with Java code when using Kotlin or interoperating with legacy code when using Dart.
There are some more edge cases, see Null safety in Kotlin and Understanding null safety in Dart for reference.