Open
Description
I would like a better way to handle optional/null values. I think Swift does this well using the guard statement.
I especially like this feature in order to unwrap optional values, and return early if null.
E.g.
String? name;
guard final nonNullName = name else {
print("name is null. Cannot process");
return;
}
print('name is $nonNullName');
Here are some more examples of how this works in Swift.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
weenzeel commentedon Mar 26, 2021
I'm no expert and there may be edge cases, but do we need this in Dart? I think the way that the Dart compiler is able to analyze the code paths and promote nullable types to proved non-nullable types is quite elegant.
erf commentedon Mar 26, 2021
My experience is that the analyzer can't detect when you have a class or chains with nullable types, like this:
If we had
guard
we could do:Or check for more complex conditions / chains like this:
weenzeel commentedon Mar 26, 2021
You are correct that nullable fields aren't promoted. The reason given in the documentation is that the compiler can't prove that the field won't change its value between the time we check it and the time we use it.
The way to handle this today would be:
This is just a different syntax compared to Swift isn't it? I think the functionality is the same. I tried a small sample in Swift and I'm not allowed to do stuff with the real field without added ceremony there either. All I'm allowed to touch is a promoted local variable, just like in Dart.
erf commentedon Mar 26, 2021
That works, but i rather not have to declare a new variable, on a new line to have the analyzer conclude it is not null. With a guard statement you could do it in a one-liner.
Not sure what you mean with the Swift example (please provide an example), but with the guard statement you would check in run-time and not only analyze the code before hand.
weenzeel commentedon Mar 26, 2021
As a layman it would be interesting to know why the compiler can't prove that the field won't be null though.
mateusfccp commentedon Mar 26, 2021
As far as I could understand your proposal, what you are proposing is similar to #1201 and wouldn't bring any benefit in relation to it.
Consider the following:
If the compiler deems
a.amount
as non-nullable inif (a.amount == null)
it will be clearly incorrect. This doesn't happen only with null-safety but any kind of promotion. You may want to refer to field promotion label.erf commentedon Mar 26, 2021
@mateusfccp I was not aware of that proposal, and it looks like it could solve the simplest cases, but not sure if you be able to unwrap a chain of nullable types like this? Also it seem you must reuse the same variable name, so you can't asign a member of another type to that local variable.
erf commentedon Mar 26, 2021
Maybe this guard solution could be related to destructuring or pattern matching.
lsegal commentedon Apr 10, 2021
@mateusfccp this seems just a little orthogonal to the original question which was specifically about fields. The example above is demonstrating the compiler's inability to determine nullness of a function. Perhaps my nomenclature is a bit off, but based on my understanding, "getter" functions sit atop the actual fields themselves, which means getters are not considered fields-- and presumably the compiler knows this?
If so, surely the compiler can detect when a function is accessed vs. a bare field, at which point presumably we should be able to promote fields without introducing any new syntaxes? Am I wrong about the distinction between fields/accessors?
It just seems pretty inconsistent to me for promotion to only work on variables in a local scope. Even global variables (which are by no means considered fields) do not get promoted?
This behavior breaks some fairly intuitive expectations around what is a variable and what is not. If we were legitimately dealing with method calls, sure, but we "know" (both intuitively and ideally provably so in Dart's AST) that
x
y
andz
above all return values in the exact same way.lrhn commentedon Apr 11, 2021
Dart getters are "functions" in the sense that they can do and return anything. Non-local variable declarations introduce getters which just return the content of the field.
The compiler might be able to detect that some getters won't actually change value between calls, and that it's therefore sound to promote a later access based on an earlier check. However, that is breaking the abstraction. It means that if you ever change any of the implementation details that the compiler used to derive this, it will stop promoting. Any such change becomes a breaking change.
Since you should always be able to change a non-local variable to a getter (and possibly setter) and vice versa, the only safe approach is to not promote non-local variables. It's not sound towards future supposedly non-breaking changes.
eernstg commentedon Apr 12, 2021
@lrhn wrote:
I agree that we should support the encapsulation of properties (such that a non-local variable can be changed to a getter-&-maybe-a-setter). This ensures that the implementer of the property has a certain amount of freedom.
However, I don't see a problem in supporting a different contract with a different trade-off as well: The designer of the property could decide that the property is stable (#1518), which means that the associated getter must return the same value each time it is invoked. The implementer now has less freedom, but clients have more guarantees (in particular, such getters can be promoted). The loss of flexibility only affects variables/getters declared as
stable
, so it's fully controlled by the designer of each declaration, and there is no penalty for non-stable variables/getters.yuukiw00w commentedon May 22, 2024
I would like to emphasize not only the use of
guard
as a syntax for unwrapping null values but also the readability aspect of the code, where it is guaranteed that the scope will be exited if the condition is false, simply by seeing guard.For instance, in Flutter, a common condition check is
context.mounted
.If we could write it as
guard context.mounted else {}
, it would make it easier to recognize thatcontext.mounted
is true below theguard
statement, which is a significant readability advantage.ghost commentedon May 22, 2024
I would just say
if (!context.mounted) return;
. Looks quite readable to me.To be honest, I don't understand the meaning of the verb "guard" here:
According to Webster, "to guard" means "to protect against damage or harm". What kind of harm? Who is protected by whom? Against what threat? And what "else" means in this context? 😄
17 remaining items