При каких условиях Rust выбирает для неаннотированного целочисленного литерала тип i32, а когда выводит другой целочисленный тип?
Неаннотированный целочисленный литерал сначала рассматривается как значение с ещё не определённым целочисленным типом. Rust выводит конкретный тип из контекста: ограничений операций, аргументов функций, присваивания и ожидаемого типа. Если подходящих ограничений нет, срабатывает резервный выбор — i32.
Явный суффикс литерала или аннотация типа имеют приоритет над резервным выбором. Если выбранный тип не может представить значение, компиляция завершается ошибкой переполнения, а не неявным переходом к более широкому типу.
Статическая типизация требует, чтобы к моменту проверки программы тип каждого выражения был известен. Одновременно запись обычных чисел без постоянных аннотаций делает код компактнее и позволяет одному синтаксису литерала работать с разными целочисленными типами.
Механизм вывода типов решает эту задачу компромиссно: тип литерала сначала оставляют гибким, затем уточняют по контексту. Резервный тип нужен для случаев, когда контекст отсутствует и компилятору всё равно требуется получить однозначную типизированную программу.
Одинаковая запись числа может участвовать в выражениях с разными ожидаемыми типами. Например, число может быть аргументом функции, частью операции с u8 или самостоятельным значением; результат вывода в этих случаях различается.
Ошибка понимания возникает, если считать, что каждый неаннотированный литерал немедленно получает i32 или что Rust автоматически расширяет тип при переполнении. На практике неверное ожидание может привести либо к конфликту типов, либо к ошибке переполнения при компиляции.
Для неаннотированного целочисленного литерала Rust создаёт целочисленную переменную типа. Затем ограничения из окружающего выражения сужают множество допустимых типов. Если литерал передаётся параметру типа u16, он выводится как u16; если участвует в операции с уже типизированным u8, тип должен согласоваться с правилами этой операции.
Если ограничений недостаточно, применяется integer fallback: тип выбирается как i32. Для неаннотированных литералов с плавающей точкой аналогичный резервный выбор — f64, но это отдельное правило для типов с плавающей точкой.
Суффикс вроде u8 или i64 задаёт тип непосредственно. Аннотация переменной также задаёт ожидаемый тип, поэтому число может быть выведено не как i32, если контекст явно требует другое.
Вывод типа не означает автоматическое безопасное расширение диапазона. Значение должно быть представимо в выбранном типе; литерал, который не помещается в него, вызывает диагностическую ошибку. Кроме того, операции между разными целочисленными типами не становятся автоматически допустимыми только потому, что оба операнда являются числами: обычно требуется явное приведение.
Минимальный пример:
В строке с по_умолчанию тип выбирается резервным правилом, потому что дополнительного контекста нет. Вызов функции задаёт ожидаемый тип аргумента, а суффикс u64 не оставляет тип для вывода.
В сетевом модуле разработчик объявил размер буфера как обычное число и затем передал его API, принимающему u16. Пока значение используется только локально, оно может иметь тип i32; после передачи API контекст потребует u16. Если значение превышает диапазон u16, компилятор сообщит об ошибке вместо молчаливого усечения.
Рассматривались два варианта. Можно было оставить вывод типа: это уменьшает шум и позволяет использовать ожидаемый тип из API, но делает тип зависимым от места использования. Либо можно было сразу написать явную аннотацию u16: это лучше документирует протокол и раньше фиксирует ошибку диапазона, но добавляет синтаксический шум.
Для границы сетевого протокола выбрали явную аннотацию. Внутри вычислений оставили вывод типов, поскольку там тип однозначно задаётся операциями и нет отдельного контрактного значения. В результате изменения API не меняют случайно типы важных параметров, а ошибки диапазона обнаруживаются при компиляции.
Дополнительный вопрос: Если переменная получает неаннотированный литерал, а затем используется в контексте другого целочисленного типа, когда происходит выбор типа?
Ответ: Тип выводится по совокупности ограничений всей проверяемой области, а не обязательно фиксируется в момент объявления переменной. Поэтому последующее использование может уточнить тип, пока оно совместимо с уже известными ограничениями. Если к моменту завершения вывода тип всё ещё не определён, применяется резервный выбор i32.
Дополнительный вопрос: Приводит ли значение неаннотированного литерала к автоматическому расширению типа при переполнении?
Ответ: Нет. Сначала Rust выбирает тип по контексту или резервному правилу, затем проверяет, представимо ли значение в этом типе. Переполнение литерала не означает автоматическую замену i32 на i64; нужно явно изменить ожидаемый тип или указать суффикс.
Дополнительный вопрос: Чем суффикс целочисленного литерала отличается от приведения уже вычисленного значения?
Ответ: Суффикс задаёт тип самого литерала до дальнейшей проверки выражения. Приведение применяется к уже типизированному выражению и может иметь другие семантические последствия, включая усечение при переходе к меньшему целочисленному типу. Поэтому суффикс обычно точнее выражает намерение для констант, а приведение следует рассматривать как отдельную операцию преобразования.