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.

{
  let point = Box::new((0.625, 0.5)); // point allocated here 
  let label = format!("{:?}", point); // label allocated here 
  assert_eq!(label, "(0.625, 0.5)");
} // both dropped here

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

struct Person { name: String, birth: i32 }
let mut composers = Vec::new();
composers.push(Person { name: "Palestrina".to_string(), birth: 1525 });
composers.push(Person { name: "Dowland".to_string(), birth: 1563 });
composers.push(Person { name: "Lully".to_string(), birth: 1632 }); 

for composer in &composers {
  println!("{}, born {}", composer.name, composer.birth);
}

Moves

let s = vec!["udon".to_string(), "ramen".to_string(), "soba".to_string()];
let t = s;

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

let mut s = "Govinda".to_string();
s = "Siddhartha".to_string(); // value "Govinda" dropped here
let mut s = "Govinda".to_string();
let t = s;
s = "Siddhartha".to_string(); // nothing is dropped here

Moves and Control Flow

let x = vec![10, 20, 30]; 
if c {
  f(x); // ... ok to move from x here 
} else {
  g(x); // ... and ok to also move from x here
}
h(x); // bad: x is uninitialized here if either path uses it
let x = vec![10, 20, 30]; 
while f() {
  g(x); // bad: x would be moved in first iteration,
        // uninitialized in second
}

Moves and Indexed Content

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

// Build a vector of the strings "101", "102", ... "105"
let mut v = Vec::new(); 
for i in 101 .. 106 {
  v.push(i.to_string());
}

// Pull out random elements from the vector.
let third = v[2]; // error: Cannot move out of index of Vec 
let fifth = v[4]; // here too

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

// Build a vector of the strings "101", "102", ... "105"
let mut v = Vec::new(); 
for i in 101 .. 106 {
      v.push(i.to_string());
}

// 1. Pop a value off the end of the vector:
let fifth = v.pop().expect("vector empty!");
assert_eq!(fifth, "105");

// 2. Move a value out of a given index in the vector, 
// and move the last element into its spot:
let second = v.swap_remove(1);
assert_eq!(second, "102");

// 3. Swap in another value for the one we're taking out:
let third = std::mem::replace(&mut v[2], "substitute".to_string()); 
assert_eq!(third, "103");

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

let v = vec!["liberté".to_string(), "égalité".to_string(),
               "fraternité".to_string()];

for mut s in v { 
  s.push('!');
  println!("{}", s);
}

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:

struct Person { name: Option<String>, birth: i32 }

let mut composers = Vec::new();
composers.push(Person { name: Some("Palestrina".to_string()),
                          birth: 1525 });

let first_name = std::mem::replace(&mut composers[0].name, None); 

// Or more conveniently

let first_name = composers[0].name.take();

Copy Types: The Exception to Moves

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

let nums_1: [i32; 5] = [1, 2, 3, 4, 5];
let nums_2 = (1, 2, 3, 4, 5);

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:

#[derive(Copy, Clone)]
struct Label { number: u32 }

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.

use std::rc::Rc;

// Rust can infer all these types; written out for clarity
let s: Rc<String> = Rc::new("shirataki".to_string()); 
let t: Rc<String> = s.clone();
let u: Rc<String> = s.clone();

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.

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

assert!(s.contains("shira"));
assert_eq!(t.find("taki"), Some(5));
println!("{} are quite chewy, almost bouncy, but lack flavor", u);

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:

use std::rc::Rc;
use std::cell::RefCell;

let value = Rc::new(RefCell::new(5));
*value.borrow_mut() += 1;

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