Coming from Java the concept of ownership was alien - so to solidify this knowledge I'm going to attempt a brief summary in ~5 minutes.
Ownership
Each variable in Rust has an owner. Once ownership is declared, there is exactly one binding to any given resource*.
Once ownership is moved, the previous owner no longer has access.
let x = vec![1];
let y = x;
println!("{}", x[0])
//: # (error[E0382]: borrow of moved value: `x`)
let x = vec![1];
let y = x;
println!("{}", x[0])
//: # (error[E0382]: borrow of moved value: `x`)
Copies
Ownership rules do not apply to primitive types, or anything that implements the Copy
Copy
trait (which gives
a type copy semantics instead of move semantics).
let a = 5 as i32;
let _y = a as i64;
println!("{}", a);
//: Finished [] target(s) in 0.65s
let a = 5 as i32;
let _y = a as i64;
println!("{}", a);
//: Finished [] target(s) in 0.65s
Functions
A function can also take ownership of a variable via its parameters.
fn new_owner(_x: Vec<i32>) {
// my_vec is now owned by the new_owner function
}
let my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(my_vec);
print!("{}", my_vec[0]);
// error[E0382]: borrow of moved value: `my_vec`
fn new_owner(_x: Vec<i32>) {
// my_vec is now owned by the new_owner function
}
let my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(my_vec);
print!("{}", my_vec[0]);
// error[E0382]: borrow of moved value: `my_vec`
Whenever you see f(value)
f(value)
you can know that f has received ownership of value.
Borrowing
Instead of taking ownership, a resource can be borrowed. Rather than passing by value, we can pass by reference. The reference borrows ownership of the variable, until the period where it is released again, at which point the prior owner can manipulate the data.
fn new_owner(_x: &Vec<i32>) {
// my_vec is now borrowed by the new_owner function
print!("{}", _x[0]);
}
let my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(&my_vec);
print!("{}", my_vec[0]);
// Compiles and prints: 11
fn new_owner(_x: &Vec<i32>) {
// my_vec is now borrowed by the new_owner function
print!("{}", _x[0]);
}
let my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(&my_vec);
print!("{}", my_vec[0]);
// Compiles and prints: 11
The references are immutable, so changes to internal state are forbidden.
You can take a reference (pointer) to an item, but you have to give it back. In particular, you have to give it back before the lifetime of the underlying item expires, as tracked by the compiler.
fn new_owner(_x: &Vec<i32>) {
_x.insert(0, 1);
}
// error[E0596]: cannot borrow `*_x` as mutable, as it is behind a `&` reference
fn new_owner(_x: &Vec<i32>) {
_x.insert(0, 1);
}
// error[E0596]: cannot borrow `*_x` as mutable, as it is behind a `&` reference
Mutable References
A mutable reference allows the mutation of borrowed resources.
fn new_owner(_x: &mut Vec<i32>) {
_x.insert(0, 5);
print("{}", _x[0]);
}
let mut my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(&mut my_vec);
// Compiles and prints: 5
fn new_owner(_x: &mut Vec<i32>) {
_x.insert(0, 5);
print("{}", _x[0]);
}
let mut my_vec: Vec<i32> = vec![1]; // vector containing a single element, 1
new_owner(&mut my_vec);
// Compiles and prints: 5
Key Points
- If you have a mutable reference to a value, you can have no other references to that value.
let mut s = String::from("Test");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
error[E0499]: cannot borrow `s` as mutable more than once at a time
let mut s = String::from("Test");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
error[E0499]: cannot borrow `s` as mutable more than once at a time
- At any given time, you can have either one mutable reference or any number of immutable references.
let mut s = String::from("Test");
let r1 = &s;
let r2 = &mut s;
println!("{}, {}, and {}", r1, r2);
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
let mut s = String::from("Test");
let r1 = &s;
let r2 = &mut s;
println!("{}, {}, and {}", r1, r2);
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
Lifetimes
Borrowing references to objects and then passing them around can be dangerous. If a resource that has been borrowed is deallocated by its owner we must ensure that other users can no longer access the value. In Rust this is done using lifetimes, which describe the scope of how long a particular reference is valid for.
Lifetimes are defined using the 'a
syntax, which means 'the lifetime a'.
fn hello_world<'a>(x: &'a i32) { ... }
fn hello_world<'a>(x: &'a i32) { ... }
The functions seen before have implicit lifetimes provided through a process known as lifetime elision.
fn print(s: &str); // elided (implicit)
fn print<'a>(s: &'a str); // expanded (explicit)
fn print(s: &str); // elided (implicit)
fn print<'a>(s: &'a str); // expanded (explicit)
Structs
struct
struct
s must define explicit lifetimes when containing a reference. This ensures that any reference to internal
reference data within an object does not live longer than the owner.
struct Point {
x: &'a i32,
y: &'b i32,
}
fn main() {
let tmp;
{
let x: &i32 = &1;
let y: &i32 = &2;
let p1 = Point { x, y };
tmp = &p1.x;
} // p1 has exited its scope here
println!("{}", tmp); // `p1.x` does not live long enough
}
struct Point {
x: &'a i32,
y: &'b i32,
}
fn main() {
let tmp;
{
let x: &i32 = &1;
let y: &i32 = &2;
let p1 = Point { x, y };
tmp = &p1.x;
} // p1 has exited its scope here
println!("{}", tmp); // `p1.x` does not live long enough
}
Smart Pointers
Smart pointer types act similarly to standard pointers, but allow further fine-grained control over behaviour. When a developer uses memory that contains dynamically allocated data with smart pointers, they are automatically de-allocated.
Reference Cell
A reference cell is a smart pointer type that executes the borrowing rules at runtime rather than at compile time.
RefCell<T>
RefCell<T>
has the following rules:
- If there is no reference to
T
T
, you may get either a mutable or immutable reference. - If there is already a mutable reference to
T
T
, you must wait until this reference is dropped. - If there are one or more immutable references to
T
T
, you may get an immutable reference.
use std::cell::RefCell;
let container = RefCell::new(11);
{
let _c = container.borrow();
// You may borrow immutably multiple times
assert!(container.try_borrow().is_ok());
// Cannot borrow as mutable because it is already borrowed as immutable.
assert!(container.try_borrow_mut().is_err());
}
use std::cell::RefCell;
let container = RefCell::new(11);
{
let _c = container.borrow();
// You may borrow immutably multiple times
assert!(container.try_borrow().is_ok());
// Cannot borrow as mutable because it is already borrowed as immutable.
assert!(container.try_borrow_mut().is_err());
}
Reference Counting
Reference counts (Rc<T>
Rc<T>
) sum up the number of references made to an object, and where no references exist, the data can be
de-allocated. A reference-counted pointer to an item allows shared ownership of the data, breaking one of the initial rules outlined
earlier, and allowing data leakage within Rust, so must be used with care.
Graph data structures use Rc<T>
Rc<T>
, as multiple edges might point to the same node, and that node is conceptually
owned by all the edges that point to it.
a
use std::rc::Rc;
use std::cell::RefCell;
struct Node {
next: RefCell<Option<Rc<Node>>>,
val: i32
}
fn main() {
let foo1 = Rc::new(Node { next: RefCell::new(None), val: 5 });
let foo2 = Rc::new(Node { next: RefCell::new(Some(foo1.clone())), val: 10 });
*foo1.next.borrow_mut() = Some(foo2.clone());
// Node with value 10 <-> Node with value 5
}
use std::rc::Rc;
use std::cell::RefCell;
struct Node {
next: RefCell<Option<Rc<Node>>>,
val: i32
}
fn main() {
let foo1 = Rc::new(Node { next: RefCell::new(None), val: 5 });
let foo2 = Rc::new(Node { next: RefCell::new(Some(foo1.clone())), val: 10 });
*foo1.next.borrow_mut() = Some(foo2.clone());
// Node with value 10 <-> Node with value 5
}