I'm trying to understand how null works in Dart. As part of that study, I am looking at how null is implemented in the source code. I found null.dart in the source code here:
/**
* The reserved word [:null:] denotes an object that is the sole instance of
* this class.
*
* It is a compile-time error for a class to attempt to extend or implement
* Null.
*/
#pragma("vm:entry-point")
class Null {
factory Null._uninstantiable() {
throw UnsupportedError('class Null cannot be instantiated');
}
external int get hashCode;
/** Returns the string `"null"`. */
String toString() => "null";
}
But I can't see where null itself is defined. Where is it found?
The expression null is defined in the grammar of the language specification.
Since null is a reserved word (like if, while and this), it is not the name of any declaration. It is just an atomic expression (like this), and it evaluates to the "null value" which is defined as the one and only value implementing the Null class.
The language implementations can implement it any way they want.
The Dart2JS compiler uses both null and undefined from JavaScript as Dart null values.
The VM has a special null value object that it creates in memory as part of its start-up.
Related
I'm learning Dart & Flutter, but I'm struggling with some basic programming issues like the use of getters:
GoogleSignInAccount get user => _user!;
What's the equivalent of the "get" method?
What does the ! at the end of a variable mean?
Thanks in advance!
That is a getter, in Java that code might look like:
public GoogleSignInAccount getGoogleUser(){ return this.user; }
Dart likes that code written more succinctly.
In Dart private class members are denoted via a _ in front of the variable/function name. So the variable that the getter is returning is a private variable, hence _user.
The ! at the end of the variable name has to do with Dart null safety. Throwing a ! at the end of the variable name is functionally the equivalent of saying assert _user != null; what it actually does is cast a nullable data type to its non-nullable equivalent.
In Dart, all data has two types, a nullable type, denoted by a ? at the end of the data type declaration, and a non nullable type.
The _user variable, it can be assumed from this code, is of the type GoogleSignInAccount? meaning that it can be null. However the getter wants to return a GoogleSignInAccount. Because there is no question mark on the end, the type the getter returns must NOT be null. So we put ! on the end of the _user variable to denote that we want to cast the nullable type to its not null form.
Note also that the name of this function is user and that in Dart you can have two functions of the same name if one is a getter and the other is a setter. To denote getter versus setter in a function declaration, you use the get and set keywords as you see above.
If you want to get a good idea of how getters and setters look in Dart, make a class with some variables. Make sure all of the variable names start with _ so that they are private, then right click in your IDE and tell it to generate some getters and setters for you. I believe both Android Studio and VSCode have the Generate option in their right click menu.
There are two things at play here:
1.
GoogleSignInAccount get user => _user!;
does the same as
GoogleSignInAccount user() {
return _user;
}
Where the first one is called a getter.
As a getter you can access it like a property. For example
myUser = userService.user;
With the function notation you access it like
myUser = userService.user();
Since you do not calculate anything but only expose that private member the getter notation is more succinct.
2.
Regarding the !:
Dart is null safe, which means types without a ? can't be null.
In your case the _user is a GoogleSignInAccount? which means it can be null. For example on startup when the user isn't signed in yet.
The getter GoogleSignInAccount get user has the type GoogleSignInAccount which means the compiler gives you an error when you try to assign null to it.
So
user can not be null.
_user could be null.
With the ! you promise to the compiler that you know that the _user is in no case null when the getter user is called.
Example:
This could be the case if the user is loaded right on startup while a progress indicator is shown. When the user is loaded you start the whole app and now you can access the user with user!. You are sure that the user is loaded.
If you somehow access the user before it's loaded (while it's still null) you get a runtime error.
Null safety just helps you to think about wheter a variable can be null and to avoid NullPointerExceptions.
The exclamation mark at the end of the private variable tells the compiler that the variable is not null and the user data returned by the getter must not be null.
There is a discussion about dynamic and var before null-safety. Then what's the Object? between each of them?
Is Object? == dynamic?
How about var? and dynamic??
Any difference between dynamic? and dynamic?
I see the official document about null-safety, but can't find the related topic.
dynamic is a special type that disables static type-checking. You can attempt to call any method on a dynamic type. If the object turns out not to have such a method, then it will result in a runtime failure instead of a compile-time one.
Object? is a base type suitable for referencing any object, including null. Unlike dynamic, it is statically type-checked, so you would get compile-time failures if you attempt to call most methods on it without explicitly checking the runtime type or without performing a cast.
var? is not valid syntax. var is not a type; it declares a variable without explicitly specifying a type, allowing the type to be inferred.
dynamic? is valid but is redundant. (See #3.)
Variables of type dynamic can already include null, so adding a ? to make it nullable is redundant. The Dart analyzer will tell you so.
3: About dynamic vs dynamic?: they are the same.
Since dynamic also represents nullable types, for the compiler it is the same of dynamic?.
From Why operator ==(Object other) accepts a nullable argument?:
You can also see the analyzer and runtime will call it dynamic even if we check the signature of a method declared to return dynamic?:
void main() {
print(test.runtimeType); //runtimeType of the test function: () => dynamic
}
dynamic? test() { }
In fact a hint of the dart linter reports as 'unnecessary' the use of ? in dynamic? (as in Null?):
The '?' is unnecessary because 'dynamic' is nullable without it. (unnecessary_question_mark).
Personally, I don't understand why dynamic? Is only reported by a hint (which many people, myself included, don't notice) keeping it valid as a syntax.
1: A variable declared with Object? type behaves like all other normal variables whose type is specified, such as String? etc. Since every class -apart Null (the type of null)- is a subclass of Object (and since in Dart there are no primitive values as opposed to objects, unlike in Java; in Dart also null, int and bool are objects. But forget this clarification, if you don't know Java), a variable declared with Object? can contain any value. But the compiler will only allow access -after a null check- to the properties of Object (toString(), ==(), runtimeType, etc).
A variable declared with dynamic (or dynamic?, see point 3) instead allows access to any public member: the compiler will not perform any checks (unless the property begins with an underscore _, because in that case it is clear that it is not public); if you try to access a non-existent member you will instead have an error at runtime Note1. Furthermore, with dynamic we also renounce null safety: dynamic is equivalent to dynamic? (in practice the question mark can be considered implicit, it is as if it were always there).
2: using var, or final -if you want an immutable reference- without declare the type, the compiler check the value assigned to the variable (in fact the omission of the type is not allowed if the variable is not initialized immediately) and treats the variable as if it were declared with that type.
dynamic at runtime:
One use of 'dynamic' that can lead to confusion is with generic classes, because dynamic as parametric type exists also at runtime:
with
dynamic obj = true;
obj at runtime has bool type, but with
List list = [bool];
list at runtime has List<dynamic> type.
However, using
var list2 = [true];
the parametric type is inferred correctly (list2 has List<bool> runtimeType).
Note1 More precisely, a invocation such as myDynamicVariable.nonexistentMember cause an invocation of the noSuchMethod() method on the object; noSuchMethod() can also be overridden and not throw any exception; but this is a rare practice, in Dart 2).
In dart with sound null safety turned on it is entirely possible to do
dynamic myVar; // myVar assumes default value of null
String someString = myVar; // No warning by IDE.
As expected the above results in a run-time error since myVar is null and someString is non-nullable.
Is this a problem with the linter/IDE?
I did discover that I can enable pedantic linting that causes the IDE to show a warning when I try to implicitly cast dynamic to another type. Turning that on helps but I think the problem is that dynamic can be null without having to be explicitly defined as nullable.
In short we don't have
dynamic? myNullableVar;
My questions is: Is this a bug?
This "issue" bites you most commonly when you do something like
Map<String, dynamic> myData = jsonDecode(receivedResponseBody);
var name = myData['name'];
In the above example I expect the IDE to show a warning that I am trying to assign a potentially null value to a non-nullable variable. The IDE should require the user to add a null check.
If this is not a bug (in the IDE or in the linter), then why not?
Long story short: The implicit cast from dynamic to another type masks the issue with null assignments, and I expect the IDE to provide a warning.
EDIT: after note from # jamesdlin below
Specifically I am OK with the following where the Left-hand side of the assignment allows null values.
dynamic someVar;
String? myString = someVar;
Side note, I was hoping that the new dart typeDef feature would allow my to build something similar to Union type hints in Python. That would allow me to then get something like
typeDev JsonValueType = { int, String, float, bool };
And the result from jsonDecode would then be
Map<String, JsonValueType?>
Which explicitly includes Null and therefore the IDE would warn the user to add a null check.
My opinion: As long as you can assign any nullable type on the right of an assignment operator to a non-nullable type on the left, you don't have true sound null safety.
It's not a bug.
You can do:
dynamic x = "not an int";
int y = x;
and not get any compiler warnings. That's just how dynamic works, it turns off compiler ("static") type-checks and relies on run-time ("dynamic") checks instead. That's where the name comes from.
There is no reason to allow dynamic? because dynamic already allows the null value (and any other value). The dynamic type is inherently nullable, adding ? to it makes no difference, just as Null? is meaningless.
The Dart type system is statically null sound (mostly, the usual caveats around generics apply), but dynamic is a way to turn that type system off on request. Don't use dynamic unless you want that effect.
I started learning Dart and was reading a critique of some of it's design choices here: https://medium.com/#krossovochkin/dart-language-bad-design-choices-6e35987dc693
The last point that is made is about the poor type system and the author cited this code snippet which prints null:
void main() {
String s = null;
if (s is String) {
print("string");
} else if (s is Null) {
print("null");
} else {
print ("none");
}
}
The is keyword was new to me but "The Dart Programming Language" by Gilad pointed out that is checks the interface implemented by an object's class and not the actual class of an object.
However this didn't help me much because I would think that the variable s is an instance of String and therefore implements String, but the evidence is to the contrary.
I get that the class is not required when defining objects/variables in Dart, and thus I started to wonder if putting the class in the definition just serves as sugar and has little functional purpose. But instead the class of an object/variable is completely determined by its value, and since the default value for all variables in Dart is null, then it would make sense that String is not implemented, but Null is. Is this the case? Am I way of base? Maybe someone could help me wrap my head around this.
The reason is that is checks the interface of the current object itself and not the reference to this object. So yes, s can point to a String object but also allowed to point to null which are a instance of Null: https://api.dart.dev/stable/2.7.2/dart-core/Null-class.html
Since Null does not implement the String interface, this will return false (null is String). This is also mentioned in the article.
The problem the article are trying to focus on are more the fact you are allowed to set the String variable to null value but Null does not implement String.
Well, in the future, this problem are going to be fixed with non-nullable types which are in development right now. When this is implemented you can actually define variables where you can be sure the value will never be null.
So I continued my Dart reading and I came to a better understanding, and that is that Dart is truly optionally typed and that means 2 things:
Type are syntactically optional.
Type has no impact on runtime semantics.
Therefore the actual type annotation of a variable in Dart only serves documentation purposes and it cannot be assumed that a type annotation is true. The actual type of a variable is wholly determined by the value stored at this variable, and in this case it is null.
In truth the variable that I defined in my example is not a String variable or an implementer of the String interface. It is just annotated that it may be/should be/most likely is a string.
I'm trying to pass a type in order to make use of the type information, but that types doesn't appear to be pass through.
I went back to the docs to double check that Dart generics are in fact reified and according to the docs, they are:
I call hydrate on a response which morphs the content of response object:
response.hydrate<BoqVO>();
I'm expecting T to be of type BoqVO:
class Response {
...
void hydrate<T>() {
print(T.runtimeType); // always prints _Type
if (T is BoqVO) {
print("IF");
} else {
print("ELSE"); // always goes into ELSE block
}
}
...
}
... but it's not.
Replacing response.hydrate<BoqVO>(); with response.hydrate(new BoqVO()); and changing the method signature to
void hydrate(T t) {
works if i now use lowercase t, but one shouldn't have to instantiate the object in order for reified generics to be available.
Any ideas why Dart is doing this or what i'm missing for reified generics to work correctly?
PS: I'm not on Dart 2 yet, currently on Dart 1.24.3
As Günther Zöchbauer has said, the type parameter doesn't work in Dart 1.24.
The following explains what would happen if you tried the same code in Dart 2.0, where it would also not work, because it uses the type parameter incorrectly.
The code T.runtimeType treats T as an expression. When a type, including a type parameter, is used as an expression, it evaluates to an instance of the class Type. What you print is the runtime type of that Type object (where _Type is an internal platform implementation of Type).
To print the real type, just print(T) (that still converts T to a Type object, but one representing the type BoqVO and with a toString that includes the BoqVO name).
Likewise for T is BoqVO, you evaluate T to a Type object, and since Type doesn't implement BoqVO, that test is always false. There is no simple way to test if the type of a type parameter implements a specific other type, but you can hack around it as <T>[] is List<BoqVO>.
Generic collections were supported from the beginning and they got some type support, but generic methods were only experimental in Dart 1 and reified type parameters were only added in Dart 2 pre releases.