системный уровеньsystems level

unsafe

Ключевое слово unsafe открывает пять конкретных дополнительных возможностей, которые компилятор иначе запрещает — разыменование сырых указателей, вызов unsafe fn, доступ к изменяемым static, реализацию unsafe trait и доступ к полям union. unsafe не отключает borrow checker — он отпирает ровно эти пять операций и ничего больше.The unsafe keyword unlocks five specific additional capabilities the compiler otherwise forbids — dereferencing raw pointers, calling unsafe fn, accessing mutable statics, implementing unsafe traits, and accessing union fields. unsafe does not disable the borrow checker — it unlocks exactly those five operations and nothing more.

системный уровень и метапрограммированиеsystems level and metaprogramming частый вопрос: что именно разрешает unsafe, а что — нетcommon question: what exactly unsafe allows, and what it doesn't

Что этоWhat it is

Ключевое слово unsafe отпирает ровно пять возможностей: разыменование сырого указателя (*const T/*mut T), вызов unsafe fn (включая FFI-функции), доступ/изменение изменяемой static-переменной, реализацию unsafe trait и доступ к полям union. Критически важно: unsafe НЕ отключает borrow checker, НЕ отключает проверку перемещений и НЕ выключает никакую другую часть обычного анализа безопасности Rust — он отпирает ровно эти пять операций, а весь окружающий обычный Rust-код внутри unsafe-блока по-прежнему полностью проверяется как обычно.The unsafe keyword unlocks five specific additional capabilities the compiler otherwise forbids: dereferencing a raw pointer (*const T/*mut T), calling an unsafe fn (including FFI functions), accessing/modifying a mutable static variable, implementing an unsafe trait, and accessing a union's fields. Crucially, unsafe does NOT disable the borrow checker, does NOT disable move-checking, and does NOT turn off any other part of Rust's normal safety analysis — it unlocks exactly those five specific operations and nothing more; all the surrounding ordinary Rust code inside an unsafe block is still fully checked as normal.

Зачем ввелиWhy it was introduced

01

Некоторые операции фундаментально невыразимы для чисто структурного borrow checker'аSome operations are fundamentally inexpressible to a purely structural borrow checker

Реализация внутренностей своей структуры данных, работа напрямую с оборудованием/API ОС, FFI с C-библиотеками, построение безопасных абстракций (Vec, Rc), на которые опирается вся остальная эта база знаний, — всё это фундаментально невозможно выразить, одновременно удовлетворяя консервативному, чисто структурному анализу borrow checker'а.Implementing a custom data structure's internals, talking to hardware/OS APIs directly, FFI with C libraries, building the safe abstractions (Vec, Rc) that the rest of this knowledge base relies on — all of this is fundamentally impossible to express while satisfying the borrow checker's purely-structural, conservative safety analysis.

02

Маленькое, явно помеченное ядро вместо полного запретаA small, explicitly marked kernel instead of an outright ban

Кто-то должен написать небольшое, тщательно продуманное ядро низкоуровневого кода, на котором строится безопасная абстракция. Ответ Rust — сделать это ядро маленьким и явно помеченным (unsafe), а не (а) запрещать эти операции полностью (делая Rust непригодным для системного программирования), либо (б) вообще не различать "проверенный" и "доверься мне" код.Someone has to write the small, carefully-reasoned-about kernel of low-level code that a safe abstraction is built on top of. Rust's answer is to make that kernel small and explicitly marked (unsafe), rather than either (a) forbidding these operations entirely (making Rust unusable for systems programming) or (b) not distinguishing "checked" from "trust me" code at all.

03

Контраст с C/C++: нет синтаксического маркера "требует дополнительной проверки"The contrast with C/C++: no syntactic marker for "requires extra scrutiny"

В C/C++ буквально все операции с указателями несут один и тот же непроверяемый риск повсюду, без какого-либо синтаксического маркера, отличающего "обычный код" от "кода, требующего дополнительного внимания". unsafe в Rust — именно такой маркер.In C/C++, literally all pointer operations carry this same unchecked risk everywhere, with no syntactic marker distinguishing "ordinary code" from "code requiring extra scrutiny". unsafe in Rust is exactly that marker.

Как это работает под капотомHow it works under the hood

Объявление и разыменование сырого указателя требует unsafe-блока именно в точке разыменования:Declaring and dereferencing a raw pointer requires an unsafe block exactly at the point of dereference:

let x = 5;
let ptr = &x as *const i32;
unsafe {
    println!("{}", *ptr);  // разыменование сырого указателя — требует unsafe// dereferencing a raw pointer — requires unsafe
}

Стандартная идиома — прятать маленькую unsafe-операцию внутри БЕЗОПАСНОЙ функции с задокументированным инвариантом, который вызывающий никогда не сможет нарушить, обосновывая unsafety комментарием // SAFETY: ..., объясняющим, почему операция действительно безопасна именно здесь. Это соглашение сообщества — не декорация, а важная инженерная практика.The standard idiom is wrapping a small unsafe operation inside a SAFE function with a documented invariant the caller must never be able to violate, justifying the unsafety with a // SAFETY: ... comment explaining exactly why the operation is actually sound here. This community convention is a genuinely important norm, not decoration.

Вызов FFI-функции всегда требует unsafe, поскольку компилятор не может проверить контракт безопасности сторонней функции:Calling an FFI function is always unsafe, since the compiler cannot verify a foreign function's safety contract:

extern "C" {
    fn abs(input: i32) -> i32;
}

unsafe {
    println!("{}", abs(-3));  // компилятор не может проверить тело C-функции// the compiler can't verify the C function's body
}

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

get_unchecked-подобный прямой доступ к массиву по индексуget_unchecked-style direct raw-pointer array access

Ручная реализация производительной структуры данных с прямым обращением по сырому указателю, обоснованная комментарием SAFETY, объясняющим инвариант (индекс гарантированно в границах по логике вызывающего кода), делающий иначе непроверяемый доступ действительно безопасным.A hand-rolled performance-sensitive data structure with direct raw-pointer-based access, justified by a SAFETY comment explaining the invariant (the index is guaranteed in-bounds by the caller/prior logic) that makes the otherwise-unchecked access sound.

unsafe fn get_unchecked(data: &[i32], index: usize) -> i32 {
    // SAFETY: вызывающий гарантирует index < data.len(), проверка границ уже сделана выше// SAFETY: the caller guarantees index < data.len(), bounds already checked above
    *data.as_ptr().add(index)
}
02

Вызов C-библиотеки через FFICalling a C library via FFI

Обязательный unsafe-блок в точке вызова, поскольку компилятор не может проверить ничего о реальном поведении/контракте безопасности сторонней функции.A mandatory unsafe block at the call site, since the compiler cannot verify anything about a foreign function's actual behavior/safety contract.

extern "C" {
    fn abs(input: i32) -> i32;
}

fn safe_abs(x: i32) -> i32 {
    unsafe { abs(x) }  // маленькое unsafe-ядро за безопасной оболочкой// a small unsafe kernel behind a safe wrapper
}
03

Упрощённый набросок реализации split_at_mutA simplified sketch of split_at_mut's implementation

Безопасная обёртка вокруг unsafe-операции — упрощённый набросок того, как split_at_mut (см. slices.html) на самом деле реализован через сырые указатели, чтобы получить два непересекающихся &mut среза из одного, — то, что сам по себе borrow checker не может проверить как безопасное, хотя по факту это так, благодаря гарантии непересечения по индексу.A safe wrapper around an unsafe operation — a simplified sketch of how split_at_mut (see slices.html) is actually implemented using raw pointers to produce two non-overlapping &mut slices from one — something the safe borrow checker alone cannot verify is sound even though it genuinely is, given the index-based non-overlap guarantee.

fn split_at_mut_demo(data: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = data.len();
    let ptr = data.as_mut_ptr();
    unsafe {
        // SAFETY: [0, mid) и [mid, len) не пересекаются по построению// SAFETY: [0, mid) and [mid, len) do not overlap by construction
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}
04

Изменяемая static — риск гонки, атомики как безопасная альтернативаA mutable static — race risk, atomics as the safe alternative

Изменяемая static-переменная требует unsafe и для чтения, и для записи — это опаснее обычной переменной, поскольку несколько потоков могут читать/писать её вообще без синхронизации, реальный потенциальный data race, который система типов больше не предотвращает внутри unsafe-блока. Атомики (AtomicUsize и т.п.) — обычная безопасная альтернатива для случаев, которые выглядят как "нужна изменяемая static".A mutable static variable requires unsafe for both read and write access — this is inherently riskier than a normal variable, since multiple threads could read/write it with no synchronization at all, a real potential data race the type system can no longer prevent once inside the unsafe block. Atomics (AtomicUsize, etc.) are the usual safe alternative for cases that look like they want a mutable static.

static mut COUNTER: u32 = 0;

unsafe {
    COUNTER += 1;  // без синхронизации — реальный риск гонки при доступе из нескольких потоков// no synchronization — a real race risk if accessed from multiple threads
}
// предпочтительная безопасная альтернатива: static COUNTER: AtomicU32 = AtomicU32::new(0);// preferred safe alternative: static COUNTER: AtomicU32 = AtomicU32::new(0);

ГраблиGotchas

unsafe не значит "всё дозволено"unsafe does not mean "anything goes"

unsafe означает "компилятор доверяет ручному обоснованию программиста для этих пяти конкретных операций" — написание действительно несостоятельного unsafe-кода (например, создание двух пересекающихся &mut ссылок на одну память через трюки с сырыми указателями) остаётся undefined behavior в Rust, ровно таким же непроверяемым, как эквивалентный UB в C/C++, несмотря на то, что это формально "разрешено" компилировать — unsafe перекладывает ответственность, а не выдаёт волшебный пропуск на безопасность.unsafe means "the compiler is trusting the programmer's manual reasoning for these five specific operations" — writing genuinely unsound unsafe code (e.g. creating two overlapping &mut references to the same memory via raw pointer tricks) is still undefined behavior in Rust, exactly as unchecked as the equivalent C/C++ UB, despite superficially "being allowed" to compile — unsafe shifts responsibility, it does not grant a magically-safe pass.

Большинство реального Rust-кода вообще не содержит unsafeMost real-world Rust code has zero unsafe blocks

Использование unsafe, чтобы "исправить" непонятную ошибку borrow checker'а, вместо реструктуризации безопасного кода или использования специализированной безопасной абстракции (Rc/RefCell/и т.п., см. smart-pointers.html), — распространённый и рискованный антипаттерн у тех, кто только начал раздражаться на borrow checker.Reaching for unsafe to "fix" a borrow-checker error you don't understand, rather than restructuring the safe code or reaching for a purpose-built safe abstraction (Rc/RefCell/etc., see smart-pointers.html), is a common and risky anti-pattern for people newly frustrated with the borrow checker.

Требования состоятельности unsafe-кода часто нелокальныUnsafe code's soundness requirements are often non-local

unsafe-блок может быть технически состоятелен в изоляции, но стать несостоятельным позже, если окружающий безопасный код изменится так, что нарушит инвариант, от которого unsafe-код молча зависел (например, инвариант длины, поддерживаемый безопасным методом, который позднее рефакторинг незаметно ломает) — именно поэтому соглашение о комментарии SAFETY (документирующем, от каких именно инвариантов зависит unsafe-код) — серьёзная инженерная практика, а не декорация: будущим сопровождающим (включая будущего вас) нужно знать, что нельзя ломать.An unsafe block can be technically sound in isolation but become unsound later if surrounding safe code changes in a way that violates an invariant the unsafe code silently depended on (e.g. a length invariant maintained by a safe method that a later refactor subtly breaks) — this is precisely why the SAFETY comment convention (documenting exactly which invariants the unsafe code depends on) is a serious engineering practice, not decoration, since future maintainers (including future you) need to know what NOT to break.