How is Dart "sound null-safety" different from Kotlin null safety? - kotlin

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
So, what extra optimization does Dart do?
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:
int getAge(Animal a) {
return a.age;
}
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
How does it interoperate with legacy codebases that are not null-safe (written before null safety) while ensuring null safety?
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.

So, what extra optimization does Dart do?
The most basic kind of optimization is that when performing calculations on numeric types, compiler can treat them (internally) as primitive types of non-reference types (unboxed values).
Why is that?
Because they cannot be null and, therefore, it is not necessary to use them as data of referenced types (boxed values).
Why is that?
Because null is represented in Dart as a null constant reference.
If there is no need to refer to this constant, then why not use value types instead of reference type? At least in the generated code, which can be optimized already at compile time.
All this thanks to the so-called "strong mode".
The strong mode in conjunction with non-nullable types allows you to optimize the code already at the compilation stage, which is very important for modes such as AOT, which do not allow code to be optimized at runtime, because it is in the RE (read and execute) mode.
How does it interoperate with legacy codebases that null-safety is not supported while ensuring null safety?
It seems to me that you should ask this as a separate question.

Related

Is it possible to compile Kotlin Native without runtime checks?

I couldn't find a list of compiler flags for the Kotlin compiler. I wanted to know if it is possible to compile Kotlin Native to "unsafe" code without Null check Range checks for arrays etc' in order to create code with less runtime overhead?
It is especially relevant for real-time systems. Is that a valid use case for Kotlin?

Can I enforce null safety for platform types from Java API?

From Kotlin version M9, the "enforcing null safety for platform types" was removed.
https://blog.jetbrains.com/kotlin/2014/10/making-platform-interop-even-smoother/
However, I want to make all the java API nullable unless specified as #NotNull, so that !! or ?. should be explicit again.
Is that possible in the current version of IntelliJ IDEA?
It is partially coming in the nearest Kotlin release with #ParametersAreNonnullByDefault
https://youtrack.jetbrains.com/issue/KT-10942

Single exclamation mark in Kotlin

What does a single exclamation mark mean in Kotlin? I've seen it a few times especially when using Java APIs. But I couldn't find it in the documentation nor on StackOverflow.
They're called platform types and they mean that Kotlin doesn't know whether that value can or cannot be null and it's up to you to decide if it's nullable or not.
In a nutshell, the problem is that any reference coming from Java may be null, and Kotlin, being null-safe by design, forced the user to null-check every Java value, or use safe calls (?.) or not-null assertions (!!). Those being very handy features in the pure Kotlin world, tend to turn into a disaster when you have to use them too often in the Kotlin/Java setting.
This is why we took a radical approach and made Kotlin’s type system more relaxed when it comes to Java interop: now references coming from Java have specially marked types -- Kotlin Blog
It's the notation for platform types:
T! means "T or T?"
Platform Types
The type names or class names ending with single exclamation mark ! are called platform types in Kotlin. You find them when you are working in Kotlin with old Java code that doesn't contain nullability information.
Examples:
Nullable Information: Nullable Type
#Nullable String in Java is considered as String? by Kotlin.
Non-null Information: Non-null Type
#NotNull String in Java is considered as String by Kotlin.
No Information: Platform Type
String without annotations in Java is considered as String! by Kotlin.
How to deal with Platform Types?
You can work with a platform type either as a nullable or a non-null. The compiler will allow you to call all methods on this type. It’s your responsibility how to use them. If you know that the value can be null, you should compare it with null before you call methods on it. If you know it’s not null, you can use it directly but as in Java, you’ll get exception if your assumption about the nullability is wrong.
Note that you can't declare platform types in Kotlin code, they come only from Java code.
Inheritance and Platform Types
While overriding Java methods in Kotlin code, you have the option to declare parameters and return types as nullable or non-null. You need to choose this wisely, because if you decide to make the parameters non-null, the Kotlin compiler generates non-null assertions for these non-null parameters. And when next time you access this Kotlin code back from Java and you pass a null value, you'll get exception.
Hope that helps clearing all your doubts about Platform Types.
A Type notated with ! is called platform type, which is a type coming from Java and thus can most probably be null. It’s what the Kotlin compiler infers by default when calling Java (for the most basic cases, Java methods can be annotated to get around this). You should handle platform types as nullable types, unless you certainly know that the particular API will never return null. The compiler allows platform types to be assigned to variables of both nullable and non-null types.
Notation for Platform Types
[...]
T! means "T or T?" [...]
You could refer to platform types as "types of unknown nullability". Also important to know is that you cannot use the exclamation-marked type for your own types, it's not part of the Kotlin syntax, it's only a notation.
I use the funny interpretation to remember those things as below:
?: I dont know whether it is null or not.
!: Be careful! This might be null.
!!: Be careful, and yes I know it. This is always not null.
I've seen it a few times especially when using Java APIs
As mentioned by s1m0nw1, T! means T or T?. The next question is: what is T?? This is nicely documented at https://kotlinlang.org/docs/reference/null-safety.html. Kotlin does not allow certain elements to be null, e.g. String, unlike Java
To allow nulls, we can declare a variable as nullable string, written
String?:
var b: String? = "abc"
b = null // ok
[...]
b?.length
This returns b.length if b is not null, and null otherwise. The type of this expression is Int?.
Excerpt from Platform Types in Kotlin :
Besides explicitly specifying a type as optional (e.g. Person?), Kotlin presents us with another beast, called Platform Type, specified by putting a single exclamation mark instead (e.g. Person!). This concept has been created for compatibility reasons, when accessing code from null-unsafe platforms like Java. It is often the case that when using a Java library, many methods return SomeType!, since the Kotlin compiler cannot infer if the result is nullable or not.
For example:
(Mutable)Collection<T>!
Just means the following: "Java collection of T may be mutable or not, may be nullable or not".
Hope this helps.

Is C++/CLI an extension of Standard ISO C++?

Is Microsofts C++/CLI built on top of the C++ Standard (C++98 or C++11) or is it only "similar" and has deviations?
Or, specifically, is every ISO standard conforming C++ program (either C++98 or C++11), also a conforming C++/CLI program?
Note: I interpret the Wikipedia article above only comparing C++/CLI to MC++, not to ISO Standard C++.
Sure, it is an extension to C++03 and can compile any compliant C++03 program that doesn't conflict with the added keywords. The only thing it doesn't support are some of the Microsoft extensions to C++, the kind that are fundamentally incompatible with managed code execution like __fastcall and __try. MC++ was their first attempt at it, kept compatible by prefixing all added keywords with underscores. The syntax was rather forced and not well received by their customers, C++/CLI dropped the practice and has a much more intuitive syntax. Stanley Lippman of C++ Primer fame was heavily involved btw.
The compiler can be switched between managed and native code generation on-the-fly with #pragma managed, the product is a .NET mixed-mode assembly that contains both MSIL and native machine code. The MSIL produced from native C++ source is not exactly equivalent to the kind produced by, say, the C# or VB.NET compilers. It doesn't magically become verifiable and doesn't get the garbage collector love, you can corrupt the heap or blow the stack just as easily. And no optimizer love either, the MSIL gets translated to machine code at runtime and is optimized just like normal managed code with the time restrictions inherent in a jitter. Getting too much native C++ code translated to MSIL is a very common mistake, the compiler hides it too well.
C++/CLI is notable for introducing syntax that got later adopted into C++11. Like nullptr, override, final and enum class. Bit of a problem, actually, it begat __nullptr to be able to distinguish between a managed and a native null pointer. They never found a great solution for enum class, you have to declare it public to get a managed enum type. Some C++11 extensions work, few beyond the ones it already had, auto is fine but no lambda expressions, quite a loss in .NET programming. The language has been frozen since 2005.
The C++/CX language extension is notable as well, one that makes writing C++ code for Store and Phone apps palatable. The syntax resembles C++/CLI a great deal, including the ref class and hats in the syntax. But with objects allocated with ref new instead of gcnew, the latter would have been too misleading. Otherwise very different from C++/CLI at runtime, you get pure native code out of C++/CX. The language extension hides the COM interop code that's underneath, automatically reference-counting objects, translating error codes into exceptions and mapping generics. The resemblance to C++/CLI syntax is no accident, they basically perform the same role. Mapping C++-like syntax to a foreign type system.
CLI is a set of extensions for standard C++. CLI has full support of standard C++ and adds something more. So every C++ program will compile with enabled CLI, except you are using a CLI reserved word and this is the weakness of the extension, because it does not respect the double underscore rule for extensions (such reserved words has to begin with __).
You can deactivate those extensions in the GUI by:
Configuration Properties -> General -> Common Language Runtime Support
Even Bjarne Stroustrup calls CLI an extension:
On the difficult and controversial question of what the CLI binding/extensions to C++ is to be called, I prefer C++/CLI as a shorthand for "The CLI extensions to ISO C++". Keeping C++ as part of the name reminds people what is the base language and will help keep C++ a proper subset of C++ with the C++/CLI extension
Language extensions could always be called deviations from the standard, because it will not compile with a compiler without CLI support (e.g. the ^ pointer).

Converting c++ project to clr safe project

I need to work on converting a very huge c++ project to clr safe. The current c++ project has a lot of stuff from c++ like templates, generics, pointers, storage/stream, ole apis, zlib compression apis, inlines etc. Where can I find the datiled document for this type of conversion? Can you suggest some good book to refer to? If anyone of you have done such conversion, can I get some analysis from you?
I'll just cough up the MSDN Library article titled "How to: Migrate to /clr:safe
Visual C++ can generate verifiable components with using /clr:safe, which causes the compiler to generate errors for each non-verifiable code construct.
The following issues generate verifiability errors:
Native types. Even if it isn't used, the declaration of native classes, structures, pointers, or arrays will prevent compilation.
Global variables
Function calls into any unmanaged library, including common language runtime function calls
A verifiable function cannot contain a static_cast Operator for down-casting. The static_cast operator can be used for casting between primitive types, but for down-casting, safe_cast or a C-Style cast (which is implemented as a safe_cast) must be used.
A verifiable function cannot contain a reinterpret_cast operator (or any C-style cast equivalent).
A verifiable function cannot perform arithmetic on an interior_ptr. It may only assign to it and dereference it.
A verifiable function can only throw or catch pointers to reference types, so value types must be boxed before throwing.
A verifiable function can only call verifiable functions (such that calls to the common language runtime are not allowed, include AtEntry/AtExit, and so global constructors are disallowed).
A verifiable class cannot use Explicit.
If building an EXE, a main function cannot declare any parameters, so GetCommandLineArgs must be used to retrieve command-line arguments.
Making a non-virtual call to a virtual function.
Also, the following keywords cannot be used in verifiable code:
unmanaged and pack pragmas
naked and align __declspec modifiers
__asm
__based
__try and __except
I reckon that will keep you busy for a while. There is no magic wand to wave to turn native C++ into verifiable code. Are you sure this is worth the investment?
The vast majority of native C++ is entirely valid C++/CLI, including templates, inlines, etc, except the CLR STL is rather slow compared to the BCL. Also, native C++ doesn't have generics, only templates.
The reality of compiling as C++/CLI is to check the switch and push compile, and wait for it to throw errors.
Rewriting native C++ into safe C++/CLI will result in a code that is syntactically different, but semantically same as C#. If that is the case, why not rewrite directly in C#?
If you want to avoid what is essentially a complete rewrite, consider the following alternatives:
P/Invoke. Unfortunately, I'm unfamiliar whether this would isolate safe from unsafe code. Even if it can perform the isolation, you'll need to wrap your existing C++ code into procedural, C-like API, so it can be consumed by P/Invoke. On a plus side, unless your API is excessively chatty, you get to keep (most of) your native performance.
Wrapping your C++ into out-of-process COM server and using COM Interop to consume it from the manged code. This way, your managed code is completely protected from any corruption that might happen at C++ end and can remain "safe". The downside is a performance hit that you'll get for out-of-process marshaling and the implementation effort you'll need to expend to correctly implement the COM.