Ownership and Moves

Example of ownership in C/C++:

std::string s = "faryed knot";

s itself is on stack. s owns a buffer on heap.


Assignment in Python vs C++:

s = ['udon', 'ramen', 'soba'] 
t=s
u=s

s, t and u actually points to the same PyListObject.

using namespace std;
vector<string> s = { "udon", "ramen", "soba" }; 
vector<string> t = s;
vector<string> u = s;

s, t and u each points to a new copy of the list.

Ownership

A variable owns its value. When control leaves the block in which the variable is declared, the variable is dropped, so its value is dropped along with it.

fn print_padovan() {
  let mut padovan = vec![1,1,1]; // allocated here 
  for i in 3..10 {
    let next = padovan[i-3] + padovan[i-2];
    padovan.push(next);
  }
  println!("P(1..10) = {:?}", padovan);
} // dropped here

Box serves as another example of ownership. A Box<T> is a pointer to a value of type T stored on the heap. Calling Box::new(v) allocates some heap space, moves the value v into it, and returns a Box pointing to the heap space. A Box owns the space it points to, when the Box is dropped, it frees the space too.

Just as variables own their values, structs own their fields, and tuples, arrays, and vectors own their elements.

Screenshot 2024-02-21 at 10.21.05 PM

Moves

The ownership of the Vector is moved to t.

If you want s to retain its ownership of the vector, then you should use s.clone() on assignment to t.

More Operations That Move

Moves and Control Flow

Moves and Indexed Content

Not every kind of value owner is prepared to become uninitialized.

Screenshot 2024-02-23 at 2.50.11 PM

If you really do want to move an element out of a vector:

for ... in ... :Collection types like Vec also generally offer methods to consume all their elements in a loop:

When we pass the vector to the loop directly, as in for ... in v, this moves the vector out of v, leaving v uninitialized.

The for loop’s internal machinery takes ownership of the vector and dissects it into its elements.

At each iteration, the loop moves another element to the variable s. Since s now owns the string, we’re able to modify it in the loop body before printing it.


If you do want to move an element out of index, consider change the value you'd like to move to Optional:

Copy Types: The Exception to Moves

A tuple or fixed-size array of Copy types is itself a Copy type. For example:

They can both be copied.

If all the fields of your struct are themselves Copy, then you can make the type Copy as well by placing the attribute # [derive(Copy, Clone)] above the definition:

While C++ lets you overload assignment operators and define specialized copy and move constructors, Rust doesn’t permit this sort of customization.

Rc and Arc: Shared Ownership

Rust provides the reference-counted pointer types Rc and Arc.

The Rc and Arc types are very similar; the only difference between them is that an Arc is safe to share between threads directly—the name Arc is short for atomic reference count—whereas a plain Rc uses faster non-thread-safe code to update its reference count.

Cloning an Rc<T> value does not copy the T; instead, it simply creates another pointer to it and increments the reference count.

Difference Between Copy and Clone

  • Copy: This trait creates a bitwise copy of an object and is implemented only by types that can be trivially duplicated. This means that the original and the copied objects occupy separate memory spaces, with no complex ownership concerns.

  • Clone: This trait performs a deep copy, potentially duplicating the data that the object owns. Unlike Copy, Clone can handle ownership complexities, allowing for more sophisticated duplication of objects.

In the case of Rc, cloning it does not create a fully independent copy. Instead, the cloned Rc shares the underlying heap memory with the original Rc, meaning both point to the same data.

Screenshot 2024-02-23 at 3.26.20 PM

You can use any of String’s usual methods directly on an Rc<String>:

A value owned by an Rc pointer is immutable. If you need to mutate the value inside an Rc, you have to wrap it in a RefCell, which provides interior mutability. RefCell allows you to borrow the value mutably at runtime, enforcing Rust’s borrowing rules dynamically rather than at compile time.

Here’s an example:

In this example, the RefCell allows the value inside the Rc to be mutated, even though Rc itself doesn’t permit direct mutation.

Last updated