C3, языковая система в традиции C, меняет подход к типизации: теперь размеры и длины по умолчанию имеют знак. Разработчик объясняет, что это решение, а не просто дань моде, так как работа с беззнаковыми числами полна скрытых опасностей. С самого начала в C3 беззнаковый тип usz (ранее usize) занимал место по умолчанию, но у него есть известные проблемы. Инфинитный цикл, возникающий при условии x >= 0 для беззнакового типа, вынуждал компилятор запрещать такие сравнения, а арифметика с неявными преобразованиями часто приводила к ошибкам, где одно число превращалось в огромное значение, ломая логику сравнения. Главная причина таких багов кроется в решении сделать беззнаковые числа стандартом для размеров.
В коде, оперирующем с индексами, приходится либо везде использовать беззнаковые типы, либо применять явные приведения. Часто разработчики механически добавляют касты, чтобы заглужить предупреждения компилятора. Если тип переменной меняется с u16 на u64, такой каст может начать молча отбрасывать старшие биты, что сложно заметить. Другой подход — минимизировать касты, допуская безопасные неявные конверсии. C3 выбрал второй путь, но при смешивании операций сложения, вычитания и деления или модуля ситуация усложнялась. Операции % (остаток) и / (деление) особенно капризны, когда один операнд беззнаковый, а другой знак имеет. Если результат превышает INT_MAX, поведение становится неочевидным. Попытки исправить это через ошибки компилятора не сработали, так как подвох скрывался в логике работы с кольцевыми буферами.
При реализации индекса в кольцевом буфере выражение ((start - offset_back) % length + length) % length работает только если offset_back корректно учитывается. При использовании беззнаковых типов результат часто оказывается неправильным, и компилятор не может сигнализировать об этой ошибке. Исторически стандарт sizeof в C стал беззнаковым size_t, что запустило волну использования беззнаковой арифметики. Даже такие языки, как Go и Java, отказались от беззнаковых типов именно из-за этих проблем, предпочтя им знаки. У беззнаковых целых чисел диапазон от 0 до 4 миллиардов кажется большим, но опасная граница находится там, где у знаковых — всего два миллиарда. Переполнение беззнакового числа часто порождает правдоподобное, но неверное значение, в то время как у знаковых оно превращается в отрицательное, что сразу бросается в глаза. Кроме того, на современных 64-битных машинах память заканчивается раньше, чем можно использовать весь диапазон знакового 64-битного типа.
В итоге команда C3 приняла решение использовать для размеров тип sz, а для явно беззнаковых — usz. Это стало событием под названием szmageddon. Явное приведение между sz и usz отменено полностью, так как сравнения между ними больше не требуются. При переписи кода многие скрытые баги стали очевидными, а код упростился. Разработчик признается, что долгое время внутренне оправдывал выбор беззнаковых типов, привыкая искать проблемы в них. Однако данные говорят обратное: переход на знаковые размеры делает код проще для анализа и корректнее, устраняя целые классы трудностей с граничными значениями и неочевидными переполнениями.