Оцените: достаточно ли NonNull<T> для безопасного разыменования указателя в unsafe-обёртке?
Нет. NonNull<T> гарантирует только отсутствие нулевого указателя при корректном создании значения; он не доказывает действительность адреса, правильное выравнивание, инициализированность объекта, допустимость доступа или корректный срок его жизни.
Raw pointers нужны Rust для взаимодействия с системными API, аллокаторами и FFI, но сами по себе не выражают даже базовое ограничение «указатель не равен null». NonNull<T> предоставляет типизированную оболочку для этого частного инварианта и позволяет API явно отделить потенциально нулевой указатель от ненулевого.
Это не замена ссылке и не аналог владельца. Такой тип сохраняет низкоуровневую гибкость raw pointer, оставляя остальные гарантии на стороне unsafe-кода или безопасной обёртки.
Проверка указателя на null устраняет только один класс ошибок. Ненулевой адрес может указывать на освобождённую память, объект другого типа, неинициализированные байты или участок с неподходящим выравниванием.
Разыменование такого значения может привести к неопределённому поведению. Кроме того, даже физически доступная память не гарантирует допустимость создания ссылки: должны соблюдаться правила валидности, алиасинга, мутабельности и времени жизни объекта.
Обычно raw pointer преобразуют в NonNull<T> через проверку NonNull::new. Если результат равен None, указатель был нулевым. Если получено Some, известно только то, что указатель ненулевой.
Функция read всё ещё небезопасна по смыслу: её вызывающий код должен гарантировать, что p указывает на живой, инициализированный и выровненный u32, а чтение не нарушает правила доступа. Само наличие NonNull таких гарантий не добавляет.
NonNull::dangling особенно наглядно показывает ограничение: он создаёт ненулевой, корректно выровненный с точки зрения типа указатель, но не указывает на настоящий объект и не допускает разыменования. Такой маркер может использоваться, например, в пустом контейнере, пока длина показывает отсутствие элементов.
NonNull<T> также не передаёт владение памятью. Освобождать объект через него можно только при наличии отдельного контракта о том, кто владеет выделением и каким способом оно должно быть уничтожено. Для T с Drop это означает необходимость гарантировать ровно одно корректное уничтожение.
Безопасная обёртка должна хранить дополнительные инварианты в своей документации и API: срок жизни объекта, допустимый размер области, правила потокового доступа, уникальность при изменении и соответствие аллокатора. Если обёртка превращает NonNull<T> в ссылку, она обязана доказать полный контракт ссылки, а не только ненулевость.
C-библиотека возвращает указатель на живой объект дескриптора, который остаётся действительным до отдельного вызова функции освобождения. Вариант с *mut T прост, но заставляет каждый участок Rust-кода заново проверять null и легко допускает случайное разыменование недействительного значения. Вариант с Box<T> выразил бы владение Rust и мог бы привести к неправильному освобождению памяти C-библиотекой.
Практичное решение — хранить дескриптор как NonNull<T> в приватной обёртке, проверять null при получении, а операции чтения и вызов освобождения реализовать внутри тщательно проверенных unsafe-методов. При этом обёртка отдельно фиксирует, что объект не перемещается и не освобождается библиотекой до завершения использования.
Результат: нулевой указатель исключён на уровне представления, но действительность указателя обеспечивается жизненным циклом дескриптора и контрактом библиотеки. NonNull снижает вероятность одной категории ошибок, не превращая внешний контракт в автоматически безопасный.
NonNull::dangling?Нет. Это ненулевой указатель-заглушка, который может быть полезен для представления пустого состояния, но не обозначает размещённый объект. Для разыменования по-прежнему нужны живой объект, корректное выравнивание, инициализированность и допустимый доступ.
NonNull<T>, что код владеет объектом?Нет. NonNull<T> не является владельцем и не запускает Drop автоматически. Он может обозначать заимствованный объект, объект во внешней библиотеке или память, освобождение которой выполняется специальной функцией; способ уничтожения должен задаваться отдельным контрактом.
NonNull<T> в другой поток?Нет. Ненулевость не гарантирует потокобезопасность. Нужно доказать отсутствие гонок, корректную синхронизацию и допустимость совместного или исключительного доступа к T; кроме того, свойства Send и Sync самой обёртки нельзя заменять одним фактом наличия NonNull.