Tarkibga o'tish

06 β€” Null safety

⬅️ Oldingi: 05 β€” To'plamlar: List, Set, Map Β· 🏠 README Β· Keyingi: 07 β€” OOP β€” obyektga yo'naltirilgan dasturlash ➑️

Bu bobda: null nima va nega u dunyodagi eng qimmat xato deb ataladi; Dart'ning sound null safety kuchi β€” o'zgaruvchi standart holatda null bo'la olmaydi; nullable tip ?; xavfsiz ishlash quroli ?., ??, ??=, !; flow analysis (promotion) β€” Dart if ichida tipni "yangilab" qo'yishi; late o'zgaruvchi va uning xavfi; va bularning bari Flutter'da nega kamroq xato (crash) keltirib chiqaradi.


null nima va nega u muammo?

Tasavvur qiling, sizga quti berishdi. Qutining ustida yorliq bor: "ichida ism bor". Siz qutini ochasiz... lekin u bo'sh. Ichida hech narsa yo'q. Ana shu "hech narsa yo'q" holatini dasturlashda bitta maxsus so'z bilan ataymiz: null.

null β€” bu "qiymat yo'q" degani. Nol (0) emas, bo'sh satr ('') emas β€” umuman hech narsa.

Muammo shu yerda boshlanadi. Aytaylik, qutida ism bor deb ishonib, uning uzunligini o'lchamoqchi bo'ldingiz:

String ism = olibKel();   // quti β€” lekin ichi bo'sh (null) chiqdi
print(ism.length);        // BUM! bo'sh qutining uzunligini o'lchab bo'lmaydi

Agar ism aslida null bo'lsa, ism.length chaqirilganda dastur ishlashni to'xtatadi β€” bu "null reference" xatosi. Foydalanuvchining telefonida ilova birdan yopiladi (crash).

πŸ’‘ Bu shunchalik keng tarqalgan va shunchalik ko'p ziyon keltirgan xatoki, uni o'ylab topgan olim Tony Hoare o'zi buni "the billion-dollar mistake" β€” "milliard dollarlik xato" deb atagan. Yarim asr davomida millionlab dasturlar aynan shu sababdan qulagan.

Eng yomoni: bu xato dastur ishga tushganda, ya'ni foydalanuvchining qo'lida sodir bo'ladi. Siz uni oldindan ko'rmaysiz.

Aynan shu muammoni Dart ildizidan hal qiladi.


Sound null safety β€” Dart'ning kuchi

Dart 3.12'da sound null safety standart va majburiy (uni o'chirib bo'lmaydi). "Sound" so'zi "ishonchli, mustahkam" degani.

Asosiy g'oya juda oddiy va juda kuchli:

Standart holatda hech bir o'zgaruvchi null bo'la olmaydi.

Ya'ni oddiy String tipidagi quti doim ichida qiymat saqlashga majbur. Uni bo'sh qoldira olmaysiz:

String ism = null;   // ❌ KOMPILYATSIYA XATOSI β€” hatto ishga ham tushmaydi

Bu juda muhim nuqta. Xato sizning telefoningizda emas, balki kodni yozayotgan paytingizda, ekranda qizil chiziq bilan ko'rsatiladi. Kompilyator (kodni tekshiruvchi) sizni oldindan himoya qiladi:

String ism = 'Ali';   // βœ… to'g'ri β€” ichida qiymat bor
print(ism.length);     // βœ… xavfsiz β€” ism hech qachon null bo'lolmaydi, demak crash yo'q

Mana shu β€” butun bobning yuragi. String tipidagi qutiga ishonsangiz bo'ladi: u hech qachon bo'sh emas. Demak ism.length doim xavfsiz.

πŸ›‘οΈ Eski tillarda (yoki eski Dart'da) har bir qiymat yashirin tarzda null bo'lishi mumkin edi β€” siz buni hech qachon bilmasdingiz. Sound null safety bu "yashirin tuzoq"ni butunlay yo'q qiladi.

Non-null quti doim to'la (ochish xavfsiz), nullable ? quti esa bo'sh bo'lishi mumkin β€” bo'shini ishlatish crash


Nullable tiplar ? β€” "bo'sh bo'lishi mumkin"

Lekin ba'zan bizga aynan "qiymat bo'lmasligi mumkin" holati kerak bo'ladi. Masalan, foydalanuvchi profilida "ikkinchi ism" maydoni bor, lekin uni hamma ham to'ldirmaydi. Bu yerda "qiymat yo'q" β€” bu normal holat.

Buning uchun tipdan keyin savol belgisi ? qo'yamiz. Bu Dart'ga aytadi: "bu quti bo'sh bo'lishi mumkin (null bo'la oladi)".

String? ikkinchiIsm;   // ? bor β€” null bo'lishi MUMKIN. Boshlang'ich qiymati null.
ikkinchiIsm = 'Vali';  // βœ… keyin qiymat bersa ham bo'ladi
ikkinchiIsm = null;    // βœ… bo'sh qoldirsa ham bo'ladi β€” ? shunga ruxsat berdi

? belgisi β€” bu rozilik (opt-in). Siz Dart'ga "men bu yerda null bo'lishi mumkinligini bilaman va tayyorman" deysiz.

Endi ikki xil tip bor:

Tip Misol null bo'la oladimi?
non-null (oddiy) String ism Yo'q ❌ β€” doim qiymat bor
nullable (? bilan) String? ism Ha βœ… β€” bo'sh bo'lishi mumkin

Va eng qizig'i β€” nullable qutini to'g'ridan-to'g'ri ishlatib bo'lmaydi:

String? ism;
print(ism.length);   // ❌ XATO β€” Dart: "bu null bo'lishi mumkin, avval tekshir!"

Dart sizni majburlaydi: agar quti bo'sh bo'lishi mumkin bo'lsa, uni ochishdan oldin tekshir. Bu mantiqan to'g'ri-ku β€” bo'sh qutining uzunligini o'lchab bo'lmaydi. Endi buni qanday xavfsiz qilishni o'rganamiz.


Nullable bilan xavfsiz ishlash β€” null quroli

Dart bizga nullable qiymatlar bilan ishlash uchun bir nechta qulay operator beradi. Ularni "null quroli" deb atasak bo'ladi.

Null quroli: ?. xavfsiz murojaat, ?? standart qiymat, ??= bo'sh bo'lsa o'zlashtir, ! xavfli tasdiq

?. β€” xavfsiz murojaat (null-aware access)

Oddiy nuqta . o'rniga ?. ishlatsangiz, Dart avval "quti bo'shmi?" deb tekshiradi. Agar bo'sh bo'lsa, crash qilish o'rniga shunchaki null qaytaradi:

String? ism;            // hozir null
print(ism?.length);     // crash YO'Q β€” natija: null

ism = 'Ali';
print(ism?.length);     // natija: 3

?. ni shunday o'qing: "agar bor bo'lsa, uning .length ini ol; agar yo'q bo'lsa β€” null".

?? β€” standart qiymat (default)

?? operatori shunday ishlaydi: "chap tomon null bo'lsa, o'ng tomonni ol". Bu bizga "zaxira" (fallback) qiymat berishga imkon beradi:

String? ism;
String korsatiladigan = ism ?? 'Mehmon';   // ism null β†’ 'Mehmon' olinadi
print(korsatiladigan);                       // Mehmon

ism = 'Ali';
print(ism ?? 'Mehmon');                      // Ali (chap tomon null emas)

?. va ?? ni birga ishlatish juda keng tarqalgan:

String? ism;
int uzunlik = ism?.length ?? 0;   // ism bo'lsa uzunligi, bo'lmasa 0

??= β€” bo'sh bo'lsa o'zlashtir (assign-if-null)

??= o'zgaruvchiga qiymat faqat u hozir null bo'lsa beradi. Agar allaqachon qiymat bor bo'lsa β€” tegmaydi:

String? til;
til ??= 'uz';   // til null edi β†’ endi 'uz'
print(til);      // uz

til ??= 'en';   // til allaqachon 'uz', null emas β†’ o'zgarmaydi
print(til);      // uz

! β€” "bang", null assertion (XAVFLI)

! operatori Dart'ga shunday deydi: "menga ishon, bu yerda qiymat aniq bor, null emas". U nullable qutini majburan non-null qilib ko'rsatadi:

String? ism = 'Ali';
print(ism!.length);   // 3 β€” biz "null emas" deb va'da berdik

Lekin agar xato qilsangiz va quti aslida bo'sh bo'lsa β€” ! darhol crash qiladi:

String? ism;          // null
print(ism!.length);   // ❌ CRASH! "Null check operator used on a null value"

⚠️ ! β€” bu xavfsizlik to'rini o'zingiz olib tashlash. U sizning va'dangizga ishonadi; va'dangiz noto'g'ri bo'lsa β€” dastur qulaydi. Shuning uchun ! ni juda kam, faqat 100% ishonchingiz komil bo'lganda ishlating. Ko'p hollarda ?. yoki ?? xavfsizroq tanlov.

To'plamlarda null-aware

Bu quroldan to'plamlar (List, Map) bilan ham foydalanamiz:

List<int>? sonlar;
print(sonlar?.length);          // null (sonlar bo'sh)

List<int> hammasi = [0, ...?sonlar];   // ...?  β€” sonlar null bo'lsa, hech narsa qo'shilmaydi
print(hammasi);                         // [0]

...? β€” bu "spread" operatorining null-aware versiyasi: agar ro'yxat null bo'lsa, xato bermay shunchaki tashlab ketadi.


Flow analysis (promotion) β€” Dart "biladi"

Mana eng go'zal qism. Aytaylik, nullable qutimiz bor va biz uni if bilan tekshirdik:

String? ism = olibKel();   // null bo'lishi mumkin

if (ism != null) {
  // shu blok ICHIDA Dart ANIQ biladi: ism null EMAS
  print(ism.length);       // βœ… ! kerak emas! Dart o'zi tushundi
}

if (ism != null) shartining ichida Dart o'zi mantiqan xulosa qiladi: "bu yerga faqat ism null bo'lmaganda kiriladi, demak ichkarida u aniq qiymatga ega". Shu sabab Dart ism ning tipini vaqtincha String? dan String ga "ko'taradi" (promote). Endi ! ham, ?. ham kerak emas β€” oddiy . ishlatasiz.

Bu flow analysis (oqim tahlili) yoki type promotion (tip ko'tarilishi) deb ataladi. Dart kodingiz oqimini "o'qib", qaerda nima null emasligini o'zi aniqlaydi.

Blokdan tashqarida esa quti yana eski, nullable holatiga qaytadi:

if (ism != null) {
  print(ism.length);   // ICHKARIDA: String (xavfsiz)
}
print(ism.length);     // ❌ TASHQARIDA: yana String? β€” xato! null bo'lishi mumkin

Buni "erta qaytish" (early return) bilan ham yozish mumkin va u juda toza chiqadi:

void salomla(String? ism) {
  if (ism == null) return;   // null bo'lsa β€” chiqib ketamiz
  // shu nuqtadan keyin Dart biladi: ism β€” String (null emas)
  print('Salom, $ism!');     // βœ… xavfsiz
}

String? qiymat if (x != null) blokiga kirganda String ga ko'tariladi; blokdan tashqarida yana nullable

🎯 Promotion β€” bu nega Dart kodida ! kam uchrashining sababi. To'g'ri yozilgan kodda Dart sizning o'rningizga deyarli hamma narsani tekshiradi.


late β€” keyin tayinlanadigan o'zgaruvchi

Ba'zan bizda shunday holat bo'ladi: o'zgaruvchi non-null bo'lishi kerak (null bo'lmasligi kerak), lekin uni e'lon qilgan zahoti qiymat berolmaymiz β€” biroz keyinroq beramiz.

Bu yerda late kalit so'zi yordamga keladi. late Dart'ga shunday va'da beradi: "hozir bo'sh, lekin men buni ishlatishdan oldin albatta to'ldiraman":

late String xabar;   // hozir qiymat yo'q, lekin String? ham emas

void tayyorla() {
  xabar = 'Salom!';   // keyinroq to'ldiramiz
}

void korsat() {
  print(xabar);       // βœ… tayyorla() chaqirilgan bo'lsa β€” ishlaydi
}

late ning yana bir foydasi β€” dangasa ishga tushirish (lazy init): qiymat faqat birinchi marta ishlatilganda hisoblanadi. Agar hisoblash "qimmat" (sekin) bo'lsa, bu tejamkor:

late String ogirHisob = qimmatFunksiya();   // qimmatFunksiya() FAQAT birinchi murojaatda chaqiriladi

Xavfi: agar late o'zgaruvchini to'ldirishdan oldin ishlatib qo'ysangiz, Dart LateInitializationError beradi:

late String xabar;
print(xabar);   // ❌ CRASH β€” hali to'ldirilmagan!

πŸ“± Flutter'da nega kerak? Keyinroq (16-bobda) StatefulWidget da initState deb nomlangan joyni ko'rasiz β€” u widget ekranga chiqishidan oldin bir marta ishlaydi. Ko'pincha o'zgaruvchini aynan o'sha yerda to'ldiramiz, e'lon paytida emas. Mana shunda late aynan to'g'ri keladi: "men buni initState da to'ldiraman, va'da beraman".


required β€” qiymat berish shart

Funksiyalar bobida (04) nomli parametrlar (named parameters) bilan tanishgansiz. Null safety ular bilan ham bog'liq. Nomli parametr standart holatda ixtiyoriy, demak uni nullable qilish kerak edi. Lekin agar parametr majburiy bo'lishini xohlasangiz, required qo'yasiz:

void ro'yxat({required String ism, int yosh = 0}) {
  print('$ism, $yosh yosh');
}

ro'yxat(ism: 'Ali');          // βœ… ism berildi, yosh standart 0
ro'yxat(yosh: 25);            // ❌ XATO β€” ism berilmadi, lekin required!

required tufayli ism non-null String bo'la oladi: chaqiruvchi unga qiymat berishga majbur, shuning uchun u hech qachon bo'sh qolmaydi. Bu null safety bilan funksiya parametrlari qanday birga ishlashini ko'rsatadi.


Amaliy misollar

Endi haqiqiy hayotda tez-tez uchraydigan holatlarni xavfsiz hal qilamiz.

1. Map'dan "bo'lishi mumkin yoki yo'q" qiymat olish

Map'dan kalit orqali qiymat olganingizda, natija doim nullable bo'ladi β€” chunki o'sha kalit umuman bo'lmasligi mumkin:

Map<String, String> sozlamalar = {'til': 'uz'};

String? tema = sozlamalar['tema'];   // 'tema' kaliti yo'q β†’ null
String aniqTema = sozlamalar['tema'] ?? 'och';   // ?? bilan zaxira: 'och'
print(aniqTema);   // och

2. API'dan kelgan nullable maydon

Tarmoqdan (API) kelgan ma'lumotda ba'zi maydonlar bo'lmasligi mumkin. Ularni nullable deb belgilab, xavfsiz ishlatamiz:

class Foydalanuvchi {
  final String ism;
  final String? telefon;   // hamma ham telefon kiritmaydi β†’ nullable
  Foydalanuvchi(this.ism, this.telefon);
}

void korsat(Foydalanuvchi u) {
  print('Ism: ${u.ism}');
  print('Tel: ${u.telefon ?? "ko'rsatilmagan"}');   // null bo'lsa zaxira matn
}

3. Zanjirli xavfsiz murojaat

Bir nechta nullable bosqichni ?. bilan zanjirlaymiz β€” biror joyda null bo'lsa, butun zanjir null qaytaradi:

String? shahar = foydalanuvchi?.manzil?.shahar;   // birortasi null bo'lsa β†’ shahar = null
print(shahar ?? 'Manzil yo'q');

Tez-tez uchraydigan xatolar

  • ! ni haddan tashqari ishlatish. Har joyda qiymat! yozish β€” bu xavfsizlik to'rini olib tashlash bilan teng. Ko'p ! bor kod β€” ko'p crash xatari. Avval ?. yoki ?? ni o'ylang.
  • Kompilyator bilan "urushish". Dart qizil chiziq ko'rsatganda, uni ! bilan "zo'rlab bostirmang". Buning o'rniga tipni ? qilib to'g'ri belgilang yoki if (x != null) bilan tekshiring. Kompilyator β€” dushman emas, yordamchi.
  • late ni to'ldirmaslik. late β€” bu va'da. To'ldirmasdan ishlatsangiz, LateInitializationError keladi. Agar qiymat haqiqatan ham bo'lmasligi mumkin bo'lsa, late emas, ? (nullable) ishlating.
  • Promotion'ni unutib ! qo'shish. if (x != null) ichida x! yozish β€” ortiqcha. Dart allaqachon biladi; oddiy x yeting.
  • Hamma narsani ? qilib qo'yish. Aksincha xato: kerak bo'lmasa-da hamma tipni nullable qilib, keyin har joyda ?. va ?? bilan kurashish. Qiymat doim bo'lishi kerak bo'lsa β€” uni non-null qoldiring.

Xulosa

  • null β€” "qiymat yo'q". Uni o'ylamay ishlatish β€” klassik crash sababi ("milliard dollarlik xato").
  • Sound null safety Dart'da standart: oddiy String hech qachon null bo'la olmaydi. Xato kodni yozish paytida ushlanadi, telefonda emas.
  • ? β€” tipni nullable qiladi: String? ism bo'sh bo'lishi mumkin.
  • Null quroli: ?. (xavfsiz murojaat), ?? (standart qiymat), ??= (bo'sh bo'lsa o'zlashtir), ! (xavfli tasdiq β€” kam ishlating).
  • Promotion: if (x != null) ichida Dart o'zi tipni String? β†’ String ga ko'taradi.
  • late: non-null, lekin keyin to'ldiriladi (masalan Flutter'da initState). To'ldirilmasa β€” LateInitializationError.
  • Natijada Flutter ilovangiz kamroq qulaydi, va analizator yo'l-yo'lakay sizni to'g'rilab boradi.

Endi siz tiplarni xavfsiz boshqarishni bilasiz. Keyingi bobda bu tiplardan o'zingizning murakkab tiplaringizni β€” klasslar va obyektlarni quramiz.


Mashqlar

1-mashq. Quyidagi koddagi xatoni toping va to'g'rilang. Nega xato berayotganini bir jumlada tushuntiring.

String laqab = null;
print(laqab);

2-mashq. String? sevimliRang o'zgaruvchisi bor. Agar u null bo'lsa, ekranga 'tanlanmagan', aks holda rangning o'zini chiqaring β€” ?? yordamida bir qatorda yozing.

3-mashq. Quyidagi funksiyani promotion yordamida to'g'rilang (ya'ni ! ishlatmasdan). Funksiya ism null bo'lsa 'Salom, mehmon!', aks holda 'Salom, <ism>!' chiqarsin.

void salomla(String? ism) {
  print('Salom, ${ism!}!');   // ism null bo'lsa crash qiladi β€” tuzating
}

4-mashq. Map<String, int> ballar = {'Ali': 90} lug'ati bor. 'Vali' ning balini xavfsiz oling: agar yo'q bo'lsa, 0 bo'lsin. ?? ishlating va natijani chiqaring.

5-mashq. late int hisob; e'lon qilingan. Quyidagi kodning 2-qatorida nima sodir bo'ladi va nega? Crashni oldini olish uchun kodni tuzating.

late int hisob;
print(hisob);
hisob = 5;

6-mashq. ?. va ?? ni birga ishlatib, String? matn ning uzunligini xavfsiz oling: agar matn null bo'lsa, natija 0 bo'lsin. Bir qatorda yozing.

βœ… Yechimlarni ko'rish

1-mashq. Oddiy String non-null, shuning uchun unga null berib bo'lmaydi β€” bu kompilyatsiya xatosi (kod ishga ham tushmaydi). Ikki yo'l bor:

String laqab = 'mehmon';   // 1-yo'l: haqiqiy qiymat ber
print(laqab);

// yoki bo'sh bo'lishi kerak bo'lsa, tipni nullable qil:
String? laqab2;            // 2-yo'l: ? qo'shdik β†’ null bo'lishi mumkin
print(laqab2);             // null

2-mashq.

String? sevimliRang;
print(sevimliRang ?? 'tanlanmagan');   // null β†’ 'tanlanmagan'

3-mashq. if (ism != null) bilan tekshirsak, Dart ism ni String ga ko'taradi va ! kerak emas:

void salomla(String? ism) {
  if (ism == null) {
    print('Salom, mehmon!');
  } else {
    print('Salom, $ism!');   // shu blokda ism β€” String, ! yo'q
  }
}

4-mashq.

Map<String, int> ballar = {'Ali': 90};
int valiBal = ballar['Vali'] ?? 0;   // 'Vali' yo'q β†’ null β†’ 0
print(valiBal);                       // 0

5-mashq. 2-qatorda (print(hisob);) hisob hali to'ldirilmagan, shuning uchun LateInitializationError (crash) sodir bo'ladi β€” late "keyin to'ldiraman" deb va'da bergan, lekin biz o'qishdan oldin to'ldirmadik. Tuzatish: avval to'ldiramiz, keyin o'qiymiz:

late int hisob;
hisob = 5;        // avval to'ldir
print(hisob);     // keyin o'qi β†’ 5

6-mashq.

String? matn;
int uzunlik = matn?.length ?? 0;   // matn null β†’ ?.length null β†’ ?? 0
print(uzunlik);                     // 0

⬅️ Oldingi: 05 β€” To'plamlar: List, Set, Map Β· 🏠 README Β· Keyingi: 07 β€” OOP β€” obyektga yo'naltirilgan dasturlash ➑️