Table of Contents
RAII (Resource Acquisition Is Initialization) is fundamental to both C++ and Rust. Resources are acquired during initialization and released when the object goes out of scope. Rust's ownership system makes RAII even more powerful by guaranteeing exactly when destructors run.
| Source: | src/rust/structs |
|---|
C++ has dedicated constructor syntax. Rust doesn't have constructors as a language
feature; instead, you define associated functions (commonly named new) that
return Self.
C++:
class Point {
double x_, y_;
public:
// Default constructor
Point() : x_(0), y_(0) {}
// Parameterized constructor
Point(double x, double y) : x_(x), y_(y) {}
// Copy constructor (implicit or explicit)
Point(const Point& other) = default;
// Move constructor
Point(Point&& other) noexcept = default;
};
int main() {
Point p1; // default constructor
Point p2(3.0, 4.0); // parameterized constructor
Point p3 = p2; // copy constructor
}Rust:
struct Point {
x: f64,
y: f64,
}
impl Point {
// Associated function (not a method - no self)
fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
// Default-like constructor
fn origin() -> Self {
Point { x: 0.0, y: 0.0 }
}
}
fn main() {
let p1 = Point::origin(); // "default" constructor
let p2 = Point::new(3.0, 4.0); // parameterized constructor
let p3 = p2; // move (Point doesn't impl Copy)
}Rust's Default trait is similar to C++ default constructors:
#[derive(Default)]
struct Config {
debug: bool, // defaults to false
timeout: u32, // defaults to 0
name: String, // defaults to ""
}
fn main() {
let config = Config::default();
// Or with struct update syntax
let custom = Config {
debug: true,
..Default::default()
};
}| Source: | src/rust/raii |
|---|
C++ uses destructors (~ClassName). Rust uses the Drop trait with a drop
method. Both are called automatically when the object goes out of scope.
C++:
#include <iostream>
class Resource {
int* data;
public:
Resource(int value) : data(new int(value)) {
std::cout << "Acquired: " << *data << "\n";
}
~Resource() {
std::cout << "Released: " << *data << "\n";
delete data;
}
// Rule of five: need copy/move constructors and assignments
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
};
int main() {
Resource r(42);
// destructor called at end of scope
}Rust:
struct Resource {
data: i32,
}
impl Resource {
fn new(value: i32) -> Self {
println!("Acquired: {}", value);
Resource { data: value }
}
}
impl Drop for Resource {
fn drop(&mut self) {
println!("Released: {}", self.data);
}
}
fn main() {
let r = Resource::new(42);
// Drop::drop called at end of scope
}Variables are dropped in reverse order of declaration:
struct Droppable(i32);
impl Drop for Droppable {
fn drop(&mut self) {
println!("Dropping {}", self.0);
}
}
fn main() {
let a = Droppable(1);
let b = Droppable(2);
let c = Droppable(3);
}
// Output: Dropping 3, Dropping 2, Dropping 1Use std::mem::drop() to drop a value before its scope ends:
C++ (no direct equivalent, use scope or unique_ptr):
{
Resource r(42);
// use r
} // r destroyed here
// continue without rRust:
fn main() {
let r = Resource::new(42);
// use r
drop(r); // explicitly drop
// r is no longer valid
println!("Resource already released");
}std::mem::forget() prevents the destructor from running. Useful for FFI when
transferring ownership to C code:
use std::mem;
fn main() {
let v = vec![1, 2, 3];
mem::forget(v); // destructor won't run, memory leaked
// Use carefully - typically only for FFI
}C++ requires implementing copy/move constructors and assignment operators (Rule of Three/Five). Rust handles this through traits:
C++ Rule of Five:
class Buffer {
char* data;
size_t size;
public:
Buffer(size_t n) : data(new char[n]), size(n) {}
~Buffer() { delete[] data; }
// Copy constructor
Buffer(const Buffer& other) : data(new char[other.size]), size(other.size) {
std::copy(other.data, other.data + size, data);
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new char[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// Move constructor
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};Rust (much simpler):
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn new(size: usize) -> Self {
Buffer { data: vec![0; size] }
}
}
// Move is automatic, no implementation needed
// Clone can be derived if needed
impl Clone for Buffer {
fn clone(&self) -> Self {
Buffer { data: self.data.clone() }
}
}
// Drop is automatic for Vec, no implementation needed| Source: | src/rust/self_this |
|---|
C++ has implicit this pointer. Rust requires explicit self parameter with
three forms that integrate with ownership:
C++:
class Counter {
int value = 0;
public:
void increment() { this->value++; } // this is implicit
int get() const { return value; }
};Rust:
struct Counter {
value: i32,
}
impl Counter {
fn increment(&mut self) { // mutable borrow
self.value += 1;
}
fn get(&self) -> i32 { // immutable borrow
self.value
}
fn into_value(self) -> i32 { // takes ownership, consumes self
self.value
}
}- :doc:`rust_basic` - Ownership and borrowing fundamentals
- :doc:`rust_smartptr` - Smart pointers for shared ownership