Menggunakan bahasa yang lebih ketat tidak hanya memindahkan posting tujuan dari mendapatkan implementasi yang benar untuk mendapatkan spesifikasi yang benar. Sulit untuk membuat sesuatu yang sangat salah namun konsisten secara logis; itulah sebabnya kompiler menangkap begitu banyak bug.
Aritmatika Pointer seperti yang biasanya dirumuskan tidak sehat karena sistem jenis tidak benar-benar berarti apa yang seharusnya berarti. Anda dapat menghindari masalah ini sepenuhnya dengan bekerja dalam bahasa sampah yang dikumpulkan (pendekatan normal yang membuat Anda juga membayar abstraksi). Atau Anda bisa lebih spesifik tentang jenis petunjuk apa yang Anda gunakan, sehingga kompiler dapat menolak apa pun yang tidak konsisten atau tidak dapat dibuktikan benar seperti yang tertulis. Ini adalah pendekatan dari beberapa bahasa seperti Rust.
Tipe yang dibangun setara dengan bukti, jadi jika Anda menulis sistem tipe yang melupakan ini, maka semua hal salah. Asumsikan untuk sementara waktu bahwa ketika kita mendeklarasikan suatu tipe, sebenarnya kita maksudkan bahwa kita menyatakan kebenaran tentang apa yang ada dalam variabel.
- int * x; // Pernyataan palsu. x ada dan tidak menunjuk ke int
- int * y = z; // Hanya benar jika z terbukti menunjuk ke int
- * (x + 3) = 5; // Hanya benar jika (x + 3) menunjuk ke int dalam array yang sama dengan x
- int c = a / b; // Hanya benar jika b bukan nol, seperti: "bukan nol int b = ...;"
- nullable int * z = NULL; // nullable int * tidak sama dengan int *
- int d = * z; // Pernyataan salah, karena z dapat dibatalkan
- if (z! = NULL) {int * e = z; } // Oke karena z bukan nol
- gratis (y); int w = * y; // Pernyataan salah, karena kamu tidak lagi ada di w
Di dunia ini, pointer tidak boleh nol. Dereferensi NullPointer tidak ada, dan pointer tidak harus diperiksa untuk nullness di mana saja. Sebagai gantinya, "nullable int *" adalah tipe yang berbeda yang dapat nilainya diekstraksi menjadi null atau ke sebuah pointer. Ini berarti bahwa pada titik di mana asumsi non-nol dimulai Anda masuk log pengecualian Anda atau turun cabang nol.
Di dunia ini, kesalahan susunan di luar batas juga tidak ada. Jika kompiler tidak dapat membuktikan bahwa ia dalam batas, maka cobalah untuk menulis ulang sehingga kompiler dapat membuktikannya. Jika tidak bisa, maka Anda harus memasukkan Assumption secara manual di tempat itu; kompiler dapat menemukan kontradiksi di kemudian hari.
Juga, jika Anda tidak dapat memiliki pointer yang tidak diinisialisasi, maka Anda tidak akan memiliki pointer ke memori yang tidak diinisialisasi. Jika Anda memiliki pointer ke memori yang dibebaskan, maka itu harus ditolak oleh kompiler. Di Rust, ada berbagai jenis penunjuk untuk membuat jenis bukti ini masuk akal untuk diharapkan. Ada pointer yang dimiliki secara eksklusif (yaitu: tidak ada alias), pointer ke struktur yang sangat abadi. Jenis penyimpanan default tidak dapat diubah, dll.
Ada juga masalah menegakkan tata bahasa yang sebenarnya didefinisikan dengan baik pada protokol (yang mencakup anggota antarmuka), untuk membatasi area permukaan input untuk persis apa yang diantisipasi. Hal tentang "kebenaran" adalah: 1) Singkirkan semua kondisi yang tidak ditentukan 2) Pastikan konsistensi logis . Kesulitan untuk sampai ke sana banyak berhubungan dengan penggunaan alat yang sangat buruk (dari sudut pandang kebenaran).
Inilah sebabnya mengapa dua praktik terburuk adalah variabel global dan goto. Hal-hal ini mencegah menempatkan kondisi pra / post / invarian di sekitar apa pun. Itu juga sebabnya tipe sangat efektif. Ketika tipe semakin kuat (akhirnya menggunakan Tipe Dependen untuk memperhitungkan nilai aktual), mereka mendekati sebagai bukti kebenaran konstruktif dalam diri mereka sendiri; membuat program yang tidak konsisten gagal dikompilasi.
Perlu diingat bahwa ini bukan hanya tentang kesalahan bodoh. Ini juga tentang mempertahankan basis kode dari penyusup yang pintar. Akan ada kasus di mana Anda harus menolak kiriman tanpa bukti properti penting yang dihasilkan mesin seperti "ikuti protokol yang ditentukan secara formal".