Сравните представление опциональной ссылки и обычной ссылки в памяти: за счёт какого механизма они могут иметь одинаковый размер?
Опциональная ссылка может занимать столько же памяти, сколько обычная, благодаря механизму niche optimization — использованию недопустимого для обычной ссылки битового шаблона. Обычная ссылка не может быть нулевой, поэтому нулевое значение можно зарезервировать для варианта None, а любой ненулевой указатель — для Some.
Это оптимизация представления, а не изменение семантики: Option<&T> по-прежнему явно различает отсутствие ссылки и наличие ссылки, не позволяя получить недействительную обычную ссылку.
В системном программировании часто требуется представить три сущности: адрес объекта, отсутствие адреса и иногда отдельный флаг состояния. Наивное представление использует указатель плюс Boolean-флаг, что увеличивает размер структуры и может ухудшать размещение данных в памяти.
Rust решает эту задачу на уровне представления типов: если у типа есть гарантированно недопустимые значения, компилятор может использовать их как варианты перечисления. Такой подход сохраняет безопасную типовую модель без обязательного дополнительного поля.
Ссылка в Rust должна указывать на корректный объект и не может быть нулевой. При этом Option<&T> должен уметь выразить отсутствие ссылки. Если хранить отдельный дискриминант рядом с указателем, значение может занимать больше памяти, чем необходимо.
Неверно было бы пытаться представить None как обычную нулевую ссылку. Нулевая ссылка не становится безопасным значением только потому, что она хранится внутри перечисления: Rust должен различать безопасный Option и недействительную ссылку.
Компилятор использует нишу — битовый шаблон, который не может представлять допустимое значение базового типа. Для ссылки такой нишей является нулевой указатель. Поэтому представление обычно выглядит концептуально так:
None;Some(reference);Для Option<&T> это позволяет сохранить размер и выравнивание обычной ссылки. Аналогичная гарантия действует для некоторых других типов с недопустимыми значениями, например Option<&mut T>, Option<Box<T>> и типов NonZero*, но её нельзя автоматически переносить на любой Option<T>.
Здесь сравнивается размер типов, а не способ доступа к значениям. Получить None можно только через безопасный конструктор Option; создавать нулевую ссылку вручную нельзя.
Оптимизация не означает, что Rust обязан раскрывать одинаковое машинное представление для всех перечислений. Для произвольного Option<T> размер может быть больше размера T, если у T нет подходящей ниши. Кроме того, одинаковый размер не равен совместимости ABI: для обмена с C нужно отдельно учитывать правила внешнего интерфейса и явно проектировать его представление.
Сервис принимает необязательную ссылку на конфигурацию. Вариант с отдельным указателем и Boolean-флагом требует согласовывать два поля: указатель не должен противоречить флагу, а структура может занимать больше памяти из-за выравнивания.
Можно использовать необязательный сырой указатель, но тогда появляются ручные проверки на null, небезопасные операции и менее выразительный контракт. Можно передавать обычную ссылку и отдельный флаг, но это не отражает отсутствие ссылки в типе.
Выбранное решение — Option<&Config>. Оно явно выражает возможность отсутствия конфигурации, проверяется компилятором и для самой ссылки не требует дополнительного слова памяти. Это уменьшает размер элемента в массивах и сохраняет безопасную работу с временем жизни; цена решения — необходимость обработать оба варианта Option.
1. Сохраняется ли такое же свойство у опциональной изменяемой ссылки?
Да, Option<&mut T> также может использовать нулевой указатель как нишу, поэтому его размер обычно равен размеру &mut T и это соответствует гарантии nullable pointer optimization для ссылочных типов. Однако это не ослабляет правило уникальности: Some(&mut value) по-прежнему предоставляет исключительный доступ на время жизни ссылки.
Нельзя копировать &mut T или одновременно создать другую изменяемую ссылку на тот же объект только из-за того, что внешняя оболочка является Option. Оптимизация памяти никак не меняет правил владения и заимствования.
2. Можно ли вручную создать нулевую ссылку и использовать её как None?
Нет. Нулевая ссылка не является допустимым значением &T, даже если программист собирается немедленно проверить её на null. Создание или использование недействительной ссылки нарушает требования к корректности Rust и может привести к неопределённому поведению.
Правильный способ выразить отсутствие объекта — None. Если требуется работать с nullable-указателем на границе небезопасного API, следует хранить его как сырой указатель и преобразовывать в ссылку только после доказательства ненулевого значения и корректности объекта.
3. Означает ли одинаковый размер полную совместимость представления с C?
Нет. Совпадение размера и выравнивания — лишь часть свойств представления. ABI, порядок полей и правила интерпретации дискриминанта могут иметь дополнительные требования, поэтому нельзя безоговорочно передавать Option<&T> во внешний интерфейс как универсальный nullable-указатель.
Для FFI обычно используют специально определённые типы интерфейса, например сырой указатель с документированным соглашением о null, либо структуру с repr(C) и явными полями. Внутри Rust Option<&T> предпочтительнее, когда важны безопасность, проверка компилятором и управление временем жизни.