2920 words
15 minutes
unique_ptr_VS_shared_ptr_Ownership

link#



Ownership Concepts|🔝|#

Yes. Vulkan is a great example of where C++ RAII and ownership concepts become extremely useful.

One important clarification first:

Vulkan itself does not use C++ ownership semantics internally. Vulkan is a C API with explicit vkCreate* / vkDestroy* functions. C++ Vulkan applications commonly build RAII ownership wrappers around Vulkan handles.

For example:

Vulkan
──────
vkCreateBuffer()

VkBuffer handle

vkDestroyBuffer()

C++ RAII wrapper
──────
constructor

owns VkBuffer

destructor

vkDestroyBuffer()

1. A Vulkan object is basically a handle|🔝|#

Consider a Vulkan buffer:

VkBuffer buffer;

You create it with something like:

vkCreateBuffer(
    device,
    &create_info,
    nullptr,
    &buffer
);

And eventually you must do:

vkDestroyBuffer(
    device,
    buffer,
    nullptr
);

Vulkan does not automatically destroy the buffer for you.

So this is possible:

void bad_function(VkDevice device)
{
    VkBuffer buffer;

    vkCreateBuffer(
        device,
        &create_info,
        nullptr,
        &buffer
    );

    // use buffer

    // Forgot vkDestroyBuffer()!
}

That’s a resource leak.


2. C++ RAII fixes this|🔝|#

You can create a C++ wrapper:

class Buffer {
public:
    Buffer(VkDevice device, const VkBufferCreateInfo& info)
        : device_(device)
    {
        if (vkCreateBuffer(
                device_,
                &info,
                nullptr,
                &buffer_) != VK_SUCCESS)
        {
            throw std::runtime_error("vkCreateBuffer failed");
        }
    }

    ~Buffer()
    {
        if (buffer_ != VK_NULL_HANDLE)
        {
            vkDestroyBuffer(
                device_,
                buffer_,
                nullptr
            );
        }
    }

private:
    VkDevice device_;
    VkBuffer buffer_{VK_NULL_HANDLE};
};

Now:

void foo(VkDevice device)
{
    Buffer buffer(device, create_info);

    // use buffer

} // Buffer::~Buffer()
  //     ↓
  // vkDestroyBuffer()

This is classic RAII.


3. The C++ object now represents ownership|🔝|#

Without the wrapper:

VkBuffer

   │ programmer must remember

vkDestroyBuffer()

With the wrapper:

Buffer

   ├── VkDevice

   └── VkBuffer

          │ owned by Buffer

     destructor

  vkDestroyBuffer()

So you can think:

Buffer buffer(...);

as:

“This C++ object owns this Vulkan buffer.”


4. Ownership hierarchy in Vulkan|🔝|#

This becomes particularly interesting because Vulkan resources have relationships.

For example:

VkInstance

    └── VkDevice

          ├── VkBuffer
          ├── VkImage
          ├── VkShaderModule
          ├── VkPipeline
          ├── VkCommandPool
          │       │
          │       └── VkCommandBuffer

          └── VkSemaphore

A Vulkan object often requires another Vulkan object to destroy it.

For example:

vkDestroyBuffer(
    device,
    buffer,
    nullptr
);

The VkDevice is required.

So the C++ wrapper needs to understand that relationship.


5. A more realistic Buffer wrapper|🔝|#

We could write:

class Buffer {
public:
    Buffer(
        VkDevice device,
        const VkBufferCreateInfo& info
    )
        : device_(device)
    {
        VkResult result =
            vkCreateBuffer(
                device_,
                &info,
                nullptr,
                &buffer_
            );

        if (result != VK_SUCCESS)
        {
            throw std::runtime_error(
                "vkCreateBuffer failed"
            );
        }
    }

    ~Buffer()
    {
        vkDestroyBuffer(
            device_,
            buffer_,
            nullptr
        );
    }

    VkBuffer get() const
    {
        return buffer_;
    }

private:
    VkDevice device_;
    VkBuffer buffer_{VK_NULL_HANDLE};
};

Usage:

void render(VkDevice device)
{
    VkBufferCreateInfo info{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = 1024,
        .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
    };

    Buffer vertex_buffer(device, info);

    vkCmdBindVertexBuffers(
        command_buffer,
        0,
        1,
        &vertex_buffer.get(),
        offsets
    );

} // automatically destroys VkBuffer

The important thing is that the caller doesn’t need to remember:

vkDestroyBuffer(...);

6. Now introduce std::unique_ptr|🔝|#

You can also express ownership explicitly using unique_ptr.

For example, imagine:

class Buffer {
public:
    // ...
};

Then:

auto buffer =
    std::make_unique<Buffer>(device, info);

Now:

unique_ptr

    │ owns

 Buffer

    │ owns

 VkBuffer

When the unique_ptr is destroyed:

unique_ptr destructor

Buffer destructor

vkDestroyBuffer()

This gives you two levels of RAII.


7. Why Vulkan ownership becomes important#

Consider this:

void create_buffer(VkDevice device)
{
    Buffer buffer(device, info);

    return;
}

At the end:

buffer

~Buffer()

vkDestroyBuffer()

The Vulkan resource cannot accidentally remain allocated because the C++ object’s lifetime controls the Vulkan resource’s lifetime.

This is one of the biggest benefits of RAII in graphics programming.


8. But there is an important Vulkan dependency#

Suppose:

VkDevice

   └── VkBuffer

You cannot destroy the device first and then destroy the buffer.

Wrong:

VkDevice destroyed

VkBuffer destroyed

Correct:

VkBuffer destroyed

VkDevice destroyed

C++ object lifetime can naturally represent this.

For example:

class Device {
public:
    // ...
};

class Buffer {
public:
    explicit Buffer(Device& device)
        : device_(device)
    {
        // create VkBuffer
    }

    ~Buffer()
    {
        // destroy VkBuffer
    }

private:
    Device& device_;
};

Then:

void application()
{
    Device device;

    {
        Buffer buffer(device);

        // use buffer
    }

    // buffer is destroyed here

} // device destroyed here

The order is:

Buffer constructor

Device already exists

use Buffer

Buffer destructor

vkDestroyBuffer()

Device destructor

vkDestroyDevice()

This is very similar to expressing a resource dependency through ownership/lifetime.


9. Vulkan-Hpp takes this idea further#

There is also an official C++-oriented Vulkan API called Vulkan-Hpp.

Instead of:

VkBuffer buffer;
vkCreateBuffer(...);
vkDestroyBuffer(...);

you can use RAII-style Vulkan-Hpp types.

For example, conceptually:

vk::raii::Device device(...);

vk::raii::Buffer buffer(
    device,
    create_info
);

Then:

{
    vk::raii::Buffer buffer(device, create_info);

    // use buffer

} // buffer automatically released

The hierarchy becomes:

vk::raii::Instance


vk::raii::PhysicalDevice


vk::raii::Device

        ├── vk::raii::Buffer
        ├── vk::raii::Image
        ├── vk::raii::Pipeline
        └── ...

This is much closer to the style Rust programmers expect.


10. Ownership doesn’t mean “Vulkan owns it”#

This is an important distinction.

When we say:

Buffer buffer(device, info);

we’re talking about C++ ownership of the Vulkan handle.

Vulkan itself doesn’t know that your C++ object exists.

Vulkan only sees:

VkDevice
VkBuffer

Your C++ wrapper establishes the rule:

C++ Buffer object

       │ owns

   VkBuffer

       │ release with

vkDestroyBuffer()

11. Vulkan handles aren’t ordinary pointers#

A VkBuffer looks like a pointer-like handle:

VkBuffer buffer;

But you shouldn’t think:

delete buffer;

That’s wrong.

Vulkan requires:

vkDestroyBuffer(device, buffer, nullptr);

That’s exactly why custom deleters / RAII wrappers are useful.

For example:

struct BufferDeleter {
    VkDevice device;

    void operator()(VkBuffer buffer) const
    {
        if (buffer != VK_NULL_HANDLE)
        {
            vkDestroyBuffer(
                device,
                buffer,
                nullptr
            );
        }
    }
};

Then conceptually:

using BufferPtr =
    std::unique_ptr<
        std::remove_pointer_t<VkBuffer>,
        BufferDeleter
    >;

However, because Vulkan handle types vary between platforms/configurations and are not necessarily ordinary pointer types, a hand-written RAII wrapper is often clearer than forcing every Vulkan handle into unique_ptr.


12. A better Vulkan C++ design#

A common design is:

class Buffer {
public:
    Buffer(VkDevice device, ...)
        : device_(device)
    {
        vkCreateBuffer(
            device_,
            ...,
            &buffer_
        );
    }

    ~Buffer()
    {
        vkDestroyBuffer(
            device_,
            buffer_,
            nullptr
        );
    }

    Buffer(const Buffer&) = delete;
    Buffer& operator=(const Buffer&) = delete;

    Buffer(Buffer&& other) noexcept
        : device_(other.device_),
          buffer_(other.buffer_)
    {
        other.buffer_ = VK_NULL_HANDLE;
    }

    Buffer& operator=(Buffer&& other) noexcept
    {
        if (this != &other)
        {
            vkDestroyBuffer(
                device_,
                buffer_,
                nullptr
            );

            device_ = other.device_;
            buffer_ = other.buffer_;

            other.buffer_ = VK_NULL_HANDLE;
        }

        return *this;
    }

private:
    VkDevice device_;
    VkBuffer buffer_{VK_NULL_HANDLE};
};

Now we’ve implemented something very similar to Rust ownership.


13. Why delete copying?#

This:

Buffer(const Buffer&) = delete;

prevents:

Buffer a(device, info);
Buffer b = a; // ❌

Why?

Because otherwise:

a ─────┐

       ├──> VkBuffer

b ─────┘

Then:

~a()

vkDestroyBuffer()

~b()

vkDestroyBuffer()   ← 💥 same Vulkan handle

That’s essentially a double destruction problem.

So we make Buffer uniquely owning.


14. Move instead of copy#

Instead:

Buffer a(device, info);

Buffer b = std::move(a);

Now:

before:

a ─────> VkBuffer


after:

a ─────> VK_NULL_HANDLE

b ─────> VkBuffer

Only b owns the Vulkan buffer.

This is very similar to Rust:

let a = Buffer::new(&device);

let b = a;

Ownership moves from a to b.


15. The C++ ↔ Rust correspondence#

This is the really useful mental model for Vulkan.

C++ Vulkan                         Rust concept
────────────────────────────────────────────────────

Buffer buffer(...)                 let buffer = Buffer::new(...)

Buffer(const Buffer&) = delete    non-Copy type

Buffer(Buffer&&)                  move

std::move(buffer)                 ownership move

~Buffer()                         Drop

VkBuffer inside Buffer            resource owned by struct

vkDestroyBuffer()                 Drop::drop()

const Buffer&                     &Buffer

Buffer&                           &mut Buffer

So this C++:

class Buffer {
public:
    ~Buffer() {
        vkDestroyBuffer(device_, buffer_, nullptr);
    }

    Buffer(const Buffer&) = delete;

    Buffer(Buffer&& other) noexcept;
};

is conceptually very close to:

struct Buffer {
    device: Device,
    buffer: vk::Buffer,
}

impl Drop for Buffer {
    fn drop(&mut self) {
        unsafe {
            vkDestroyBuffer(
                self.device.handle(),
                self.buffer,
                std::ptr::null(),
            );
        }
    }
}

16. But Vulkan has an additional ownership concept#

There is another meaning of ownership in Vulkan that you should not confuse with C++ ownership.

Vulkan has concepts such as queue-family ownership for resources.

For example:

Queue Family 0

       │ owns

    VkImage

       │ ownership transfer

Queue Family 1

       │ owns

    VkImage

That’s Vulkan synchronization/resource ownership, not C++ object ownership.

So there are really three different concepts:

                OWNERSHIP

C++ ownership

      └── Who destroys the Vulkan object?


Vulkan resource ownership

      └── Which queue family owns a resource?


Rust ownership

      └── Which variable/value owns the object?
          Who may borrow it?
          How long is the borrow valid?

These concepts interact, but they are not the same thing.


17. The big picture#

A well-designed C++ Vulkan application might look conceptually like:

Application

    └── Instance

          └── Device

                ├── Swapchain
                │     ├── Image
                │     └── ImageView

                ├── Buffer
                │     └── DeviceMemory

                ├── ShaderModule

                ├── Pipeline

                └── CommandPool

                       └── CommandBuffer

And the C++ lifetimes can mirror that dependency tree:

construct

Instance

Device

resources

use Vulkan

resources destroyed

Device destroyed

Instance destroyed

That is where RAII + ownership becomes extremely powerful in Vulkan.

Instead of writing thousands of lines of:

vkDestroyBuffer();
vkDestroyImage();
vkDestroyImageView();
vkDestroyPipeline();
vkDestroyShaderModule();
vkDestroySwapchainKHR();
vkDestroyDevice();
vkDestroyInstance();

at various cleanup points, you design your C++ objects so that:

{
    Device device(...);

    {
        Buffer buffer(device, ...);
        Image image(device, ...);
        Pipeline pipeline(device, ...);

        // rendering...
    }

    // Buffer/Image/Pipeline destroyed here

} // Device destroyed here

The C++ object lifetime becomes the lifetime of the Vulkan resource.

That’s the central connection between Vulkan, C++ RAII, and Rust-style ownership.


Please compare C++ and Rust and explain them in detail.#

  • Yes. The most important distinction is:

C++ RAII and Rust ownership solve a similar resource-management problem, but Rust makes ownership, borrowing, aliasing, and lifetimes part of the type system and compiler checks.

Let’s compare them step by step.


1. The basic idea: “who owns the resource?”#

Suppose we allocate an object.

C++#

auto p = std::make_unique<int>(42);

p owns the int.

When p is destroyed:

p goes out of scope

unique_ptr::~unique_ptr()

delete int

Rust#

let p = Box::new(42);

p owns the i32.

When p goes out of scope:

p goes out of scope

Drop

memory is deallocated

So these are conceptually similar:

C++                              Rust

std::unique_ptr<T>        ≈      Box<T>
destructor                ≈      Drop
RAII                      ≈      ownership + Drop

2. C++ RAII#

RAII means:

Resource Acquisition Is Initialization

The resource is acquired during construction and released during destruction.

For example:

class File {
public:
    explicit File(const char* path) {
        file_ = std::fopen(path, "r");

        if (!file_) {
            throw std::runtime_error("failed to open file");
        }
    }

    ~File() {
        std::fclose(file_);
    }

private:
    FILE* file_;
};

Usage:

void foo()
{
    File file("data.txt");

    // use file
}

When foo() returns:

foo()

 ├── File constructed

 ├── use File

 └── File destructor

       fclose()

This is extremely powerful.

Even if an exception happens:

void foo()
{
    File file("data.txt");

    do_something();

    throw std::runtime_error("error");
}

the destructor still runs:

exception

stack unwinding

File::~File()

fclose()

That’s why C++ RAII is such an important design pattern.


3. Rust does essentially the same thing#

Rust:

struct File {
    file: std::fs::File,
}

std::fs::File itself implements Drop.

fn foo() -> std::io::Result<()> {
    let file = std::fs::File::open("data.txt")?;

    // use file

    Ok(())
}

When foo() returns:

foo()

 ├── File created

 ├── use File

 └── Drop

    close file

So both languages have deterministic destruction.


4. But Rust adds ownership rules#

This is where the major difference starts.

Consider C++:

std::string a = "hello";
std::string b = a;

This copies the string.

You now have:

a ──> "hello"

b ──> "hello"

Both objects independently own their data.

Rust:

let a = String::from("hello");
let b = a;

This is not a copy.

Ownership moves:

before:

a ──> String ──> "hello"


after:

a         b
│         │
│         └──> String ──> "hello"

└── moved

Therefore:

println!("{}", a);

produces a compile-time error.

The compiler says, essentially:

a was moved into b.


5. Why does Rust do this?#

Consider a hypothetical C++ class containing a raw pointer:

class Buffer {
    int* data;
};

If copying is implemented incorrectly:

Buffer A ──┐
           ├──> memory
Buffer B ──┘

Now both objects think they own the same memory.

When they are destroyed:

A destructor

delete memory

B destructor

delete memory AGAIN

That’s a double free.

Rust’s ownership system is designed to make this class of mistake impossible in safe code.


6. Rust move semantics#

Consider:

fn consume(value: String) {
    println!("{value}");
}

fn main() {
    let s = String::from("hello");

    consume(s);

    // println!("{s}"); // ❌ error
}

Calling:

consume(s);

moves ownership into consume.

main                         consume
────                         ───────

s ───── ownership ────────> value

When consume() finishes:

value

Drop

memory released

There is no second owner in main.


7. C++ equivalent#

In modern C++, you can explicitly express a similar idea:

void consume(std::unique_ptr<std::string> value)
{
    std::cout << *value;
}

int main()
{
    auto s = std::make_unique<std::string>("hello");

    consume(std::move(s));

    // s is now empty
}

This is very similar:

C++                              Rust

std::unique_ptr<T>        ≈      owned T

std::move(s)              ≈      move of s

unique_ptr destructor     ≈      Drop

But notice something important:

Rust#

consume(s);

The move happens automatically.

C++#

consume(std::move(s));

You explicitly tell C++:

I am transferring ownership.


8. Borrowing: the really important Rust feature#

Now suppose we don’t want to transfer ownership.

We just want to temporarily use the object.

Rust:

fn print_string(s: &String) {
    println!("{s}");
}

fn main() {
    let s = String::from("hello");

    print_string(&s);

    println!("{s}"); // OK
}

&s means:

Borrow s without taking ownership.

The ownership remains:

main

s ───────────────> String

 │ temporary borrow

print_string(&s)

After print_string() returns, the borrow ends.


9. C++ references are similar#

C++:

void print_string(const std::string& s)
{
    std::cout << s;
}

int main()
{
    std::string s = "hello";

    print_string(s);

    std::cout << s; // OK
}

This is conceptually very similar to Rust:

C++                              Rust

const T&                   ≈      &T
T&                         ≈      &mut T
pass by reference          ≈      borrow

But there’s an enormous difference.

Rust’s compiler verifies that the reference is valid.


10. Mutable borrowing#

Rust:

fn change(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let mut s = String::from("hello");

    change(&mut s);

    println!("{s}");
}

The &mut means:

Give me exclusive mutable access temporarily.

Rust enforces:

Either:

    many immutable references

OR:

    one mutable reference

but NOT both simultaneously.

For example:

let mut s = String::from("hello");

let a = &s;
let b = &s;

println!("{a} {b}");

Valid.

Multiple readers are allowed.

But:

let mut s = String::from("hello");

let a = &mut s;
let b = &mut s;

❌ Compile error.

Two simultaneous mutable references are forbidden.


11. Why is that useful?#

Because it prevents data races and many aliasing bugs.

Imagine:

             same object

        ┌────────┴────────┐
        ↓                 ↓
    mutable A          mutable B
        │                 │
        └──── conflict ───┘

Rust says:

No. You cannot have two simultaneous mutable aliases.

This rule is called the aliasing XOR mutability principle:

many readers
    OR
one writer

12. C++ allows this|🔝|#

C++:

int value = 42;

int& a = value;
int& b = value;

a = 10;
b = 20;

This is perfectly legal.

C++ trusts the programmer.

That flexibility is useful, but it means the programmer has to ensure that the references are used safely.

Rust moves much of that responsibility to the compiler.


13. Lifetimes|🔝|#

This is another major difference.

Consider C++:

int& bad()
{
    int x = 42;
    return x;
}

This compiles with a diagnostic from the compiler, but if you actually use the returned reference, the program has undefined behavior.

The problem:

bad()

 ├── x created

 └── x destroyed

      return reference

   reference points nowhere

Rust rejects the equivalent:

fn bad() -> &i32 {
    let x = 42;
    &x
}

The compiler says, essentially:

You are returning a reference to something that will be destroyed.

Rust’s borrow checker understands the lifetime relationship.


14. Explicit lifetime example|🔝|#

You may see Rust code like:

fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() {
        a
    } else {
        b
    }
}

'a describes the relationship between the references.

It means roughly:

The returned reference is valid for a lifetime that is compatible with the borrowed inputs.

You don’t normally manipulate lifetimes at runtime.

They’re primarily compile-time information.


15. C++ has lifetimes too — but differently|🔝|#

C++ absolutely has object lifetimes:

{
    std::string s = "hello";

} // s's lifetime ends here

But C++ doesn’t have Rust’s borrow checker.

So:

const std::string& get()
{
    std::string s = "hello";
    return s; // dangerous
}

The language allows you to express many things that can become invalid.

Rust’s compiler rejects many such patterns before the program runs.


16. unique_ptr vs Rust ownership|🔝|#

This is perhaps the closest comparison.

C++#

auto p = std::make_unique<int>(42);

auto q = std::move(p);

Rust#

let p = Box::new(42);

let q = p;

Both express:

one owner

transfer ownership

new owner

But Rust’s ownership model applies much more broadly than Box.

For example:

let s = String::from("hello");

let t = s;

No Box is required.

Ownership is a fundamental language concept.


17. shared_ptr vs Rc|🔝|#

C++:

auto a = std::make_shared<int>(42);
auto b = a;

Rust:

use std::rc::Rc;

let a = Rc::new(42);
let b = Rc::clone(&a);

Both use reference counting.

       ┌────────────┐
a ────>│            │
       │   object   │
b ────>│            │
       └────────────┘

       reference count = 2

When the count reaches zero, the object is destroyed.


18. shared_ptr vs Arc|🔝|#

For multithreaded code:

C++:

auto data = std::make_shared<int>(42);

std::thread t([data] {
    std::cout << *data;
});

Rust:

use std::sync::Arc;
use std::thread;

let data = Arc::new(42);

let data2 = Arc::clone(&data);

let t = thread::spawn(move || {
    println!("{}", *data2);
});

t.join().unwrap();

The correspondence is:

C++                              Rust

shared_ptr<T>              ≈     Rc<T>
shared_ptr<T> + threads    ≈     Arc<T>

Although the exact thread-safety rules and guarantees differ.


19. Custom deleters vs Drop|🔝|#

This connects directly to your original question.

C++:

struct FileDeleter {
    void operator()(FILE* f) const {
        if (f)
            std::fclose(f);
    }
};

using FilePtr = std::unique_ptr<FILE, FileDeleter>;

Now:

FilePtr file(std::fopen("data.txt", "r"));

When file is destroyed:

unique_ptr

FileDeleter

fclose()

Rust would normally make a wrapper:

struct File {
    handle: *mut std::ffi::c_void,
}

impl Drop for File {
    fn drop(&mut self) {
        unsafe {
            close_handle(self.handle);
        }
    }
}

When:

let file = File::new();

goes out of scope:

File

Drop::drop()

close_handle()

So:

C++                              Rust

custom deleter             ≈     Drop implementation

unique_ptr<T, D>            ≈    struct T + impl Drop

20. RAII is not the same thing as ownership|🔝|#

This distinction is important.

RAII is primarily a resource-management technique:

constructor

acquire resource

object exists

destructor

release resource

Rust ownership is a language-level system governing:

  • who owns a value
  • when ownership moves
  • who can borrow it
  • whether borrowing is mutable
  • how long references remain valid
  • whether aliases are allowed

So:

C++ RAII

    └── automatic resource cleanup


Rust ownership

    ├── ownership
    ├── moves
    ├── borrowing
    ├── mutable borrowing
    ├── lifetimes
    ├── aliasing rules
    └── Drop

Rust uses RAII-like deterministic destruction as part of a much larger ownership model.


21. A useful side-by-side summary|🔝|#

ConceptC++Rust
Automatic destructionDestructorDrop
RAIIYesYes
Unique ownershipunique_ptrOwnership / Box<T>
Shared ownershipshared_ptrRc<T>
Thread-safe shared ownershipshared_ptr + synchronizationArc<T>
Movestd::move()Move is built into language
Immutable borrowconst T&&T
Mutable borrowT&&mut T
Lifetime checkingLimitedCompiler/borrow checker
Aliasing checkingMostly programmer responsibilityCompiler enforced in safe code
Data-race preventionProgrammer/library responsibilityStrongly enforced by type system
Custom resource cleanupDestructor/custom deleterDrop
Ownership in type systemPartiallyFundamental

22. The biggest conceptual difference|🔝|#

I would summarize it like this:

C++#

"Here are powerful tools for managing ownership."

        unique_ptr
        shared_ptr
        references
        RAII
        move semantics
        custom deleters



Programmer must use them correctly.

Rust#

"Ownership is a fundamental language rule."

        ownership

        move semantics

        borrowing

        &T / &mut T

        lifetime checking

        Drop



Compiler verifies the rules.

That’s why Rust can reject code before it runs that would potentially produce use-after-free, double-free, dangling-reference, or many data-race problems in C++.

And this is the key connection to your original quote:

C++ unique_ptr + RAII is conceptually close to Rust ownership + Drop, but Rust’s ownership system goes considerably further by statically enforcing how references and ownership may be used.

unique_ptr_VS_shared_ptr_Ownership
https://younghakim7.github.io/blog/posts/unique_ptr_vs_shared_ptr_ownership/
Author
YoungHa
Published at
2026-08-16