Перейти к содержанию

Заметки о Rust - Unique and NonNull

Posted on:6 июля 2023 г. at 08:52

Example Dynamic OG Image link

Unique

Unique<T> - это тип указателя в Rust, который обычно используется внутри Rust для представления уникальной семантики владения и полезен для построения абстракций, таких как Box<T>, Vec<T>, String и HashMap<K, V>.

В стандартной библиотеке объявляется в core/src/ptr/unique.rs как:

pub struct Unique<T: ?Sized> {
    pointer: NonNull<T>,
    _marker: PhantomData<T>,
}

Unique<T> - это ненулевой ковариантный уникальный указатель. Вот что означают эти термины:

Такое сочетание свойств позволяет Rust делать определенные допущения, позволяющие оптимизировать код. Например, поскольку указатели Unique<T> не имеют значения NULL и уникальны, оптимизатор Rust может предположить, что переопределение Unique<T> всегда безопасно и что указатель не будет изменен или перемещен какой-либо другой частью кода.

Обратите внимание, что Unique<T> является внутренней детализацией реализации стандартной библиотеки Rust. Это не тип, который обычно используется непосредственно в собственном коде. Вместо этого можно использовать такие типы, как Box<T>, Vec<T> и т.д., которые создаются с использованием Unique за кулисами.

Например, Box<T> объявляется в стандартной библиотеке в файле alloc/src/boxed.rs как:

pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);

PhantomData

Как показано выше, Unique<T> имеет поле _marker типа PhantomData<T>.

PhantomData<T> в Rust - этот тип используется, чтобы указать, что дженерик тип T - часть структуры данных, даже если T не используется в структуре данных.

PhantomData<T> играет важную роль в способности компилятора Rust понимать владение, заимствование и время жизни, и это важно при создании сложных структур данных или при работе с необработанными указателями.

Теперь Unique<T> обычно используется с PhantomData, поскольку сам Unique<T> не «владеет» своим указателем в системе типов Rust. Фактическое владение часто выражается с помощью PhantomData.

В объявлении Unique<T> структуры PhantomData<T> выражает, что он «владеет» T, даже если T фактически не используется в структуре. Это говорит компилятору Rust, что при удалении Unique<T> он может удалить T, поэтому он не должен позволять использовать T впоследствии.

Без PhantomData<T> система типов Rust не поймет, что Unique<T> владеет T, и это потенциально может привести к неопределенному поведению.

Таким образом, PhantomData<T> и Unique<T> часто используются вместе в Rust для выражения сложных отношений владения, которые система типов Rust не может выразить самостоятельно.

NonNull

Кроме того, Unique<T> не хранит необработанный указатель напрямую. Вместо этого он является указателем поля типа NonNull<T>.

В Rust значение NonNull<T> является оберткой необработанного указателя, которое гарантированно не равно NULL. Это по существу то же самое, что и *mut T, но с добавленным инвариантом, что он не является нулевым.

Необработанные указатели в Rust обычно используются при взаимодействии с кодом C или при построении небезопасных абстракций Rust. Однако указатели NULL часто могут вызывать ошибки и сбои, поэтому NonNull<T> предоставляется как способ гарантировать, что необработанный указатель никогда не будет null.

Это дает некоторые гарантии безопасности в том смысле, что перед использованием не нужно проверять значение NULL. Но имейте в виду, что, несмотря на то, что оно не может быть нулевым, использование NonNull<T> по-прежнему небезопасно, поскольку оно не дает вам никаких гарантий заимствования или срока службы, которые ссылаются (&T и &mut T).

Ниже приведен пример использования NonNull<T>:

use std::ptr::NonNull;

let mut value = 10;
let pointer = NonNull::new(&mut value as *mut _);if let Some(ptr) = pointer {
    unsafe {
        *ptr.as_ptr() = 20;
    }
    assert_eq!(value, 20);
}

В этом примере NonNull::new используется для создания нового NonNull<T>. Затем NonNull::as_ptr используется для получения необработанного указателя, который можно отменить и изменить в unsafe блоке.