Why default value of an optional parameter must be a constant? - dart

class Foo {
final DateTime date;
static final DateTime defDate = DateTime.now();
Foo([this.date = defDate]); // Error
}
What's wrong in this code, I am providing a static final value as the default value to the optional parameter, but it isn't acceptable by Dart, can anyone please explain this behavior?
Edit:
// Global field
final DateTime defDate = DateTime.now();
class A {
void a([DateTime i = defDate]) => a; // Shouldn't have an error
}
class B extends A {
#override
void a([DateTime i = defDate]) => a; // Shouldn't have an error
}

Dart default values must be compile-time constant for a number of reasons.
First of all, the default value is considered part of the signature of instance methods.
Subclasses must override a method with parameters taking the same default value. That is meaningless if the value isn't known at compile-time.
More importantly is that there is not a single obvious time when the expression should be evaluated. Dart deliberately avoids evaluating anything before starting main to reduce startup latency. The only exception is compile-time constants because they can be evaluated entirely at compile-time.
That means that when you all a function with a default value the first time, the default value would not have been evaluated yet. It would definitely need to be evaluated at that point, at least if you omit an argument for the parameter, so the compiler might have to evaluate an arbitrary expression in the middle of a function call. Apart from the extra overhead, which is a problem by itself, the real issue is that it makes the code unpredictable.
In your example, the default value would be the DateTime.now() that happened to be the time when the function was first called.
The alternative would be evaluating the default value expression every time the function is called without an argument for that parameter. The Dart language team are considering that option (as a potential future move, no real current plans), but it would mean dropping the idea that the default value is a part of the signature.
Until the language does such a thing, the default value must be constant.

Related

Difference between Object, Dynamic and Var in Dart?

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).

What is the reason behind not able to use this. keyword and datatypes like string and int in named constructor in Dart

Why in named constructor not able to use datatypes and this keyword? It shows Error:
The parameter 'name' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.dart(missing_default_value_for_parameter)
Your problem is that, if nothing else is specified, the default value for optional named parameters is null. So what your code actually does right now is setting the variable late String name to null which is not allowed because of the type String. If you want to allow null, the type should be String?.
The late keyword is used in situations where you cannot set the variable when initializing the object and is just a promise to the Dart compiler that the variable will have a value when you are going to use the variable at some point. (the compiler will add a check to make sure the variable is actually given a value before use).
But late is not a way to allow null as a valid value.
Another problem in your code is that it does not really make sense to do:
person({this.name}) {
this.name = name;
}
The reason is that {this.name} is already used for setting the variable name. When you later do: this.name = name then both name is pointing to the same variable in your object.
Based on the rest of your code I think you should remove the late keyword and instead use required to make sure the user of your class are specifying your named parameter. So something like this should work (also, please use Person instead of person for naming classes):
class Person {
String name;
int age;
Person({required this.name, required this.age});
void sayName() => print(name);
}
void main() {
final p = Person(name: 'Mathew', age: 24);
p.sayName();
print(p.age);
}
I removed the named constructor Person.age since I don't think it makes much sense to make a Person without a name. If name and age is truly optional, you can instead change the types to String? and int? and remove the required keyword.
This will make it clear when using your class that these two variable can be empty and the user of the class should remember to check for possible null values.
I recommend reading more about Dart null safety feature starting here: https://dart.dev/null-safety

Why is asigning a dynamic to a non-nullable not an error with sound null safety

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.

Can late and final be used together?

I am trying NNBD as of now and I would like to know if you can use the new keyword late and final together.
From what I understood, a late property can be set anywhere. You are basically telling the analyzer that it will not be null when used.
I think that is kinda dangerous in some situations.
So I am wondering if you can add a late final in NNBD, this would tell the analyzer that the property must be initialized within the class constructor.
There is a question similar but I guess there wasn't null safety at the time:
Dart. Late initialize final variables
You can declare a late final variable.
If you declare it with an initializer, late final foo = computeSomething();, then it is a lazy final variable. You can't assign to the variable, but its value is only computed the first time the variable is read. (In my experience, this is never the right choice for local variables, even though the language allows it. If you care about lazy initialization of a local variable, you also almost always want to know whether it was initialized, and a lazy variable doesn't give you that information. It's also confusing that the code is executed out-of-order, and it doesn't allow you to use await in the initializer expression).
If you declare a late final variable without an initializer, you are allowed to write to the variable once. Because the variable is late, the compiler won't complain about assignments at compile-time, unless it's absolutely certain that you have assigned the variable already, and only if it's a local variable (because that's the only variables that the compiler attempts to track assignments to).
If the late final variable without an initializer is an instance member of a class, that means that the class interface has a setter. You need to be very, very careful about exposing late final variables in the public API of a class. (Read: Don't do that!)
It's better to use late variables internally and guard access to the fields, so you can ensure that nobody assigns the variable twice. The goal of a late final variable is not to throw if it's assigned twice. It should never be assigned twice. It's there to allow allow code which knows for some reason that the compiler cannot comprehend, that the variable is only assigning once. So, only allow access to late final variables to code which are aware of that reason, and which maintains the invariant.
Yes!
You can see this pattern commonly used when initializing AnimationController.
class _MyState extends State<MyPage> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
}
And you can use it for lazy initialization, like:
class Foo {
late final int i = calculate; // Initialized only when used.
int get calculate => ...;
}
Short answer: No, you'll not get any help from the analyzer.
From the nnbd language spec:
It is an error if a top level variable or static variable with a
non-nullable type has no initializer expression unless the variable is
marked with a late or external modifier.
It is an error if a class declaration declares an instance variable
with a potentially non-nullable type and no initializer expression,
and the class has a generative constructor where the variable is not
initialized via an initializing formal or an initializer list entry,
unless the variable is marked with a late, abstract, or external
modifier.
late final int foo; basically turns off null awareness for foo. It seems to be the equivalent of using implicitly unwrapped optionals in Swift, which can be hazardous, if you familiar with that.
https://github.com/dart-lang/language/blob/master/accepted/future-releases/nnbd/feature-specification.md
Besides that, the static analyzer does not warn you about trying to reset a late final.
Let D be a late and final local variable declaration named v. It is a run-time error, throwing an instance of LateInitializationError, to assign a value to v if a value has previously been assigned to v.
https://github.com/dart-lang/language/blob/master/accepted/future-releases/nnbd/feature-specification.md#late-fields-and-variables
Using late means you need to know exactly when things are being initialized and used.

Why does an unitialized typed variable in Dart not implement the type's interface?

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.

Resources