← Dart EnglishChapter 10 of 13

Null Safety

## Learning Objectives - Understand null safety in Dart - Master nullable types - Use null-aware operators - Work with late variables ## Why Null Safety? ### The Problem ```dart // Without null safety String? name; // Can be null print(name.length); // Runtime error if null! ``` ### Dart's Solution ```dart // With null safety - caught at compile time String name = 'Alice'; // Cannot be null // name = null; // Error! String? optional; // Nullable optional = null; // OK // print(optional.length); // Error! Can't access without null check ``` ## Nullable Types ### Basic Syntax ```dart void main() { // Non-nullable (must have value) String name = 'Alice'; // Nullable (can be null) String? nullable; nullable = null; // OK nullable = 'Bob'; // OK } ``` ### Null Check Required ```dart void main() { String? nullable; // Error: nullable can't be assigned to non-nullable // String mustNotBeNull = nullable; // Solution 1: Check for null if (nullable != null) { String definitelyNotNull = nullable; // OK print(definitelyNotNull.length); } // Solution 2: Use nullable directly with operators print(nullable?.length); // null (safe access) } ``` ## Null-Aware Operators ### Null-aware Access (?.) ```dart void main() { String? name; print(name?.length); // null (doesn't throw) name = 'Alice'; print(name?.length); // 5 } ``` ### Null-aware Assignment (??=) ```dart void main() { String? name; name ??= 'Default'; // Only assigns if null print(name); // Default name = 'Alice'; name ??= 'Default'; // No effect since not null print(name); // Alice } ``` ### Null-aware Index ([]?) ```dart void main() { List? numbers = [1, 2, 3]; print(numbers?[0]); // 1 numbers = null; print(numbers?[0]); // null } ``` ### If-null Operator (??) ```dart void main() { String? name; // Returns left if not null, otherwise right String displayName = name ?? 'Guest'; print(displayName); // Guest name = 'Alice'; displayName = name ?? 'Guest'; print(displayName); // Alice } ``` ### Null-aware Method Invocation ```dart void main() { String? text; // Only invokes if not null text?.toUpperCase(); // null text = 'hello'; text?.toUpperCase(); // HELLO } ``` ## Force Unwrap (!) ### When to Use ```dart void main() { String? name = 'Alice'; // Use ! when you're certain it's not null // Only use if you've checked or know it's guaranteed print(name!.length); // 5 // Danger! String? danger; // print(danger!.length); // Runtime error! } ``` ### Pattern for Known Non-null ```dart void main() { String? compute() { // ... complex logic that always returns non-null return 'result'; } String result = compute()!; // Safe if you trust the logic print(result.length); } ``` ## Late Variables ### Basic Usage ```dart late String description; void init() { description = 'Initialized'; } void main() { // print(description); // Error if accessed before init() init(); print(description); // Initialized } ``` ### With Final ```dart late final String config; void load() { config = 'Loaded'; } void main() { load(); print(config); // Loaded } ``` ### Lazy Initialization ```dart late String expensive = compute(); String compute() { print('Computing...'); return 'Result'; } void main() { // expensive not computed yet print('Before access'); print(expensive); // Computing... Result } ``` ### Use Cases ```dart class User { late String displayName; User(Map data) { // Expensive operation deferred displayName = data['name'] ?? 'Unknown'; } } ``` ## Null Safety in Functions ### Parameters ```dart // Nullable parameter void greet(String? name) { if (name != null) { print('Hello, $name'); } } // Required non-null void greetRequired(String name) { print('Hello, $name'); } void main() { greet(null); // OK greet('Alice'); // OK // greetRequired(null); // Error! greetRequired('Alice'); // OK } ``` ### Return Types ```dart // Nullable return String? findName() { return 'Alice'; // or return null; } // Non-null return (must always return value) String findGreeting() { return 'Hello'; } ``` ## Null Safety in Classes ### Nullable Fields ```dart class User { String name; String? email; // Nullable User(this.name, [this.email]); } void main() { var user = User('Alice', 'alice@email.com'); print(user.email); // alice@email.com var user2 = User('Bob'); print(user2.email); // null } ``` ### Late Fields ```dart class DataService { late String data; Future load() async { data = await fetchData(); } } ``` ### Nullable Fields with Late ```dart class Config { late String? cachedData; void load() { cachedData = fetch(); } String? get() { return cachedData; } } ``` ## Null Safety in Collections ### Nullable Elements ```dart void main() { // List of nullable strings List items = ['a', null, 'b', null, 'c']; for (var item in items) { if (item != null) { print(item.toUpperCase()); } } } ``` ### Nullable Collection ```dart void main() { // Nullable list List? nullableList; print(nullableList?.length); // null nullableList = [1, 2, 3]; print(nullableList?.length); // 3 } ``` ## Null Safety in Generics ### Nullable Type Arguments ```dart void main() { // List - list of nullable strings List nullableStrings = ['a', null, 'b']; // List? - nullable list of strings List? optionalList; // Map - nullable keys and values Map mixed = {null: 0, 'a': 1}; } ``` ## Dart 3 Non-nullable by Default ### Required Named Parameters ```dart void createUser({ required String name, String? email, // Optional int age = 0, }) { print('$name, $email, $age'); } void main() { createUser(name: 'Alice'); // OK // createUser(); // Error: name required } ``` ## Best Practices ### 1. Prefer Non-nullable Types ```dart // Bad String? name = 'Alice'; // Good String name = 'Alice'; ``` ### 2. Use Late Sparingly ```dart // Only when necessary late String data; // Better if you can use constructor Data(this.data); // preferred ``` ### 3. Use Null Checks ```dart // Before accessing nullable if (value != null) { value.someMethod(); } // Or use safe operators value?.someMethod(); ``` ### 4. Provide Defaults ```dart // With ?? operator String display = name ?? 'Unknown'; // With ??= for mutable name ??= 'Default'; ``` ## Summary - Dart is null-safe by default - `?` suffix makes type nullable - `?.` safe property access - `??` if-null operator - `??=` null-aware assignment - `!` force unwrap (use carefully) - `late` defers initialization - Null safety prevents runtime null errors at compile time

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →