null safety (? ! ?? ??=) în Dart

Tipurile nu pot fi null decât dacă au ?; operatorii !, ?? și ??= lucrează cu valorile care pot lipsi.

Sintaxă
String? nume;          // poate fi null
nume!                  // „sigur nu este null” (aruncă dacă este)
nume ?? 'implicit'     // valoare de rezervă dacă e null
nume ??= 'implicit';   // atribuie doar dacă e null

Ce face null safety (? ! ?? ??=)

Din Dart 2.12 (și obligatoriu din Dart 3), un tip obișnuit ca String sau int nu poate fi niciodată null. Dacă o valoare poate lipsi, adaugi ? la tip: String?. Analizorul nu te lasă să apelezi metode pe un String? până nu verifici că nu este null.

După if (x != null), variabila locală x este promovată la tipul fără ?. ?? dă o valoare de rezervă: nume ?? 'anonim'. ??= atribuie doar dacă variabila este null.

! (operatorul „null assertion”) spune „sunt sigur că nu este null”. Dacă greșești, programul aruncă o eroare la rulare. Folosește-l rar, doar când știi ceva ce analizorul nu poate deduce.

Parametri

Parametru Descriere
Tip? Tipul care acceptă și null.
x! Transformă T? în T; aruncă TypeError dacă x este null.
a ?? b a dacă nu este null, altfel b. b se evaluează doar când e nevoie.
a ??= b Atribuie b lui a doar dacă a este null.

Exemple

Exemplul 1: Promovare după verificare

cauta poate întoarce null. După if (tel != null), tel este String și putem apela substring.

final telefoane = {'Ana': '0722111222', 'Radu': '0744333444'};

String? cauta(String nume) => telefoane[nume];

void main() {
  for (final nume in ['Ana', 'Mihai']) {
    final tel = cauta(nume);
    if (tel != null) {
      print('$nume: ${tel.substring(0, 4)}...');
    } else {
      print('$nume nu are telefon salvat.');
    }
  }
}

Rezultat

Ana: 0722...
Mihai nu are telefon salvat.

Exemplul 2: ?? și ??=

?? dă o valoare implicită; ??= completează doar câmpurile care lipsesc.

void main() {
  final Map<String, String?> profil = {'nume': 'Elena', 'oras': null};

  print('Oraș: ${profil['oras'] ?? 'necunoscut'}');

  profil['oras'] ??= 'Brașov';
  profil['nume'] ??= 'Anonim';
  print(profil);
}

Rezultat

Oraș: necunoscut
{nume: Elena, oras: Brașov}

Exemplul 3: Operatorul ! și riscul lui

! pe o valoare null aruncă o eroare la rulare. Aici o prindem ca să vedem mesajul.

int? gasesteNota(Map<String, int> note, String elev) => note[elev];

void main() {
  final note = {'Ana': 10, 'Dan': 8};
  final notaAna = gasesteNota(note, 'Ana')!;
  print('Nota Anei + 1 punct bonus: ${notaAna + 1}');

  try {
    final notaIon = gasesteNota(note, 'Ion')!;
    print(notaIon);
  } on TypeError catch (e) {
    print('Eroare: $e');
  }
}

Rezultat

Nota Anei + 1 punct bonus: 11
Eroare: Null check operator used on a null value

Greșeli frecvente

  • String nume = null;: A value of type 'Null' can't be assigned to a variable of type 'String'.
  • ! pus peste tot ca să dispară erorile: transformă fiecare loc într-o posibilă eroare la rulare. Preferă ?? sau o verificare.
  • Promovarea nu merge pe câmpuri publice: if (this.nume != null) this.nume.length dă eroare. Copiază câmpul într-o variabilă locală.

Comenzi înrudite

  • ?. și ?..: Accesul condiționat: ?. apelează o metodă doar dacă obiectul nu este null, altfel dă null.
  • late: Amână inițializarea unei variabile nenule: valoarea se dă mai târziu sau se calculează la prima folosire.
  • Object?: Tipul de bază al tuturor valorilor din Dart, inclusiv null; varianta sigură a lui dynamic.
  • pattern-uri (destructurare): Desfac o valoare (record, listă, map, obiect) în variabile separate, într-o singură declarație.