Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest null-check patterns for type promo #5679

Merged
merged 11 commits into from
Apr 8, 2024
13 changes: 10 additions & 3 deletions examples/misc/lib/effective_dart/usage_good.dart
Original file line number Diff line number Diff line change
Expand Up @@ -350,24 +350,31 @@ class Response {
String get reason => '';
}

// #docregion shadow-nullable-field
// #docregion shadow-nullable-field, null-check-promo
class UploadException {
final Response? response;

UploadException([this.response]);

@override
String toString() {
// #enddocregion shadow-nullable-field
if (this.response case var response?) {
return 'Could not complete upload to ${response.url} '
'(error code ${response.errorCode}): ${response.reason}.';
}
// #enddocregion null-check-promo
// #docregion shadow-nullable-field
final response = this.response;
if (response != null) {
return 'Could not complete upload to ${response.url} '
'(error code ${response.errorCode}): ${response.reason}.';
}

// #docregion null-check-promo
return 'Could not upload (no response).';
}
}
// #enddocregion shadow-nullable-field
// #enddocregion shadow-nullable-field, null-check-promo

//----------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion src/content/effective-dart/_toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ the project:
* <a href='/effective-dart/usage#dont-use-an-explicit-default-value-of-null'>DON'T use an explicit default value of <code>null</code>.</a>
* <a href='/effective-dart/usage#dont-use-true-or-false-in-equality-operations'>DON'T use <code>true</code> or <code>false</code> in equality operations.</a>
* <a href='/effective-dart/usage#avoid-late-variables-if-you-need-to-check-whether-they-are-initialized'>AVOID <code>late</code> variables if you need to check whether they are initialized.</a>
* <a href='/effective-dart/usage#consider-assigning-a-nullable-field-to-a-local-variable-to-enable-type-promotion'>CONSIDER assigning a nullable field to a local variable to enable type promotion.</a>
* <a href='/effective-dart/usage#consider-type-promotion-or-null-check-patterns-for-using-nullable-types'>CONSIDER type promotion or null-check patterns for using nullable types.</a>

**Strings**

Expand Down
48 changes: 37 additions & 11 deletions src/content/effective-dart/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ prevpage:
url: /effective-dart/documentation
title: Documentation
---
<?code-excerpt plaster="none"?>
<?code-excerpt replace="/([A-Z]\w*)\d\b/$1/g"?>
<?code-excerpt path-base="misc/lib/effective_dart"?>

Expand Down Expand Up @@ -325,7 +326,7 @@ Of course, if `null` is a valid initialized value for the variable,
then it probably does make sense to have a separate boolean field.


### CONSIDER assigning a nullable field to a local variable to enable type promotion
### CONSIDER type promotion or null-check patterns for using nullable types

Checking that a nullable variable is not equal to `null` promotes the variable
to a non-nullable type. That lets you access members on the variable and pass it
Expand All @@ -337,7 +338,30 @@ private final fields. Values that are open to manipulation

Declaring members [private][] and [final][], as we generally recommend, is often
enough to bypass these limitations. But, that's not always an option.
One pattern to work around this is to assign the field's value

One pattern to work around type promotion limitations is to use a
[null-check pattern][] to simultaneously confirm the member's value is not null,
and bind that value to a new non-nullable variable of the same base type.

<?code-excerpt "usage_good.dart (null-check-promo)"?>
```dart tag=good
class UploadException {
final Response? response;

UploadException([this.response]);

@override
String toString() {
if (this.response case var response?) {
return 'Could not complete upload to ${response.url} '
'(error code ${response.errorCode}): ${response.reason}.';
}
return 'Could not upload (no response).';
}
}
```

Another work around is to assign the field's value
to a local variable. Null checks on that variable will promote,
so you can safely treat it as non-nullable.

Expand All @@ -355,14 +379,21 @@ class UploadException {
return 'Could not complete upload to ${response.url} '
'(error code ${response.errorCode}): ${response.reason}.';
}

return 'Could not upload (no response).';
}
}
```

Assigning to a local variable can be cleaner and safer than using `!` every
time you need to treat the value as non-null:
Be careful when using a local variable. If you need to write back to the field,
make sure that you don't write back to the local variable instead. (Making the
local variable [`final`][] can prevent such mistakes.) Also, if the field might
change while the local is still in scope, then the local might have a stale
value.

Sometimes it's best to simply [use `!`][] on the field.
In some cases, though, using either a local variable or a null-check pattern
can be cleaner and safer than using `!` every time you need to treat the value
as non-null:

<?code-excerpt "usage_bad.dart (shadow-nullable-field)" replace="/!\./[!!!]./g"?>
```dart tag=bad
Expand All @@ -383,15 +414,10 @@ class UploadException {
}
```

Be careful when using a local variable. If you need to write back to the field,
make sure that you don't write back to the local variable instead. (Making the
local variable [`final`][] can prevent such mistakes.) Also, if the field might
change while the local is still in scope, then the local might have a stale
value. Sometimes it's best to simply [use `!`][] on the field.

[can't be type promoted]: /tools/non-promotion-reasons
[private]: /effective-dart/design#prefer-making-declarations-private
[final]: /effective-dart/design#prefer-making-fields-and-top-level-variables-final
[null-check pattern]: /language/pattern-types#null-check
[`final`]: /effective-dart/usage#do-follow-a-consistent-rule-for-var-and-final-on-local-variables
[use `!`]: /null-safety/understanding-null-safety#non-null-assertion-operator

Expand Down
2 changes: 1 addition & 1 deletion src/content/guides/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ we made the following changes to this site:
* Reorganized and simplified site infrastructure across the board, in preparation
to [move away from using Jekyll][].

[type promotion]: /effective-dart/usage#consider-assigning-a-nullable-field-to-a-local-variable-to-enable-type-promotion
[type promotion]: /effective-dart/usage#consider-type-promotion-or-null-check-patterns-for-using-nullable-types
[Understanding Null Safety]: /null-safety/understanding-null-safety
[C interop]: /interop/c-interop#native-assets
[Breaking changes]: /resources/breaking-changes
Expand Down