Why Optional Constant Does Not Automatically Have a Default Value of Nil

Why optional constant does not automatically have a default value of nil

Not setting a read-only (constant) field with either an:

  • initialization expression
  • initializer

is almost certainly an indication of an error in your program.

Since you have no other opportunity to set the value of your let field, the value of the field is going to remain nil (or some other default). It is rather unlikely that a programmer would find such behavior desirable, and request it on purpose.

That is why Swift marks this situation as an error. On the other hand, if you actually wanted your String constant to remain nil, you could add an expression to set it to nil, and silence the error:

let owner: String? = nil // Pretty useless, but allowed

Constant unassigned optional will not be nil by default

Yes it's correct

An optional variable doesn't need to be manually initialized. If you read it before having populated it does contain nil.

From Apple docs

If you define an optional variable without providing a default value, the variable is automatically set to nil for you [...]

On the other hand the compiler does force you to manually initialize an Optional constant (let) before you can read it.

Unlike a variable, the value of a constant cannot be changed once it is set. Attempting to do so is reported as an error when your code is compiled [...]

Why?

A constant can be written only once. It doesn't need to happened on the same line it is initialized but it must happened before your read it.

E.g. this code works fine

let num: Int?
num = 1
print(num)

However if the compiler had put a temporary nil value inside num then the constant would have been wrote twice. Which is against the concept of constant.

let num: Int?
print(num) // nil ??? <- this can't work!
num = 1
print(num) // 1

Another example

This code snippet works fine

func printArea(width: Int?, height:Int?) {
let area: Int?
if let width = width, height = height {
area = width * height
} else {
area = nil
}
print(area)
}

Again, if the compiler had put a temporary nil value inside area then...

func printArea(width: Int?, height:Int?) {
let area: Int?
print(area) // not possible! area is going to change in a moment
if let width = width, height = height {
area = width * height
} else {
area = nil
}
print(area)
}

Why do you have to initialize a constant that is declared an optional?

If I don't assign a value to an optional why isn't its value nil and the if let catch it?

It would be, if you initialized it as nil, either like this:

let favoriteSong: String? = nil

Or like this:

let favoriteSong: String?
favoriteSong = nil

But you didn't do either. Thus, because you might still have done the second one, the compiler gives an error when you try to use the uninitialized variable.

Think about it this way: if

 let favoriteSong: String?

...automatically meant

 let favoriteSong: String? = nil

...then it would be impossible to say this:

let favoriteSong: String?
favoriteSong = "Rock Around the Clock"

...because this is a constant — it cannot be changed. But we need to be able to say that! It's legal syntax. Therefore that is not what

 let favoriteSong: String?

...means. Do you see?

The rule for var is different, because it's changeable. Because of that, you get automatic default initialization to nil and can change it later.

But for let, you only get one shot at initialization, so you don't get the automatic default which would prevent you from doing your own initialization in the next line; you have to initialize explicitly.

Are optional parameters in null-safe dart automatically nullable? If no, is there an easy way to make my code null-safe?

In null safe Dart, actual optional parameters must either have a default value, or they must be nullable (in which case they have the default default-value of null).

There is no exception. Because of default values, optional parameters are not inherently nullable.

You should not expect existing code to work with null safety as-is. You are expected to do a migration of the code to be null safe.

The easiest way to make your code null safe is to run dart migrate on your project. It will insert most of the necessary ?s for you.

Default values of an optional parameter must be constant

Try

enum MyEnum { a, b }

class ClassA {
final MyEnum myEnum;
ClassA({this.myEnum});
}

class ClassB {
final ClassA classA;
ClassB({this.classA}); // ClassA expression is underlined with red
}

no need for '=' operator. It will automatically assign the value when you will pass it to the constructor.

Use the '=' operator only when you need to pass a default value to your variables hence, making them optional parameters.

Edit

enum MyEnum { a, b }

class ClassA {
final MyEnum myEnum;
const ClassA({this.myEnum = MyEnum.a});
}

class ClassB {
final ClassA classA;
ClassB({this.classA = const classA()}); // ClassA expression is underlined with red
}

This is the only way i could find to achieve what you want, the constructor should be default

This is called a canonicalized constructor.

Why can't I give a default value as optional parameter except null?

A starting point is that the CLR has no support for this. It must be implemented by the compiler. Something you can see from a little test program:

class Program {
static void Main(string[] args) {
Test();
Test(42);
}
static void Test(int value = 42) {
}
}

Which decompiles to:

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldc.i4.s 42
IL_0002: call void Program::Test(int32)
IL_0007: ldc.i4.s 42
IL_0009: call void Program::Test(int32)
IL_000e: ret
} // end of method Program::Main

.method private hidebysig static void Test([opt] int32 'value') cil managed
{
.param [1] = int32(0x0000002A)
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Program::Test

Note how there is no difference whatsoever between the two call statements after the compiler is done with it. It was the compiler that applied the default value and did so at the call site.

Also note that this still needs to work when the Test() method actually lives in another assembly. Which implies that the default value needs to be encoded in the metadata. Note how the .param directive did this. The CLI spec (Ecma-335) documents it in section II.15.4.1.4

This directive stores in the metadata a constant value associated with method parameter number Int32,
see §II.22.9. While the CLI requires that a value be supplied for the parameter, some tools can use the
presence of this attribute to indicate that the tool rather than the user is intended to supply the value of
the parameter. Unlike CIL instructions, .param uses index 0 to specify the return value of the method,
index 1 to specify the first parameter of the method, index 2 to specify the second parameter of the
method, and so on.




[Note: The CLI attaches no semantic whatsoever to these values—it is entirely up to compilers to
implement any semantic they wish (e.g., so-called default argument values). end note]

The quoted section II.22.9 goes into the detail of what a constant value means. The most relevant part:

Type shall be exactly one of: ELEMENT_TYPE_BOOLEAN, ELEMENT_TYPE_CHAR,
ELEMENT_TYPE_I1, ELEMENT_TYPE_U1, ELEMENT_TYPE_I2, ELEMENT_TYPE_U2,
ELEMENT_TYPE_I4, ELEMENT_TYPE_U4, ELEMENT_TYPE_I8, ELEMENT_TYPE_U8,
ELEMENT_TYPE_R4, ELEMENT_TYPE_R8, or ELEMENT_TYPE_STRING; or
ELEMENT_TYPE_CLASS with a Value of zero

So that's where the buck stops, no good way to even reference an anonymous helper method so some kind of code hoisting trick cannot work either.

Notable is that it just isn't a problem, you can always implement an arbitrary default value for an argument of a reference type. For example:

private void Process(Foo f = null)
{
if (f == null) f = new Foo();

}

Which is quite reasonable. And the kind of code you want in the method instead of the call site.

Why can I have a non optional fields and no initializers

String! is an implicitly unwrapped optional. It will still crash your app if you attempt to access its value when it is nil and you do not expect it. Implicitly unwrapped optionals are useful for variables which you cannot set the value for at initialization but know you will certainly set before use. Relevant:

An implicitly unwrapped optional is a normal optional behind the
scenes, but can also be used like a nonoptional value, without the
need to unwrap the optional value each time it is accessed.

Neither var previewJson: String? nor var previewJson: String! will require a custom init because they're both setting default values of nil.

How to set a default value for an optional positional parameter of type Function?

You can only use constant values (aka. compile-time constants) as default values.
You cannot create a constant function literal, so there is no way to write the function in-line in the constructor.

However, references to top-level or static functions are constants, so you can declare the default value function as a static function or top-level function.

void main() {
Person p = Person();
print(p.foo('Hello')); // Prints "Hello"
}

class Person {
final String Function(String) foo;
Person({this.foo = _identity});
static String _identity(String value) => value;
}
// or as top-level.
// String _identity(String value) => value;

You can (and should) choose to make the function public if the default value is on an instance method, and you expect anyone to extend or implement your class. In that case, they need to declare the same default value.

Another option, which is often at least as useful, is to not use a default value, but replace a null before using the value:

class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = foo ?? _identity;
static String _identity(String value) => value;
}

or even using a non-constant value:

class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = (foo ?? (String x) => x);
}

For a constructor, it makes very little difference. If it was an instance method instead, using ?? to replace null avoids subclasses having to use the exact same function as default value.

Personally I recommend always using ?? instead of a default value. It's more flexible since it allows non-constant values. For non-function default values, you'll have to document the default behavior instead of just letting the dartDoc show {int x = 42}, but for functions, you'll have to document them anyway.



Related Topics



Leave a reply



Submit