diff --git a/firebase.json b/firebase.json index 30efbdfe5b..dee896c35b 100644 --- a/firebase.json +++ b/firebase.json @@ -82,6 +82,7 @@ { "source": "/cl", "destination": "https://dart-review.googlesource.com/q/status:open+-is:wip", "type": 301 }, { "source": "/cl/:rest*", "destination": "https://dart-review.googlesource.com/c/sdk/+/:rest*", "type": 301 }, { "source": "/cloud{,/**}", "destination": "/server/google-cloud", "type": 301 }, + { "source": "/codelabs/null-safety{,/**}", "destination": "/null-safety/understanding-null-safety", "type": 301 }, { "source": "/codelabs/server{,/**}", "destination": "/tutorials/server/httpserver", "type": 301 }, { "source": "/code-of-conduct", "destination": "/community/code-of-conduct", "type": 301 }, { "source": "/concurrency", "destination": "/language/concurrency", "type": 301 }, @@ -225,7 +226,7 @@ { "source": "/mailing-list", "destination": "https://groups.google.com/a/dartlang.org/forum/#!forum/misc", "type": 301 }, { "source": "/mobile", "destination": "/multiplatform-apps", "type": 301 }, { "source": "/news{,/**}", "destination": "https://medium.com/dartlang", "type": 301 }, - { "source": "/null-safety/tour", "destination": "/codelabs/null-safety", "type": 301 }, + { "source": "/null-safety/tour", "destination": "/null-safety/understanding-null-safety", "type": 301 }, { "source": "/observatory{,/**}", "destination": "/tools/dart-devtools", "type": 301 }, { "source": "/packages", "destination": "https://pub.dev", "type": 301 }, { "source": "/platforms", "destination": "/overview#platform", "type": 301 }, diff --git a/src/_data/side-nav.yml b/src/_data/side-nav.yml index ff51ddce14..9c655c3183 100644 --- a/src/_data/side-nav.yml +++ b/src/_data/side-nav.yml @@ -16,8 +16,6 @@ permalink: /codelabs/iterables - title: Asynchronous programming permalink: /codelabs/async-await - - title: Null safety - permalink: /codelabs/null-safety - title: Language expanded: false children: diff --git a/src/content/codelabs/index.md b/src/content/codelabs/index.md index b2fdb75db8..8d0c82f9b5 100644 --- a/src/content/codelabs/index.md +++ b/src/content/codelabs/index.md @@ -31,10 +31,6 @@ Discover Dart 3's new records and patterns features. Learn how you can use them in a Flutter app to help you write more readable and maintainable Dart code. -### [Null safety](/codelabs/null-safety) - -Use DartPad to learn about Dart's null-safe type system. - ## Flutter To learn about Flutter, try one of the diff --git a/src/content/codelabs/null-safety.md b/src/content/codelabs/null-safety.md deleted file mode 100644 index cb2965973b..0000000000 --- a/src/content/codelabs/null-safety.md +++ /dev/null @@ -1,554 +0,0 @@ ---- -title: Null safety codelab -description: Learn about and practice writing null-safe code in DartPad! -js: [{url: 'https://dartpad.dev/inject_embed.dart.js', defer: true}] ---- -{% assign useIframe = false -%} - - - -This codelab teaches you about Dart's [null-safe type system](/null-safety). -Dart introduced null safety as an optional setting in Dart 2.12. -Dart 3 requires null safety. -With null safety, values can't be `null` unless you say they can be. - -This codelab covers the following material: - -* Nullable and non-nullable types. -* When to add `?` or `!` to indicate nullability or non-nullability. -* Flow analysis and type promotion. -* How and when to use null-aware operators. -* How the `late` keyword affects variables and initialization. - -Using embedded DartPad editors, you can test your knowledge by -completing and running exercises. To get the most out of -this codelab, you should have some knowledge of [basic Dart syntax](/language). - -:::note -This page uses embedded DartPads to display exercises. -{% include 'dartpads-embedded-troubleshooting.md' %} -::: - - -## Nullable and non-nullable types - -With [null safety](/null-safety#enable-null-safety), all types default -to non-nullable. For example, if you have a variable of -type `String`, it always contains a string. - -To allow a variable of type `String` to accept any string -or the value `null`, add a question mark (`?`) after the type name. -This changes the type of variable to a nullable type. -For example, a variable of type `String?` can contain a string, -or it can be null. - -### Exercise: Non-nullable types - -In the following example, the developer declared variable `a` an `int`. -Try changing the value in the assignment to `3` or `145`, but not `null`! - - -```dart:run-dartpad:ga_id-nonnullable_type -void main() { - int a; - a = null; - print('a is $a.'); -} -``` - -### Exercise: Nullable types - -What if you need a variable that *can* hold a null value? -Try changing the type of `a` so that `a` can be either `null` or an `int`: - - -```dart:run-dartpad:ga_id-nullable_type -void main() { - int a; - a = null; - print('a is $a.'); -} -``` - -### Exercise: Nullable type parameters for generics - -Type parameters for generics can also be nullable or non-nullable. -Try adding question marks to correct the type declarations of -`aNullableListOfStrings` and `aListOfNullableStrings`: - - -```dart:run-dartpad:ga_id-nullable_type_generics -void main() { - List aListOfStrings = ['one', 'two', 'three']; - List aNullableListOfStrings; - List aListOfNullableStrings = ['one', null, 'three']; - - print('aListOfStrings is $aListOfStrings.'); - print('aNullableListOfStrings is $aNullableListOfStrings.'); - print('aListOfNullableStrings is $aListOfNullableStrings.'); -} -``` - - -## The non-null assertion operator (!) - -If you're sure an expression with a nullable type doesn't equal `null`, -you can use the [non-null assertion operator][] -(`!`) to make Dart treat it as non-nullable. -By adding `!` after the expression, -you assert two conditions to Dart about the expression: - -1. Its value doesn't equal `null` -2. Dart can assign the value to a non-nullable variable - -:::warning -If the expression does equal `null`, **Dart throws an exception at run-time**. -This makes the `!` operator _unsafe_. -Don't use it unless you have no doubt the expression can't equal `null`. -::: - -[non-null assertion operator]: /null-safety/understanding-null-safety#non-null-assertion-operator - -### Exercise: Null assertion - -In the following code, try adding exclamation points to correct the -broken assignments: - - -```dart:run-dartpad:ga_id-null_assertion -int? couldReturnNullButDoesnt() => -3; - -void main() { - int? couldBeNullButIsnt = 1; - List listThatCouldHoldNulls = [2, null, 4]; - - int a = couldBeNullButIsnt; - int b = listThatCouldHoldNulls.first; // first item in the list - int c = couldReturnNullButDoesnt().abs(); // absolute value - - print('a is $a.'); - print('b is $b.'); - print('c is $c.'); -} -``` - -## Null-aware operators - -If a variable or expression is nullable, -you can use [type promotion](#type-promotion) -to access the type's members. -You can also use null-aware operators to handle nullable values. - -Sometimes the flow of the program tells you that the value of an -expression cannot be `null`. -To force Dart to treat that expression as non-nullable, -add the [non-null assertion operator](#the-non-null-assertion-operator) (`!`). -If the value does equal `null`, using this operator throws an exception. - -To handle potential `null` values, use the conditional property access -operator (`?.`) or null-coalescing operators (`??`) -to conditionally access a property or -provide a default value if `null` respectively. - - -### Exercise: Conditional property access - -If you don't know that an expression with a nullable type equals `null` or not, -you can use the conditional member access operator (`?.`). -This operator evaluates to `null` if the target expression resolves to `null`. -Otherwise, it accesses the property on the non-null target value. - - -```dart -// The following calls the 'action' method only if nullableObject is not null -nullableObject?.action(); -``` - -In the following code, try using conditional property access -in the `stringLength` method. This fixes the error and -returns the length of the string or `null` if it equals `null`: - -```dart:run-dartpad:ga_id-null-safety-conditional-property -{$ begin main.dart $} -int? stringLength(String? nullableString) { - return nullableString.length; -} -{$ end main.dart $} -{$ begin solution.dart $} -int? stringLength(String? nullableString) { - return nullableString?.length; -} -{$ end solution.dart $} -{$ begin test.dart $} -void main() { - const nonNullString = 'testing'; - try { - final nonNullResult = stringLength(nonNullString); - if (nonNullResult != nonNullString.length) { - _result(false, [ - 'Tried calling `stringLength`, with the string \'testing\' but ' - 'received $nonNullResult instead of the expected ${nonNullString.length}.' - ]); - return; - } - - final nullableResult = stringLength(null); - if (nullableResult != null) { - _result(false, [ - 'Tried calling `stringLength`, with a `null` value but ' - 'received $nullableResult instead of the expected `null`.' - ]); - return; - } - - _result(true); - } on UnimplementedError { - _result(false, [ - 'Tried running `stringLength`, but received an error. Did you implement the method?' - ]); - return; - } catch (e) { - _result( - false, ['Tried calling `stringLength`, but received an exception: $e']); - } -} -{$ end test.dart $} -{$ begin hint.txt $} -You can use the conditional property access operator (?.) -to only access a property of if expression is not null otherwise return null. -{$ end hint.txt $} -``` - -### Exercise: Null-coalescing operators - -If you want to provide an alternative value -when the expression evaluates to `null`, -you can specify another expression to evaluate and return instead -with the null-coalescing operator (`??`). - - -```dart -// Both of the following print out 'alternate' if nullableString is null -print(nullableString ?? 'alternate'); -print(nullableString != null ? nullableString : 'alternate'); -``` - -You can also use the null-coalescing assignment operator (`??=`) -to evaluate and assign an expression result to a variable -only if that variable is currently `null`. - - -```dart -// Both of the following set nullableString to 'alternate' if it is null -nullableString ??= 'alternate'; -nullableString = nullableString != null ? nullableString : 'alternate'; -``` - -In the following code, try using these operators to implement -`updateStoredValue` following the logic outlined in its documentation comment: - -```dart:run-dartpad:ga_id-null-safety-coalescing-operators -{$ begin main.dart $} -abstract class Store { - int? storedNullableValue; - - /// If [storedNullableValue] is currently `null`, - /// set it to the result of [calculateValue] - /// or `0` if [calculateValue] returns `null`. - void updateStoredValue() { - TODO('Implement following documentation comment'); - } - - /// Calculates a value to be used, - /// potentially `null`. - int? calculateValue(); -} -{$ end main.dart $} -{$ begin solution.dart $} -abstract class Store { - int? storedNullableValue; - - /// If [storedNullableValue] is currently `null`, - /// set it to the result of [calculateValue] - /// or `0` if [calculateValue] returns `null`. - void updateStoredValue() { - storedNullableValue ??= calculateValue() ?? 0; - } - - /// Calculates a value to be used, - /// potentially `null`. - int? calculateValue(); -} -{$ end solution.dart $} -{$ begin test.dart $} -class NullStore extends Store { - @override - int? calculateValue() { - return null; - } -} - -class FiveStore extends Store { - @override - int? calculateValue() { - return 5; - } -} - -void main() { - try { - final nullStore = NullStore(); - if (nullStore.storedNullableValue != null) { - _result(false, - ['The `storedNullableValue` field should be `null` at first.']); - return; - } - nullStore.updateStoredValue(); - if (nullStore.storedNullableValue != 0) { - _result(false, [ - 'Tried calling `updateStoredValue`, when `calculateValue` returned `null` ' - 'but `storedNullableValue` was ${nullStore.storedNullableValue} ' - 'instead of the expected 0.' - ]); - return; - } - - final fiveStore = FiveStore(); - fiveStore.updateStoredValue(); - if (fiveStore.storedNullableValue != 5) { - _result(false, [ - 'Tried calling `updateStoredValue`, when `calculateValue` returned `5`' - 'but `storedNullableValue` was ${fiveStore.storedNullableValue} ' - 'instead of the expected 5.' - ]); - return; - } - - fiveStore.storedNullableValue = 3; - if (fiveStore.storedNullableValue != 3) { - _result(false, [ - 'Tried calling `updateStoredValue`, when `storedNullableValue` ' - 'was already not `null`' - 'but `storedNullableValue` was still updated when it shouldn\'t be.' - ]); - return; - } - - _result(true); - } on UnimplementedError { - _result(false, [ - 'Tried running `updateStoredValue`, but received an error. Did you implement the method?' - ]); - return; - } catch (e) { - _result(false, - ['Tried calling `updateStoredValue`, but received an exception: $e']); - } -} -{$ end test.dart $} -{$ begin hint.txt $} -You can think of the null-coalescing operators -as providing an alternative value if the left-hand side is `null`. -{$ end hint.txt $} -``` - -## Type promotion - -Dart's [flow analysis](/null-safety/understanding-null-safety#flow-analysis) -accounts for nullability. -Dart treats nullable variables and fields -with no ability to contain null values as non-nullable. -We call this behavior -[type promotion](/null-safety/understanding-null-safety#type-promotion-on-null-checks). - -### Exercise: Definite assignment - -Dart's type system can track where variables are assigned and read. -It can also verify that the developer assigned values to non-nullable variables -before any code tries to read from those variables. -This process is called -[definite assignment](/null-safety/understanding-null-safety#definite-assignment-analysis). - -Try uncommenting the `if`-`else` statement in the following code. -Watch the analyzer errors disappear: - - -```dart:run-dartpad:ga_id-definite_assignment -void main() { - String text; - - //if (DateTime.now().hour < 12) { - // text = "It's morning! Let's make aloo paratha!"; - //} else { - // text = "It's afternoon! Let's make biryani!"; - //} - - print(text); - print(text.length); -} -``` - -### Exercise: Null checking - -In the following code, add an `if` statement to the beginning of -`getLength` that returns zero if `str` is `null`: - - -```dart:run-dartpad:ga_id-null_checking -int getLength(String? str) { - // Add null check here - - return str.length; -} - -void main() { - print(getLength('This is a string!')); -} -``` - -### Exercise: Promotion with exceptions - -Promotion works with exceptions as well as return statements. -Try a null check that throws an `Exception` instead of returning zero. - - -```dart:run-dartpad:ga_id-promotion_exceptions -int getLength(String? str) { - // Try throwing an exception here if `str` is null. - - return str.length; -} - -void main() { - print(getLength(null)); -} -``` - -## The late keyword - -Sometimes variables—fields in a class, or top-level variables—should -be non-nullable, but they can't be -assigned a value immediately. -For cases like that, use the -[`late` keyword](/null-safety/understanding-null-safety#late-variables). - -When you put `late` in front of a variable declaration, -that tells Dart the following about the variable: - -- The developer didn't want to assign it a value yet. -- It will get a value later. -- It will have a value _before_ being used. - -If you declare a variable `late` and Dart reads the variable before -you assigned a value, Dart throws an error. - -### Exercise: Using late - -Try using the `late` keyword to correct the following code. -For a little extra fun afterward, -try commenting out the line that sets `description`! - - -```dart:run-dartpad:ga_id-late_keyword -class Meal { - String _description; - - set description(String desc) { - _description = 'Meal description: $desc'; - } - - String get description => _description; -} - -void main() { - final myMeal = Meal(); - myMeal.description = 'Feijoada!'; - print(myMeal.description); -} -``` - -### Exercise: Late circular references - -The `late` keyword helps with tricky patterns like circular references. -The following code has two objects that need to maintain non-nullable references -to each other. Try using the `late` keyword to fix this code. - -You don't need to remove `final`. You can create -[`late final` variables](/null-safety/understanding-null-safety#late-final-variables): -you set their values once, and after that they stay read-only. - - -```dart:run-dartpad:ga_id-late_circular -class Team { - final Coach coach; -} - -class Coach { - final Team team; -} - -void main() { - final myTeam = Team(); - final myCoach = Coach(); - myTeam.coach = myCoach; - myCoach.team = myTeam; - - print('All done!'); -} -``` - -### Exercise: Late and lazy - -The `late` keyword can help with another pattern: -[lazy initialization](/null-safety/understanding-null-safety#lazy-initialization) -for expensive non-nullable fields. -Try the following: - -
    -
  1. Run this code without changing it, and note the output.
  2. -
  3. Think: What will change if - you make _cache a late field?
  4. -
  5. Make _cache a late field, and run the code. - Was your prediction correct?
  6. -
- - -```dart:run-dartpad:ga_id-lazy_late -int _computeValue() { - print('In _computeValue...'); - return 3; -} - -class CachedValueProvider { - final _cache = _computeValue(); - int get value => _cache; -} - -void main() { - print('Calling constructor...'); - var provider = CachedValueProvider(); - print('Getting value...'); - print('The value is ${provider.value}!'); -} -``` - -:::note Fun fact -After you add `late` to the declaration of `_cache`, -if you move the `_computeValue` function into the -`CachedValueProvider` class, the code still works! -Initialization expressions for `late` fields can use instance -methods in their initializers. -::: - - -## What's next? - -Congratulations, you've finished the codelab! -To learn more, check out some suggestions for where to go next: - -* Learn more about null safety - * [Overview of null safety](/null-safety). - * [Deep dive into understanding null safety](/null-safety/understanding-null-safety). -* If you want to improve this codelab, check out [issue #3093][]. - -[issue #3093]: https://github.com/dart-lang/site-www/issues/3093 diff --git a/src/content/guides/whats-new.md b/src/content/guides/whats-new.md index 0c29e62c07..1f28ddfe15 100644 --- a/src/content/guides/whats-new.md +++ b/src/content/guides/whats-new.md @@ -693,7 +693,7 @@ we made the following changes to this site: links to helpful documentation and samples. * The [command-line tutorial][] has been completely updated. * Published some other new pages: - * [Null safety codelab][] teaches you about Dart's null-safe type system, + * Null safety codelab that teaches you about Dart's null-safe type system, which was introduced in Dart 2.12. * [Numbers in Dart][] has details about differences between native and web number implementations. @@ -722,7 +722,6 @@ we made the following changes to this site: [get Dart]: /get-dart [HTTP server tutorial]: /tutorials/server/httpserver [`lints`]: {{site.pub-pkg}}/lints -[Null safety codelab]: /codelabs/null-safety [Numbers in Dart]: /guides/language/numbers [streams tutorial]: /tutorials/language/streams [typedef section]: /language/typedefs diff --git a/src/content/null-safety/index.md b/src/content/null-safety/index.md index 7dfb921069..7a0c39227f 100644 --- a/src/content/null-safety/index.md +++ b/src/content/null-safety/index.md @@ -28,7 +28,8 @@ flag if a non-nullable variable has either: * Not been initialized with a non-null value * Been assigned a `null` value. -These checks allows you to fix these errors _before_ deploying your app. + +These checks allow you to fix these errors _before_ deploying your app. ## Introduction through examples @@ -49,9 +50,10 @@ just add `?` to its type declaration: int? aNullableInt = null; ``` -- To try an interactive example, - see the [null safety codelab][Null safety codelab]. -- To learn more about this topic, see +- To try some interactive examples, + try out some of the null-safety orientated examples in the + [Dart cheatsheet](/codelabs/dart-cheatsheet). +- To learn more about null safety, check out [Understanding null safety](/null-safety/understanding-null-safety). @@ -176,7 +178,6 @@ check out the [migration guide][]. To learn more about null safety, check out the following resources: -* [Null safety codelab][] * [Understanding null safety][] * [Migration guide for existing code][migration guide] * [Null safety FAQ][] @@ -185,7 +186,6 @@ To learn more about null safety, check out the following resources: [calculate_lix]: https://github.com/dart-lang/samples/tree/main/null_safety/calculate_lix [migration guide]: /null-safety/migration-guide [Null safety FAQ]: /null-safety/faq -[Null safety codelab]: /codelabs/null-safety [Understanding null safety]: /null-safety/understanding-null-safety [#34233]: https://github.com/dart-lang/sdk/issues/34233 [#49529]: https://github.com/dart-lang/sdk/issues/49529