Пример: функция may_overflow
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// Прибавляет 2^31 - 1 к отрицательным числам без проверки переполнения.
///
/// # Safety
///
/// Вызов с `a >= 1` приводит к переполнению знакового целого, что является
/// неопределённым поведением при использовании `unchecked_add`.
unsafe fn may_overflow(a: i32) -> i32 {
unsafe { a.unchecked_add(i32::MAX) }
}
fn main() {
let x = unsafe { may_overflow(123) };
println!("{x}");
}
«Ключевое слово unsafe может иметь немного иной смысл, чем предполагают некоторые».
«Автор кода считает, что код корректен. В принципе, код безопасен».
«В этом игрушечном примере функция may_overflow предназначена для вызова только с отрицательными числами».
Спросите слушателей, могут ли они объяснить, почему may_overflow требует ключевого слова unsafe.
«Если вы не уверены, в чём проблема, давайте ненадолго остановимся и объясним. Для положительных чисел в i32 доступно только 31 бит».
В стандартном безопасном Rust переполнение целого (например, a + i32::MAX) не является неопределённым поведением: при включённых проверках переполнения (отладочная сборка) программа паникует, а в релизной сборке выполняется перенос в дополнительном коде. Однако unchecked_add — небезопасная операция, которая полностью пропускает проверки переполнения и объявляет переполнение неопределённым поведением (UB). Компиляторы оптимизируют код, исходя из того, что неопределённое поведение невозможно, и это может привести к удалению «мёртвого» кода и неожиданному поведению во время выполнения.
Скомпилируйте и запустите код: в отладочной сборке стандартная библиотека обнаруживает нарушение предусловия безопасности и паникует (unsafe precondition(s) violated: i32::unchecked_add cannot overflow). В релизной сборке unchecked_add порождает настоящее UB.
«Этот код можно использовать корректно, однако неправильное использование крайне опасно».
«А компилятор не может проверить, что использование корректно».
Это и имеем в виду, когда говорим, что ключевое слово unsafe отмечает место, где ответственность за безопасность памяти переходит от компилятора к программисту.