ข้ามไปยังเนื้อหา

Null Safety

Dart มี sound null safety: type ไม่สามารถเก็บ null ได้เว้นแต่คุณจะบอกชัด ๆ ด้วย ? นี่คือการันตีแบบแข็ง ไม่ใช่ lint — ถ้า type system บอกว่าค่านั้น non-null จะเป็น null ตอน runtime ไม่ได้จริง ๆ

int a = 1; // non-nullable — เป็น null ไม่ได้เลย
int? b = null; // nullable — ? opt-in ให้เป็น null ได้
a = null; // ❌ compile error: a เป็น null ไม่ได้
b = 42; // ✅ nullable ก็เก็บค่าจริงได้เหมือนกัน

คำสำคัญคือ “sound”: เพราะการันตีนี้แน่นหนา compiler จึง optimize ตามได้ และคุณก็ เชื่อ type ได้จริง widget property แบบ String name จะไม่มาเซอร์ไพรส์คุณด้วย null

String? maybeName;
// ?. — null-aware access: เรียกก็ต่อเมื่อไม่ null ไม่งั้นทั้งก้อนเป็น null
final len = maybeName?.length; // int? — เป็น null ถ้า maybeName เป็น null
// ?? — if-null: ให้ค่า fallback เมื่อฝั่งซ้ายเป็น null
final safe = maybeName ?? 'Guest'; // String — ไม่มีทางเป็น null
// ??= — assign ก็ต่อเมื่อตอนนี้เป็น null
maybeName ??= 'Default';
// ! — null assertion: "ฉันสัญญาว่าอันนี้ไม่ null" (throw ถ้าคุณคิดผิด)
final definite = maybeName!; // String — อันตรายถ้ามันเป็น null จริง

operator ! คือตัวที่ต้องเคารพ: ! สั่งปิดปาก compiler ด้วยการ assert ว่า non-null และถ้าคุณผิด จะ throw ตอน runtime ทุก ! คือการเดิมพันเล็ก ๆ — ใช้เมื่อคุณพิสูจน์ได้ว่าค่านั้น non-null เท่านั้น

คุณแทบไม่ต้องใช้ ! เพราะ flow analysis ของ Dart จะ narrow nullable เป็น non-null หลังผ่านการเช็ค:

void greet(String? name) {
if (name == null) return;
// จากตรงนี้ลงไป Dart รู้ว่า name เป็น String (ไม่ใช่ String?) — ไม่ต้องใช้ !
print(name.toUpperCase());
}
flowchart TB
  start["name: String?"] --> check{"name == null?"}
  check -->|yes| ret["return early"]
  check -->|no| promoted["name: String
(promote แล้ว ใช้ได้ปลอดภัย)"]
flow analysis promote nullable เป็น non-null

มีจุดที่กัด Flutter dev อยู่บ่อย: flow analysis ทำงานกับ local variable ไม่ใช่กับ instance field หรือ getter (พวกนั้นอาจเปลี่ยนค่าระหว่างเช็คกับใช้งาน) สำหรับ field ที่เป็น nullable ให้ copy ไปเป็น local ก่อน แล้วค่อยเช็ค local นั้น

late บอกว่า “อันนี้จะเป็น non-null ตอนที่มีใครอ่าน แค่ตอนประกาศยังไม่มีค่า” มีประโยชน์กับค่าที่ set ใน initState หรือคำนวณครั้งเดียว

late final String config; // ยังไม่มีค่า แต่สัญญาว่าจะมีก่อนอ่านครั้งแรก
// ถ้าอ่านก่อน assign จะได้ LateInitializationError

ใช้ late อย่างประหยัด — เพราะเป็นการแลกการันตีตอน compile-time ไปเป็นตอน runtime late ที่ใช้ผิดก็คือ ! ในคราบปลอมนั่นเอง

"sound" null safety ใน Dart หมายความว่าอะไร?
operator `??` ทำอะไร?
ทำไม operator `!` (null assertion) ถึงเสี่ยง?
ทำไมบางที flow analysis ถึง promote nullable field ไม่ได้?