Imagine that a dependency, which you aren't able to change, defines the following trait:
trait WidgetCalculator {
fn get_total_widgets(&self) -> i32;
}
In my crate, I've implemented this trait:
struct SlowWidgetCalculator {
db: WidgetDatabase,
}
impl WidgetCalculator for SlowWidgetCalculator {
fn get_total_widgets(&self) -> i32 {
self.db.extremely_slow_database_query()
}
}
As the name would suggest, the database query is extremely slow, so I'd like to cache the result:
struct SlowWidgetCalculator {
db: WidgetDatabase,
cache: Option<i32>,
}
impl WidgetCalculator for SlowWidgetCalculator {
fn get_total_widgets(&self) -> i32 {
match self.cache {
Some(value) => value,
None => {
let value = self.db.extremely_slow_database_query();
self.cache = Some(value);
value
}
}
}
}
But wait, now the compiler is angry at me:
error[E0594]: cannot assign to `self.cache` which is behind a `&` reference
--> src/lib.rs:22:17
|
17 | fn get_total_widgets(&self) -> i32 {
| ----- help: consider changing this to be a mutable reference: `&mut self`
...
22 | self.cache = Some(value);
| ^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be written
I can't change the definition of get_total_widgets
, because it's not in my crate, and I can't avoid mutating self
if I want to store the data
Here comes RefCell
to save the day:
struct SlowWidgetCalculator {
db: WidgetDatabase,
cache: RefCell<Option<i32>>,
}
impl WidgetCalculator for SlowWidgetCalculator {
fn get_total_widgets(&self) -> i32 {
let mut cache = self.cache.borrow_mut();
match *cache {
Some(value) => value,
None => {
let value = self.db.extremely_slow_database_query();
*cache = Some(value);
value
}
}
}
}
This is all a very long winded way of saying: RefCell
is useful whenever you need to modify some data, but all you have is a &
reference. A lot of the time, you can get around the need to do this by restructuring your program, but in some cases (like when ownership of the data is shared via Rc
, or when you don't have control over the type of reference you get), you need the flexibility of a runtime check.
Runnable version of the final example: Rust Playground