Why is `== null` preferable to `is Null`? - dart

In Dart, checking for a value to be == null seems similar to checking if it is Null. Why is the former more preferable?

It is the type of comparison you are doing. In == null, you are comparing an object/primative to null whereas in the latter, is Null, null is an object. It makes no difference in the compiler.
== null is more favourable as the two being compared could be both primitives, which saves memory in the system.

== null is familiar to developers coming from other popular languages.

Using this type check in Dart not a quite correct.
if(value is Null) {
}
In Dart the static type of null is bottom type.
An as we know, the bottom type is a subtype of all types.
Now we test subtypes in Dart (including value of bottom type, null)
void main() {
var subtype = new Subtype();
var typeOfBase = new TypeOf<Base>();
test(subtype, typeOfBase);
test(null, typeOfBase);
}
void test(value, TypeOf typeOf) {
var type = typeOf.type;
var runtimeType = value.runtimeType;
if(typeOf.isSubtypeOf(value)) {
print("Value [$value] of type [$runtimeType] is a subtype of [$type[");
} else {
print("Value [$value] of type [$runtimeType] is NOT a subtype of [$type]");
}
}
class Base {
}
class Subtype implements Base {
String toString() => "subtype";
}
class TypeOf<T> {
Type get type => T;
bool isSubtypeOf(value) => value is T;
}
Results:
Value [subtype] of type [Subtype] is a subtype of [Base[
Value [null] of type [Null] is NOT a subtype of [Base]
So, here we can see that performing this test is not quite correct in Dart because null has not bottom type but a value of regular type Null which are not a bottom type and, of course, not a subtype of Base type.
So, I not recommend using is Null in Dart because in the future the class Null can be considered as deprecated and replaced by some internal implemented type as void or dynamic.

Related

Bang operator vs assert statements

In the code below, I'm casting away the nullability by using the bang operator.
void main() {
String? foo;
foo!.toLowerCase();
}
But when I use assert just before calling the function, I get an error.
void main() {
String? foo;
assert(foo != null);
foo.toLowerCase(); // Error
}
Is bang operator not doing the same thing behind the scene?
Note: I'm not looking for a solution to how to make it work, there are many ways, even the flow statement ones, if (foo == null) return;
(Adding this an answer instead of a comment.)
The point of asserts is that they can be disabled and that they will not incur any runtime penalty. It's been some matter of debate, but the current philosophy is that this means that asserts will not perform type promotion, so even if you do:
bool foo(Object object) {
assert(object is String);
// error: The getter 'isEmpty' isn't defined for the type 'Object'
return object.isEmpty;
}
For the same reason, assert(someLocalVariable != null) will not promote someLocalVariable to a non-nullable type:
bool bar(String? string) {
assert(string != null);
// error: The property 'isEmpty' can't be unconditionally accessed
// because the receiver can be null.
return string.isEmpty;
}
As of Dart 2.12 with null-safety enabled, however, you can get the desired effect by just performing the cast directly. That will promote the type and throw a runtime exception if the cast fails:
bool foo(Object object) {
object as String;
return object.isEmpty;
}
bool bar(String? string) {
string!;
return string.isEmpty;
}

How can I make sure that a non-nullable type will always be inferred?

I'm currently making an API that I want the optional value of type T to never be nullable (having null-safety enabled, of course).
Let's exemplify with the following:
void main() {
T nonNullableGeneric<T>(T value) {
if(value == null) {
throw 'This is null';
}
return value;
}
nonNullableGeneric(1); // Works fine
nonNullableGeneric(null); // Works fine BUT I would like that the type inference would not allow a nullable value
// Now this works as expected:
// "the argument type 'Null' can't be assigned to the parameter type 'int'."
//
// ... but I have to specify the type `T`, which is not clear given my function signature, that clearly
// requires a non-nullable value of type `T`
nonNullableGeneric<int>(null);
// How can I make the following call:
// nonNullableGeneric(null)
// to throw me something like "the argument type 'Null' can't be assigned to a parameter type 'T'."?
}
I've also created a dartpad.
As you can see, I must always specify the type if I want to make sure that it can't be Null when using a generic argument.
How can I make sure that it will never allow a nullable type?
I've found that the answer was really obvious, although I think it's reasonable to mention anyways: just make your type T constraints (extend) to a type of Object. This will make sure that that particular argument can never be optional.
Quoting the Object docs:
/// ...
/// Because `Object` is a root of the non-nullable Dart class hierarchy,
/// every other non-`Null` Dart class is a subclass of `Object`.
/// ...
So, in this example that I've mentioned, the solution would be:
void main() {
T nonNullableGeneric<T extends Object>(T value) {
return value;
}
nonNullableGeneric(1); // Works fine
// Throws a:
// Couldn't infer type parameter 'T'. Tried to infer 'Null' for 'T' which doesn't work: Type parameter 'T' declared to extend 'Object'.
// The type 'Null' was inferred from: Parameter 'value' declared as 'T' but argument is 'Null'.
// Consider passing explicit type argument(s) to the generic
nonNullableGeneric(null);
}

How do I check whether a generic type is nullable in Dart NNBD?

Let's say I had some function that takes a generic type as an argument. How do I check within that function whether the generic type argument is nullable or not? I want do something like this:
void func<T>() {
print(T is nullable);
}
void main(){
func<int>(); //prints false
func<int?>(); //prints true
}
All I can think of to do is to check if T.toString() ends with a ? which is very hacky.
Try:
bool isNullable<T>() => null is T;
The accepted answer really just checks if the type can be null. It doesn't care about the type that you are operating the null operator on.
If you want to check if a type is a specific nullable type, a.k.a if you want to check if a type is specifically one of type DateTime? and not String?, you can't do this in dart via T == DateTime? as this conflicts with ternary operator syntax.
However, since dart allows passing nullable types into generic arguments, it's possible to it like so:
bool isType<T, Y>() => T == Y;
isType<T, DateTime?>() works.
I have come across this a lot. And #Irn method works, except for when T is type Type (when using generics), it will always return saying that T is not null.
I needed to test the actual type of Type not Type its self.
This is what I have that is working really well for me.
bool get isNullable {
try {
// throws an exception if T is not nullable
final value = null as T;
return true;
} catch (_) {
return false;
}
}
It creates a new List instance to verify if it's type is nullable or not by using the is operator which supports inheritance:
bool isNullable<T>() => <T?>[] is List<T>;

What does the exclamation mark mean before a function call?

I was following a PR for Flutter and came across this code:
if (chunkCallback != null) {
chunkCallback!(0, 100);
}
What does the exclamation mark mean after chunkCallback? Nothing I search on Google works.
"!" is a new dart operator for conversion from a nullable to a non-nullable type.
Read here and here about sound null safety.
Use it only if you are absolutely sure that the value will never be null and do not confuse it with the conditional property access operator.
chunkCallback is a nullable reference to a function.
If you are sure that chunkCallback can't be null at runtime you can "Cast away nullability" by adding ! to it to make compiler happy
typedef WebOnlyImageCodecChunkCallback = void Function(
int cumulativeBytesLoaded, int expectedTotalBytes);
...
class Class1 {
final WebOnlyImageCodecChunkCallback? chunkCallback;
Class1(this.chunkCallback);
void test() {
if (chunkCallback == null) {
throw Exception("chunkCallback is null");
}
chunkCallback!.call(0, 100);
}
}
Esentially, ! in this case is a syntactic sugar for
(chunkCallback as WebOnlyImageCodecChunkCallback).call(0, 100);
I think it is a shorthand syntax for “Casting away nullability”, as per the docs: https://dart.dev/null-safety/understanding-null-safety#null-assertion-operator
The variable chunkCallback must be able to accept null and you cannot call a function on a nullable type without either using ! or ?. This is part of Darts sound null safety
Some great videos on this:
Flutter vid
YouTube vid
Even though the conditional statement checks for null, Dart still requires the exclamation mark before the function call. The difference between using ! over ? is that it will throw an exception instead of using the variable if the value is null.
Some examples:
class Car {
String? make; // String or null type
Car([this.make]); // parameter is optional
}
main() {
Car test = Car('Ford'); // initialised with a value
Car test2 = Car(); // no value given so null is default
// returns 4
if (test.make != null) {
print(test.make!.length); // ! still needed even though !=null condition stated
} else {
print('The value is null');
}
// returns The value is null
if (test2.make != null) {
print(test2.make!.length);
} else {
print('The value is null');
}
}
Above example shows that conditional check for null is not enough.
And choosing between ? and !
class Customer {
String? name;
String? surname;
Customer(this.name, [this.surname]); // constructor with optional parameter []
}
main() {
Customer ford = Customer('John'); //only name is given a value
// calling a method on a nullable type doesn't work
// so ? and ! used here after variable name and before method
print(ford.name!.length); // operation executed as usual => 4
print(ford.surname?.length); // ? on null value returns null => null
print(ford.surname!.length); // Exception is thrown => TypeError
}

Syntax error when trying to determine if a variable is of a certain type

Pretty much as the title says: If you have a Type stored in a variable, there's no way to compare your actual object to this type variable, as far as I can tell. I can probably accomplish what I'm trying to do with mirrors, but I'd prefer not to if at all possible.
void example() {
Type myType = String;
String myExample = "Example";
//Syntax error here: The name 'myType' is not a type and cannot be used in an 'is' expression
if (myExample is myType) {
}
}
You can't generally test if a value is of a type using the Type object.
Type objects are reflected types, not real types. They represent the real type, but you can't use them in the code where you need a type: as type assertions, as generic type parameters or with the is/as operators. You must use the name of a type in those places, and not the name of a normal variable that happens to hold a Type object.
Clever stuff using mirrors might get there, but it's likely overkill for most cases (and I understand that you don't want it).
What you might be able to do instead, is to not pass around raw Type objects. You could instead make your own type abstraction, something like:
class MyType<T> {
const MyType();
Type get type => T;
bool isA(Object object) => object is T;
}
Then you can use that to represent types, not a Type object, and do something like:
void main(List<String> args) {
MyType myType = const MyType<String>();
String myExample = "Example";
if(myType.isA(myExample)) {
print('is');
} else {
print('is not');
}
}
That does require that your entire program uses your type objects to pass around types, but it also gives you a lot of control over those objects, so you can implement the functionality that you need.
I tried
library x;
void main(List<String> args) {
Type myType = String;
String myExample = "Example";
if(myExample.runtimeType == myType) {
print('is');
} else {
print('is not');
}
}
and it worked.
I have not much experience with such code in Dart though. Maybe that is not a fail-safe approach.
import 'package:reflection/reflection.dart';
void main() {
var childType = typeInfo(Child);
var baseType = typeInfo(Base);
if(childType.isA(baseType)) {
print("Child is Base");
}
if(baseType.isAssignableFrom(childType)) {
print("Base is assignable from Child");
}
}
class Base {
}
class Child extends Base {
}
Child is Base
Base is assignable for Child
P.S.
The "reflection" package incompatible with dart2js. It work only when used in Dart language.

Resources