When Unique mutable borrow rule is too restrictive
In order to ensure memory safety and to have consistent readability for single threaded and multi threaded code, rust has the following rules
A single mutable reference &mut T (or)
Multiple immutable references &T
These rules are too restrictive to write performant single threaded and multi threaded code. In this chapter we will see scenarios in single threaded code where these rules makes it impossible to write efficient code. Since Rust promises performance it provides us with types that can help the programmer to handle these scenarios without comprimising on speed!
Scenario 1: Caching of computations
Consider,
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance_from_origin(&self) -> f64 {
println!("doing the complex mathematical powers and square root");
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn demo() {
let p = Point {x: 23.3, y: -45.1342};
println!("{}", p.distance_from_origin());
println!("{}", p.distance_from_origin());
}
If you run the example, you can see that everytime the expensive mathematical computations are carried out.
use std::cell::Cell;
struct Point {
x: f64,
y: f64,
cache: Cell<Option<f64>>, // because initially the cache will be empty/none.
}
// By this stage you should know how to initialize a value for the above struct
impl Point {
fn distance_from_origin(&self) -> f64 {
match cache.get() {
Some(d) => {
println!("returning cached value");
d
},
None => {
println!("doing the complex mathematical powers and square root");
let d = (self.x.powi(2) + self.y.powi(2)).sqrt();
self.set(Some(d));
d
}
}
}
}
fn demo() {
let p = Point {x: 23.3, y: -45.1342};
println!("{}", p.distance_from_origin());
println!("{}", p.distance_from_origin());
}
Notice that in both the implementations we take an immutable reference but in the latter mutation happens interiorly.
NOTE: In both the scenarios that we saw, Cell comes in handy when we need to have a certain Copyable field mutable. So there is no overhead in using Cell and it is one of zero cost optimization.
Borrow checking at runtime
Scenario 3: Graph with mutable nodes
A node in a graph can have muliple references to it, so we need to use Rc in conjunction with RefCell.
The example rust code to represent a GraphNode, with mutable value.
To do the computation once but still have an immutable variable/reference we make use of the type.
When you want to have interior mutability of types which are not copyable use . This comes with runtime over head because it checks in runtime whether there is a unique mutable borrow and panics in cases where borrow checking rules are violated.
One should observe that without Cell and RefCell we would not be able to write safe Rust code. Yes you guessed it! Both these types uses unsafe type under the hood.
Consider the :
But we don't want to borrow the entire structure as mutable, just the individual element and modify it. We can do this with RefCell. See below: