5 марта 2026 года в своём доме, в окружении семьи, тихо умер человек, чей код вы трогали на этой неделе. Возможно, прямо сегодня. Возможно, он уронил вам прод.
Звали его сэр Чарльз Энтони Ричард Хоар. Для друзей — Тони. Для пары поколений студентов — C. A. R. Hoare, тот самый, что в 26 лет придумал quicksort, выиграв у начальника спор на шесть пенсов. Тьюринговская премия 1980 года, логика Хоара, CSP, на которой потом выросла половина теории конкурентности. Большая, красивая, почти безупречная карьера.
Почти. Потому что в 1965 году тот же самый человек добавил в язык одну маленькую штуку. И эта штука пережила его, переживёт нас и, скорее всего, прямо сейчас лежит где-то в вашем стектрейсе.
Это null.
Есть расхожий сюжет: коварная индустрия наплодила багов, а гениальные инженеры героически с ними борются. Красиво. И, как обычно, неправда. Потому что самый дорогой баг в истории софта добавил не злодей и не нерадивый джун. Его добавил один из умнейших людей в истории computer science.
Вот про эту историю и поговорим.
Человек, который извинился через 44 года
В 2009 году на конференции QCon в Лондоне Хоар вышел к залу, набитому инженерами, и сделал то, чего легенды его уровня не делают почти никогда. Он не рассказал, как был прав. Он признался, что был неправ.
Дословно он назвал это так: my billion-dollar mistake. Ошибкой на миллиард долларов. И речь шла не о quicksort, не о логике Хоара и не о чём-то экзотическом. Речь шла о null-ссылке, которую он собственноручно изобрёл в 1965-м.
Зал, говорят, притих. Потому что каждый в этом зале на той неделе ловил NullPointerException.
1965. Как это вообще произошло
Контекст важен, иначе вся ирония теряется.
Хоар в тот момент проектировал систему типов для ALGOL W — по сути, первую серьёзную, всеобъемлющую систему типов для ссылок в объектно-ориентированном языке. Задача у него стояла амбициозная и, по тем временам, почти утопическая: сделать так, чтобы любое обращение по ссылке было гарантированно безопасным. Чтобы компилятор сам, автоматически, не давал тебе обратиться к тому, чего нет.
Вдумайтесь, человек строил крепость, главный смысл которой — «здесь физически невозможно выстрелить себе в ногу».
А потом взял и проделал в стене дырку.
Почему? Он сам объяснил это предельно честно: добавить null-ссылку было просто. Реализовать — пара строк. Соблазн оказался сильнее. Он понимал, что это пробивает дыру в той самой гарантии безопасности, ради которой всё затевалось. И всё равно сделал.
«Так проще» — два слова, которые с тех пор стоили индустрии больше, чем любая отдельно взятая атака, любой отдельно взятый баг и, пожалуй, любая отдельно взятая технология.
Сколько стоит «так проще»
Давайте посчитаем, во что обошлась эта дырка в стене.
null — это значение, которое говорит «здесь ничего нет», но при этом притворяется, что что-то есть. Тип говорит «это строка». Переменная говорит «я строка». А внутри — пустота. И в тот момент, когда ты к этой пустоте обращаешься как к строке, всё падает.
Вы знаете это под разными именами, в зависимости от того, на чём пишете:
В Java — NullPointerException, Три буквы NPE, выгравированные в подкорке у любого, кто хоть раз держал прод.
В C и C++ — segmentation fault, дереференс нулевого указателя, тот самый случай, когда программа не «бросает исключение», а просто умирает, иногда унося с собой данные.
В JavaScript — бессмертное Cannot read properties of undefined, мем, диагноз и стиль жизни одновременно.
В Go, Python, C#, PHP — везде своё, но болит одинаково.
И это не «иногда у джунов», а системно. OWASP десятилетиями держит null dereference в списке значимых уязвимостей: на нём ловят падения сервисов, через него заходят туда, куда заходить не должны. «Миллиард долларов» Хоар назвал в 2009-м. Это была вежливая оценка. К 2026-му, с учётом всех NPE, всех сегфолтов, всех ночных дежурств и всех CVE — цифру можно смело считать заниженной на пару порядков.
«Но ведь null удобен и вообще нужен»
Стандартный контраргумент. Он звучит разумно, и в нём кроется ровно та же ошибка, что и в 1965-м.
Да, понятие «значения нет» — нужно. Запрос ничего не нашёл, поля нет, опциональный параметр не передали — это нормальные, законные ситуации. Проблема не в идее «отсутствующего значения». Проблема в том, как именно Хоар её реализовал: он сделал null невидимым для системы типов.
То есть язык знает, что значение может отсутствовать, но никак тебя об этом не предупреждает и ничего не требует. «Тут может быть пусто» и «тут гарантированно есть значение» выглядят в коде абсолютно одинаково. Вся ответственность — на человеке, который должен помнить про каждую дырку вручную. А человек, сюрприз, не помнит. Никогда. Особенно в чужом коде на 200 тысяч строк.
Это и есть billion-dollar mistake. Не сам null. А null, который компилятор молча пропускает.
Как это в итоге починили
Самое интересное началось как раз после того доклада 2009 года. Языки, которые проектировали уже с оглядкой на признание Хоара, эту дырку начали заделывать — каждый по-своему, но в одну сторону: отсутствие значения должно быть видно в типе, и компилятор должен заставить тебя его обработать.
В Haskell это Maybe. В Rust — Option<T>, и raw-null там просто нет как класса
Разница принципиальная. В Java компилятор молчал, и ты узнавал о проблеме в проде. В Rust компилятор не даёт собрать программу, пока ты явно не ответишь на вопрос «а если тут пусто?». Дырку в стене вернули обратно в чертёж — и заварили на этапе компиляции.
То же самое, разными путями, сделали и остальные, кто учился на чужих граблях: Swift с его optionals и синтаксисом String?, Kotlin с nullable-типами и оператором ?., C# с nullable reference types, TypeScript со strictNullChecks. Все они, по сути, реализуют ту самую исходную мечту Хоара из 1965-го — «любое обращение по ссылке безопасно». Просто на 50 лет позже и уже зная, чем заканчивается соблазн «так проще».
Тут полагается мораль про важность хороших систем типов. Она верная, но скучная, и вы её и так знаете.
Хоар не был дураком. Он был ровно противоположностью дурака — один из самых строгих, формальных, помешанных на корректности умов в истории дисциплины. Человек, который буквально придумал математический аппарат для доказательства правильности программ. И даже он не устоял перед «давай добавим вот эту маленькую удобную штуку, я же знаю, что делаю».
Каждый из нас принимает такое решение по десять раз на дню. Захардкодить. Не покрыть тестом. Пропустить проверку, «потому что сюда null никогда не придёт». Оставить `// TODO: обработать позже». Это всё — маленькие null'ы 1965 года. Большинство из них не выстрелит. Какой-то один — переживёт вас и будет ронять чей-то прод в 2086-м.
И вот ещё что. Настоящее наследие Хоара — не в том, что он ошибся. А в том, что он встал перед полным залом и сказал вслух: я был неправ, и вот сколько это стоило. Именно это признание, а не сама ошибка, в итоге родило Option, Maybe и optionals. Индустрия чинит null до сих пор — но чинить начала только после того, как автор набрался смелости назвать вещи своими именами.
Он умер 5 марта. Quicksort останется в учебниках. Логика Хоара — в курсах по верификации. А его «ошибка на миллиард долларов» останется в каждом Option<T>, который существует ровно потому, что один очень умный человек не побоялся признать, что облажался.
Комментарии (28)

Komrus
10.06.2026 08:31Тут ещё наверное вторая сторона медали присутствует. Крайняя ограниченность вычислительных ресурсов в 1965 году... Для реализации небезопасного "null" сколько байт кода в самом компиляторе надо? А для реализации "Option<T>" со всеми условиями?

redfox0
10.06.2026 08:31“Как Rust обманывает процессор: тайная жизнь niche-оптимизации, drop flags и MIR” https://habr.com/ru/articles/1031398/

Medeyko
10.06.2026 08:31Это Вы говорите про результирующий код программы. Там, да, во многих случаях абстракции системы типов не приводят к разрастанию программы. Но коллега говорит о размере и сложности компилятора, а не скомпилированной программы. Компиляторы 60-х были куда проще современного компилятора Rust.

redfox0
10.06.2026 08:31Согласен, тот же язык Ада, кажется, проиграл из-за медленного компилятора на микроЭВМ.

vvzvlad
10.06.2026 08:31Ну и в целом чтобы придумать как именно это решать даже с небольшими ресурсами надо десяток лет пожить с этой ошибкой в проде. Щас-то с послезнанием хорошо говорить “это было ошибкой”.

arteast
10.06.2026 08:31В Java компилятор молчал, и ты узнавал о проблеме в проде. В Rust компилятор не даёт собрать программу, пока ты явно не ответишь на вопрос «а если тут пусто?». Дырку в стене вернули обратно в чертёж — и заварили на этапе компиляции.
Явно типизированный концепт Maybe лучше, чем автоматическое наложение этого концепта на вообще всё. Но он встречается настолько часто, что "для удобства" сделали unwrap и expect, которые втыкают в каждое второе место. В результате джависты смотрят на NPE в проде, а растисты - на креши всего продукта в проде.

DmitryOgn
10.06.2026 08:31Вместо работы с проблемой сказали "у нас ее нет, подпишите вот здесь что осознаете риск и берете ответственность" и как-бы вынесли за пределы безопасной безопасности языка.
А когда проблему игнорируют, то получается еще хуже.

Boneyan
10.06.2026 08:31Я бы сказал что NPE хуже unwrap() в том смысле, что unwrap сразу виден и его, например, поиском по коду можно найти. А вот с местами, где разименовывается указатель всё не так просто, места где может возникнуть NPE не так очевидны

heuristicum
10.06.2026 08:31Самое интересное началось как раз после того доклада 2009 года. Языки, которые проектировали уже с оглядкой на признание Хоара, эту дырку начали заделывать — каждый по-своему, но в одну сторону: отсутствие значения должно быть видно в типе, и компилятор должен заставить тебя его обработать.
В Haskell это Maybe. В Rust — Option<T>, и raw-null там просто нет как класса
Ну позвольте, это всё как минимум в Standard ML'е или даже в Hope появилось, к моменту этого доклада этому типу в том или ином виде минимум лет 20-30

Dhwtj
10.06.2026 08:31Это примерно как вопрос почему Страуструп не реализовал ООП как надо было по Аллану Кею.
Потому что "правильный" способ работал слишком медленно.

GeorgSokolov96
10.06.2026 08:31Страус хотя бы не прятал башку в песок...

Dhwtj
10.06.2026 08:31ему пофиг на глубокую теорию было
судя по его высказываниям "ООП это все что хорошо, C++ это хорошо, значит это ООП"

vkrasikov
10.06.2026 08:31Во-первых, не понимаю чего тут плюсуют. Статья сделана при помощи ИИ, но не в этом суть.
Во-вторых. ИЗВИНИЛСЯ??? За то, что создал null???
Ну не создал бы он, создал бы кто-нибудь другой. Потому что как без null? Разве кто-то хочет, чтоб ссылки вели куда-нибудь не туда, лишь бы не были null?

Medeyko
10.06.2026 08:31Ну не создал бы он, создал бы кто-нибудь другой.
Я абсолютно согласен с тем, что кто-нибудь в каком-нибудь языке высокого уровня обязательно сделал бы nil - это слишком напрашивающееся решение, это просто индикатор указателя, которому не присвоено значение; естественная альтернатива этому - случайное значение (что ещё хуже), а вовсе не современные абстрактны средства систем типов. nil ведь как раз и был придуман как дешёвый способ отличить неинициализированный указать от инициализированного.
Если бы Хоар в языке Algol-W сделал бы умную обработку ситуации с неинициализированными указателями, то, естественно, языков с корректной обработкой (с умной системой типов) было бы больше - потому что многие языки позаимствовали многие идеи из Алгола; а имеющих эту проблему - меньше. Но, думаю, вряд ли очень уж сильно. В Алголе были различные навороты, от которых при проектировании других языков отказались ради простоты и производительности, и от этого наворота отказались бы в пользу обычного null.
Да и похожая идея, по большому счёту, придумана независимо в Си - null-terminated string, пожалуй.
Разве кто-то хочет, чтоб ссылки вели куда-нибудь не туда, лишь бы не были null?
Речь не об этом, речь о языковых конструкциях, вынуждающих программиста использовании указателей ("разыменовании указателя") обрабатывать ситуацию, когда указатель не инициализирован, а не продолжать использовать его, как будто он инициализирован. Введение nil позволило программисту проверять, инициализирован ли указатель, но не заставило его это делать.

Alex_Veremeenko
10.06.2026 08:31Был вариант - использовать Null Object Pattern.
Но производительность падала сильно.

Kenya-West
10.06.2026 08:31Разве кто-то хочет, чтоб ссылки вели куда-нибудь не туда, лишь бы не были null?
Не очень в тему, но напомнило параллельный мем:
Скрытый текст

Совсем пользователя без ответа оставлять нельзя

OnoIt
10.06.2026 08:31Хоар в тот момент проектировал систему типов для ALGOL W — по сути, первую серьёзную, всеобъемлющую систему типов для ссылок в объектно-ориентированном языке
ALGOL W - не объектно-ориентированный язык. Но если вы знаете о неизвестных работах Хоара в этой области, то это хорошая тема для следующей статьи

wmlab
10.06.2026 08:31А вот в современном C#/.NET есть типы string? и string. первый может указывать на null, второй - нет. дыра, наконец, закрыта?

pashagoroshko
10.06.2026 08:31Видимо вы плохо знаете современный си шарп: и там и там будет null, это ссылочный тип и защиты от null как только проверить if'ом на null нет.
Но тут нет проблемы, просто нужно себя приучать всегда проверять на null если может случиться Null reference exception

Alex_Veremeenko
10.06.2026 08:31Уже есть Nullable reference types и опции компилятора, компилятор ловит уже такие случаи.

victor_1212
10.06.2026 08:31для тех кому интересна история -
можно сказать Konrad Zuse Plankakul был в самом начале, примерно в 1949-50 он вел семинар и читал лекции в Мюнхене по архитектуре Z3, Z4 и компиляции Plankalkul, на этих семинарах впервые появились идеи по использованию стека, reverse polish notation и пр., участники приняли позже активное участие в проекте ALGOL-58


потребность в языках высокого языка была очевидна всем, но особенно при разработке больших систем реального времени, поэтому не ожидая окончания стандартизации на основе ALGOL-58 в US был создан JOVIAL, Julers Own Version of IAL, IAL = International Algebraic Language, так тогда называли ALGOL-58 в US, Jules (Jules Schwarz) один из его авторов, JOVIAL был основной язык программирования больших систем реального времени следующие 20 лет до появления ADA,
после стандартизации ALGOL-60, международный комитет продолжил работу, через несколько лет появился новый проект ALGOL W, и его конкурент ALGOL-68, который был одобрен как более продвинутый, позже ALGOL W стал основой PASCAL

Affdey
10.06.2026 08:31А в чём суть вышеописанной проблемы? Пустая строка это пустая строка, и что? Или это необъявленная переменная? Я вроде не сталкивался с проблемой и падениями "все знакомы с этой бедой". Бригаду....

diverdm
10.06.2026 08:31Не пустая строка, а отсутствие строки. Присутствие отсутствия вообще чего бы то ни было.

White_Scorpion
10.06.2026 08:31Проблема в том, что рано или поздно кто-то будет чинить совершенно специфический баг, который должен работать "ВЧЕРА!" и сооблазнится "меньшим злом" в виде "быстрого решения" - подготовить тип и передать посредством некой переменной в другую функцию, которая вызовет другую функцию, а та третью....
И проблема в том, что быстрое решение - не позволит ему подготовить объект "как надо" - одно из свойств подготовленного объекта будет NULL.... Но именно это свойство будет требоваться где-то в глубине стека функций, одна из которых вызовет подсвойство указанного свойства - то бишь буквально вызвав <ОбъектПередаваемогоТипа>.SubProperty..... Но у NULL, который упакован под видом <ОбъектПередаваемогоТипа> нет никаких SubProperty, SubFunction или прочей фигни, присущей полноценным объектам.... И при этом у NULL есть ссылка на тип, который, в подсказке среды разработки говорит, что "всё нормуль, SubProperty и SubFunction - ЕСТЬ!
И в результате ваша программа или сервис - гакнется на проде со свистом и со стеком, который она естественно скушает, потому что это Release билд, в котором обычно включают сокращённую трассировку.

qmained
10.06.2026 08:31Из плюсов: в джаве стали часто внедрить JSpecify, штука, которая призвана всё, не помеченное как
@NonNull, и используемое без проверок на null, находить и помечать как warning. Ещё очень нравится как null реализован в котлине, там в целом нельзя засунуть null туда, где он не ожидается
ascjke
Это база, ловить NPE на проде)