Ownership is a central part of Rust. Ownership is similar to a unique_ptr in C++, but for all variables, including referenced variables.
To create a deep copy of a variable, using the clone
method is required.
let s1 = String::from("Daltie");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
The Copy
trait copies a variable onto the stack. Scalar types and tuples implement the Copy
trait by default.
A type may either have a Copy
trait or a Drop
trait. If a type or any part of a type has implemented the Drop
trait then it cannot have the Copy
trait. This is to make it very obvious if the type is to be placed on the stack or the heap.
Similar to assignment, variables are either moved or copied into a function. Same for return values. For example:
fn main() {
let s1 = String::from("Daltie");
takes_ownership(s1); // s1 is no longer valid
let x = 3;
makes_copy(x); // x is still valid
let s2 = gives_ownership(); // The return value from gives_ownership() is moved
let s3 = takes_and_gives_ownership(s2); // s2 is no longer valid
}
// s3 is dropped.
// s1 and s2 were already moved, so nothing happens.
// x goes out of scope.
fn takes_ownership(some_str: String) {
println!("{}", some_str);
}
fn makes_copy(some_int: i32) {
println!("{}", some_int);
}
fn gives_ownership() -> String {
let some_str = String::from("Daltie");
some_str
}
fn takes_and_gives_ownership(some_str: String) -> String {
some_str
}
In Rust, you need to know what variables will be put on the stack and which variables will be added to the heap. Heap and stack variables are treated differently.
let x = 3;
let y = x;
Both x
and y
are valid and contain the value 3
until they go out of scope.
Heap variables follow similar but more strict rules to shallow copies in Python in the sense that the new variable will point to the data, however, in Rust, the old variable becomes invalid. For example:
let s1 = String::from("hello");
let s2 = s1;
Line 1 initializes a String variable by creating a string of size 5(ish) on the heap and a pointer to said string on the stack. Line 2 then takes ownership of this heap string. s1
now no longer points to the string and s1
is no longer valid and cannot be used until it is re-assigned. The data is NEVER copied, instead it behaves similarly to C++'s move semantics.
See the Rust Book for fantastic diagrams of this process, specifically Figure 4-4.
Rust lets a function borrow a variable using references.
fn main() {
let s1 = String::from("Daltie");
let len = find_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn find_length(s: &String) -> usize {
s.len()
}
// s is never actually given ownership, it is simply borrowing the String,
// thus nothing happens when s goes out of scope
Rust allows for exactly one mutable reference per scope (as long as the variable is active).
fn main() {
let mut s = String::from("Daltie");
change(&mut s);
let r1 = &mut s;
r1.push_str(" is ");
println!("{}", r1); // "Daltie Cole is"
let r2 = &mut s; // Okay because r1 is never used after r2 creation
r2.push_str(" cool!");
println!("{}", r2); // "Daltie Cole is cool!"
// Either unlimited immutable references are allowed at once OR one mutable reference is allowed per scope
let r3 = &s;
let r4 = &s;
//let r5 = &mut s; // BREAKS
println!("{}, {}", r3, r4);
}
fn change(some_str: &mut String) {
some_str.push_str(" Cole");
}