Sistem Tipe Sehat Dart (Sound Type System)
Definisi Inti
Dart merupakan bahasa tipe statis sehat yang dilengkapi dua lapisan pengaman: pengecekan statis saat kompilasi dan validasi saat runtime. Nilai variabel saat program berjalan selalu cocok dengan tipe yang dideklarasikan secara statis.
Aturan tipe diterapkan secara wajib, namun anotasi tipe boleh dihilangkan karena kompiler akan menyimpulkan tipe secara otomatis.
Analisis statis pada tahap kompilasi dapat menangkap sebagian besar kesalahan tipe lebih awal.
Apa itu Soundness (Kesehatan Tipe)
Kode tidak akan pernah masuk ke keadaan tipe yang tidak valid. Jika tipe statis suatu ekspresi adalah String, saat runtime hanya akan menghasilkan nilai teks, tipe lain tidak diizinkan. Bahasa sejenis: Java, C#.
- Pemblokiran saat kompilasi:
int a = "abc";langsung memunculkan error; - Pemblokiran saat runtime: Konversi paksa pakai
asakan melempar pengecualian jika tipe tidak cocok.
Empat Keunggulan Utama
Bug tipe muncul sejak kompilasi, menghindari crash produksi yang sulit direproduksi akibat ketidakcocokan tipe;
Kode lebih mudah dibaca, tipe tidak menyesatkan pengembang tentang bentuk data;
Kode mudah dipelihara, jika satu bagian kode diubah, pengecekan tipe akan menandai semua bagian yang terdampak;
Efisiensi kompilasi AOT lebih tinggi, informasi tipe membantu kompiler menghasilkan kode mesin yang lebih optimal.
Contoh Kode
Error statis yang dipicu koleksi dinamis
// Mengharapkan parameter List<int>
void printInts(List<int> a) => print(a);
void main() {
// Tidak deklarasikan generik, otomatis disimpulkan List<dynamic>
final list = [];
list.add(1);
list.add('2'); // Daftar dinamis bisa campur berbagai tipe
printInts(list);
// Error kompilasi: List<dynamic> tidak bisa ditugaskan ke List<int>
}Code language: Dart (dart)
Deklarasi batasan generik secara eksplisit
void printInts(List<int> a) => print(a);
void main() {
final list = <int>[]; // Tentukan generik int secara jelas
list.add(1);
// list.add('2'); // Error kompilasi, tidak bisa masukkan teks
list.add(2);
printInts(list); // Lulus pengecekan statis dengan normal
}Code language: Dart (dart)
Tiga Aturan Inti
1 Saat menimpa metode, tipe kembalian harus tipe asli atau subtipe-nya (Aturan Produsen)
Nilai kembalian metode kelas induk bersifat produsen. Subkelas boleh mengembalikan subtipe yang lebih spesifik, tapi tidak boleh tipe yang tidak berhubungan.
Contoh
Kasus Valid
class Animal {
Animal get parent => Animal();
}
class HoneyBadger extends Animal {
@override
HoneyBadger get parent => HoneyBadger(); // Subtipe, valid
}Code language: Dart (dart)
Kasus Tidak Valid
class Root {}
class HoneyBadger extends Animal {
@override
Root get parent => Root(); // Tipe tidak berhubungan, error kompilasi
}Code language: Dart (dart)
2 Saat menimpa metode, tipe parameter harus tipe asli atau supertipe-nya
Parameter metode bersifat konsumen. Subkelas hanya boleh memperluas cakupan parameter (pakai supertipe), tidak boleh menyempit ke subtipe; penyempitan akan merusak keamanan tipe.
Contoh valid (parameter diperluas ke Object)
class Animal {
void chase(Animal a) {}
}
class HoneyBadger extends Animal {
@override
void chase(Object a) {} // Object adalah supertipe Animal, valid
}Code language: Dart (dart)
Contoh tidak valid (parameter disempit ke subkelas Mouse)
class Mouse extends Animal {}
class Cat extends Animal {
@override
void chase(Mouse a) {} // Menyempit tipe parameter → error statis
}
void main() {
Animal cat = Cat();
cat.chase(Alligator()); // Secara teori boleh kirim Alligator, menyebabkan tipe tidak aman
}Code language: Dart (dart)
Aturan 3: Koleksi generik dinamis tidak boleh langsung ditugaskan ke koleksi generik tipe tetap
List<dynamic> dan List<Dog> tidak bisa langsung ditugaskan ke List<Cat> dan akan menghasilkan error kompilasi. Tidak ada konversi implisit antar koleksi generik yang berbeda.
class Cat {}
class Dog {}
void main() {
List<Cat> foo = <dynamic>[Dog()]; // Error statis kompilasi
List<dynamic> bar = <dynamic>[Dog(), Cat()]; // Valid, daftar dinamis boleh campur tipe
}Code language: Dart (dart)
Pengecekan Tipe Saat Runtime
Pengecekan statis tidak mencakup semua skenario, sehingga ada pengecekan tipe tambahan saat runtime yang melempar pengecualian jika tipe tidak cocok.
Error runtime dari konversi generik
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
void main() {
List<Animal> animals = <Dog>[Dog()];
List<Cat> cats = animals as List<Cat>;
// Pengecualian runtime: tidak bisa konversi List<Dog> ke List<Cat>
}Code language: Dart (dart)
Konversi turun implisit dari dynamic (dicek runtime)
Menugaskan variabel dynamic ke tipe konkret akan memicu konversi implisit yang divalidasi saat runtime; jika tipe tidak cocok akan muncul error.
int assumeString(dynamic object) {
String string = object; // Cek saat runtime apakah object bertipe String
return string.length;
}
void main() {
assumeString("test"); // Berjalan normal
assumeString(123); // Error runtime: int tidak bisa dikonversi jadi String
}Code language: Dart (dart)
Nonaktifkan Konversi Implisit (Mode Tipe Ketat)
Aktifkan aturan konversi ketat di analysis_options.yaml untuk blokir konversi turun implisit dari dynamic
analyzer:
language:
strict-casts: trueCode language: JavaScript (javascript)
Penyimpulan Tipe (Type Inference)
Dart otomatis menyimpulkan tipe variabel, generik, dan nilai kembalian metode. Jika informasi tipe kurang lengkap, tipe default adalah dynamic.
Penyimpulan tipe variabel biasa
// Otomatis disimpulkan Map<String, Object>
var arguments = {'argA': 'hello', 'argB': 42};
var x = 3; // x disimpulkan sebagai int
x = 4.0; // Error, int tidak bisa simpan double
num y = 3; // Deklarasi eksplisit num, bisa simpan int / double
y = 4.0; // ValidCode language: Dart (dart)
Penyimpulan konstruktor generik / koleksi
List<int> listOfInt = []; // Otomatis simpulkan <int>[]
var listOfDouble = [3.0]; // Disimpulkan List<double>
var ints = listOfDouble.map((x) => x.toInt());
// x disimpulkan double, callback kembalikan int → ints disimpulkan Iterable<int>Code language: Dart (dart)
Sistem Tipe Sehat