Rust Notes: Unique and NonNull

0xor0ne
3 min readJun 29, 2023

--

Unique

Unique<T> is a pointer type in Rust that is commonly used internally by Rust to represent the unique ownership semantics and is useful for building abstractions like Box<T>, Vec<T>, String, and HashMap<K, V>.

In the standard library is declared in core/src/ptr/unique.rs as:

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

Unique<T> is a non-null, covariant, unique pointer. Here's what these terms mean:

  • Non-null: It’s guaranteed to never be NULL.
  • Covariant: This is a property that allows it to be used with subtypes and supertypes. In Rust, the covariance of Unique<T> is used for example to ensure that Box<T> is covariant with T.
  • Unique: It represents that there’s only a single owner of the pointee.

This combination of properties allows Rust to make certain assumptions that can enable optimizations in code. For example, because Unique<T> pointers are non-null and unique, Rust's optimizer can assume that dereferencing a Unique<T> is always safe and that the pointee will not be modified or moved by any other part of the code.

Please note that Unique<T> is an internal implementation detail of Rust's standard library. As such, it's not a type that you'll typically use directly in your own code. Instead, you'd use types like Box<T>, Vec<T>, and so on, which are built using Unique behind the scenes.

For example, Box<T> is declared in the standard library in alloc/src/boxed.rs as:

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

PhantomData

As shown above, Unique<T> has a field _marker of type PhantomData<T>.

PhantomData<T> in Rust is a type that's used to indicate that a particular generic type T is a part of a data structure, even if T isn't used in the data structure.

PhantomData<T> plays a critical role in the Rust compiler's ability to understand ownership, borrowing, and lifetimes, and it's important when creating complex data structures or when dealing with raw pointers.

Now, Unique<T> is typically used with PhantomData because Unique<T> itself does not "own" its pointee in Rust's type system. The actual ownership is often expressed using PhantomData.

In the declaration of Unique<T> struct, PhantomData<T> expresses that it "owns" a T, even though the T isn't actually used in the struct. This tells the Rust compiler that when a Unique<T> is dropped, it might drop a T, so it shouldn't allow the T to be used afterwards.

Without the PhantomData<T>, Rust's type system would not understand that Unique<T> owns a T, and this could potentially lead to undefined behavior.

In summary, PhantomData<T> and Unique<T> are often used together in Rust to express complex ownership relationships that Rust's type system can't express on its own.

For more details about PhantomData see Rust Notes: PhantomData.

NonNull

Also, Unique<T> does not store a raw pointer directly. Instead, it as a field pointer of type NonNull<T>.

In Rust, NonNull<T> is a wrapper of a raw pointer that is guaranteed to not be NULL. It's essentially the same as a *mut T, but with the added invariant that it's not null.

Raw pointers in Rust are typically used when interacting with C code or when building unsafe Rust abstractions. However, null pointers can often cause bugs and crashes, so NonNull<T> is provided as a way to ensure that a raw pointer is never null.

This gives some safety guarantees in that you don’t have to check for null before using it. But keep in mind, even though it can’t be null, using a NonNull<T> is still unsafe because it doesn't give you any of the borrowing or lifetime guarantees that references (&T and &mut T) do.

Here’s an example of how NonNull<T> might be used:

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);
}

In this example, NonNull::new is used to create a new NonNull<T>. Then, NonNull::as_ptr is used to get the raw pointer, which can be dereferenced and modified in an unsafe block.

References

0xor0ne

--

--

0xor0ne

Cyber Security | Reverse Engineering | IoT/Embedded | Exploit | Linux kernel | PhD | :)