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
49 changes: 38 additions & 11 deletions src/content/effective-dart/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,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 work arounds to bypass type promotion limitations

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 +337,31 @@ 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 @@ -350,19 +374,27 @@ class UploadException {

@override
String toString() {
// ···
final response = this.response;
if (response != null) {
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 +415,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
Loading