Використання більш чіткої мови не просто переміщує пости цілей: від правильної реалізації до отримання специфікації правильно. Важко зробити щось, що дуже неправильно, але послідовно логічно; саме тому компілятори виловлюють стільки помилок.
Арифметика вказівника, як це зазвичай формулюється, не є голосною, оскільки система типів насправді не означає, що вона повинна означати. Ви можете повністю уникнути цієї проблеми, працюючи мовою зібраного сміття (звичайний підхід, який змушує вас також платити за абстрагування). Або ви можете бути набагато більш конкретними щодо того, які типи покажчиків ви використовуєте, щоб компілятор міг відхилити все, що непослідовно або просто не може бути доведено правильним як написане. Це підхід деяких мов, як Руст.
Побудовані типи еквівалентні доказам, тому якщо ви пишете систему типів, яка це забуває, то всі речі йдуть не так. Припустимо, на деякий час, коли ми оголошуємо тип, ми фактично маємо на увазі, що ми стверджуємо правду про те, що є в змінній.
- int * x; // Неправдиве твердження. x існує і не вказує на int
- int * y = z; // Вірно лише, якщо z доведено, що z вказує на інт
- * (x + 3) = 5; // Вірно лише, якщо (x + 3) вказує на int у тому ж масиві, що і x
- int c = a / b; // Вірно лише, якщо b ненульовий, наприклад: "nonzero int b = ...;"
- нульовий int * z = NULL; // nullable int * - не те саме, що int *
- int d = * z; // Неправдиве твердження, оскільки z є нульовим
- якщо (z! = NULL) {int * e = z; } // Добре, тому що z не є нульовим
- вільний (у); int w = * y; // Неправдиве твердження, тому що y більше не існує при w
У цьому світі покажчики не можуть бути нульовими. Відміни NullPointer не існують, і покажчики не повинні перевірятись на нікчемність ніде. Натомість "нульовий int *" - це інший тип, який може отримати своє значення або до нуля, або до вказівника. Це означає, що в точці, де невідмінна припущення, ви або перейдете до журналу свого винятку, або вниз по нульовій гілці.
У цьому світі помилок масиву поза межами також не існує. Якщо компілятор не може довести, що він знаходиться в межах, спробуйте переписати його, щоб компілятор міг це довести. Якщо це не вдається, то вам доведеться поставити Успенське вручну на цьому місці; компілятор може виявити протиріччя цьому згодом.
Крім того, якщо ви не можете мати покажчик, який не ініціалізований, у вас не буде вказівників на неініціалізовану пам'ять. Якщо у вас є вказівник на звільнену пам'ять, його слід відхилити компілятором. У Іржі існують різні типи покажчиків, щоб зробити такі докази розумними для очікування. Існують виключно вказівники, що належать (тобто: немає псевдонімів), вказівники на глибоко незмінні структури. Тип зберігання за замовчуванням незмінний тощо.
Існує також питання застосування фактично чітко визначеної граматики на протоколах (до яких входять члени інтерфейсу), щоб обмежити площу вхідної поверхні саме тим, що передбачається. Річ у "правильності" полягає в: 1) Позбавленні від усіх невизначених станів 2) Забезпечення логічної послідовності . Складність потрапляння туди має багато спільного з використанням надзвичайно поганих інструментів (з точки зору правильності).
Саме тому дві найгірші практики - глобальні змінні та гото. Ці речі заважають ставити умови перед / після / інваріантними навколо будь-чого. Це також, чому типи такі ефективні. У міру того, як типи посилюються (зрештою, використовуючи залежні типи для врахування фактичного значення), вони підходять як конструктивні докази коректності самі по собі; що робить невідповідні програми компіляції не вдається.
Майте на увазі, що мова йде не лише про німі помилки. Йдеться також про захист бази коду від розумних інфільтраторів. Будуть випадки, коли вам доведеться відхилити подання без переконливого машинного доказу важливих властивостей, таких як "слідує формально визначений протокол".