Dung (Donny) Nguyen

Senior Software Engineer

Nullable

In Dart, a nullable variable is one that can hold a null value, meaning it might have no value at all. This concept is important in languages like Dart that enforce null safety, where variables must either explicitly allow nulls or be guaranteed to hold a non-null value.

Declaring Nullable and Non-Nullable Variables

In Dart, all variables are non-nullable by default. This means that a variable cannot be null unless we explicitly mark it as nullable by adding a question mark ? after its type. Here’s how it works:

int a;       // Non-nullable: cannot hold `null`
int? b;      // Nullable: can hold `null`

In this example:

Checking for Null

To safely access or manipulate a nullable variable, we need to check if it’s null before using it. Dart provides several ways to handle nullable variables:

  1. Using if statements:
    int? b;
    if (b != null) {
      print(b + 5);  // Safe to use `b` here
    }
    
  2. Null-aware operators: Dart includes several operators to work with nullable values efficiently.

    • Null-aware assignment (??=): This assigns a value to a nullable variable only if it’s currently null.
      int? b;
      b ??= 10;   // Assigns 10 to `b` if `b` is null
      print(b);   // Output: 10
      
    • Null-coalescing operator (??): This provides a fallback value if the variable is null.
      int? b;
      int result = b ?? 0;   // Uses 0 if `b` is null
      print(result);         // Output: 0
      
    • Null-aware access (?.): This allows we to access a member of an object only if it isn’t null.
      String? name;
      print(name?.length);   // Safe; returns null if `name` is null
      
  3. Type promotion with if: When we check a nullable variable with an if statement, Dart promotes it to a non-nullable type within that scope, meaning we don’t need extra syntax to use it safely.

    int? b;
    if (b != null) {
      print(b + 5);   // `b` is promoted to non-nullable here
    }
    
  4. The late keyword: If we’re certain a variable will be initialized later but can’t assign a value immediately, we can use late:
    late int a;
    a = 10;
    print(a);   // Output: 10
    

Null Assertion Operator

In addition to the techniques mentioned above, Dart also provides the null assertion operator, denoted by the exclamation mark !. This operator tells the Dart compiler that the variable is guaranteed not to be null at that point in the code.

Here’s an example:

String? name = "John Doe";
print(name!.toUpperCase()); // Output: JOHN DOE

In this example, name is declared as a nullable String. The ! after name tells Dart that we are certain name is not null, so it’s safe to call the toUpperCase() method on it without an additional null check.

However, it’s important to be careful when using the null assertion operator, as it can lead to runtime errors if the variable is actually null at the time of access. If you’re not absolutely sure the variable is not null, it’s generally better to use the null-aware operator ?. or an if statement to handle the null case gracefully.

Example Usage

Here’s an example showing how nullable variables work in practice:

void printName(String? name) {
  // Providing a default name if `name` is null
  print("Hello, ${name ?? 'Guest'}!");
}

void main() {
  String? userName;
  printName(userName);   // Output: Hello, Guest!

  userName = "Alice";
  printName(userName);   // Output: Hello, Alice!
}

In this example, name is a nullable String, so it can hold null. When null is passed to printName, it defaults to “Guest”. When a name is assigned, that name is used instead.

Summary

This null safety system in Dart helps reduce runtime errors by enforcing nullable and non-nullable distinctions at compile-time.