Reputation: 4329
I'm currently in the process of learning Rust. In chapter 13 of the book in the part, there is an example of a Cacher
struct. The idea behind a cacher is that the value is only evaluated once it's requested and then stored. In the example the cacher has an input of i32
and also an output of i32
. Since I wanted to make it a bit more useful, I wanted the cacher to not take any input and generate a value of any type (basically the Lazy<T>
type from .NET if you're familiar).
My first idea was just modifying the given Cacher
with generic annotations like so:
struct Cacher<TCalc, TVal>
where TCalc: Fn() -> TVal
{
calculation: TCalc,
value: Option<TVal>,
}
impl<TCalc, TVal> Cacher<TCalc, TVal>
where TCalc: Fn() -> TVal
{
fn new(calculation: TCalc) -> Cacher<TCalc, TVal> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self) -> TVal {
match self.value { // cannot move out of `self.value.0` which is behind a mutable reference
Some(v) => v,
None => {
let v = (self.calculation)();
self.value = Some(v);
v // use of moved value: `v`
},
}
}
}
As you can see annotated by the comments, this threw some errors my way in the value
method.
I then tried many things and came up with a working solution for the value
method. Note that it now returns &TVal
instead of TVal
but that doesn't really bother me.
fn value(&mut self) -> &TVal {
if let None = self.value {
let v = (self.calculation)();
self.value = Some(v);
}
self.value.as_ref().unwrap()
}
I can create and use this cacher like so:
let mut expensive_val = Cacher::new(|| {
println!("Calculating value..");
"my result"
});
println!("Cacher was created.");
println!("The value is '{}'.", expensive_val.value());
println!("The value is still '{}'.", expensive_val.value());
// Cacher was created.
// Calculating value..
// The value is 'my result'.
// The value is still 'my result'.
This works just fine but I felt like having two type arguments is redundant for this so I tried to remove the first one (TCalc
). After some research I came up with this:
struct Cacher<'a, T>
{
calculation: &'a dyn Fn() -> T,
value: Option<T>,
}
impl<'a, T> Cacher<'a, T>
{
fn new(calculation: &'a dyn Fn() -> T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self) -> &T {
if let None = self.value {
let v = (self.calculation)();
self.value = Some(v);
}
self.value.as_ref().unwrap()
}
}
This cacher still works but now I have to pass in a reference to the closure instead of the closure itself.
let mut expensive_val = Cacher::new(&|| { // Note the &
println!("Calculating value..");
"my result"
});
I don't really see any disadvantage in that but is there a way to do it without a reference? I mean with a single type parameter and while still passing in a closure instead of a reference. Simply trying to store the Fn() -> T
directly will result in the size for values of type `(dyn std::ops::Fn() -> T + 'static)` cannot be known at compilation time
.
Ps. Maybe I've said some things that are just wrong or not how you do it in rust so if you can correct me on those, please do :)
Upvotes: 0
Views: 493
Reputation: 19662
You're digging yourself in a hole. Let's take a step back to understand why this is happening.
Your goal is to cache a potentially expensive computation in order not to have to repeat it on subsequent coals. This means, however, that the return of your call will either return a reference to the final result, or a full value.
This choice is much, much more important than it looks like for your implementation.
Your Cacher
struct then becomes:
struct Cacher<TCalc, TVal: Clone>
where TCalc: Fn() -> TVal
{
calculation: TCalc,
value: Option<TVal>,
}
And your accessor then becomes:
fn value(&mut self) -> TVal {
match &self.value {
Some(r) => r.clone(),
None => {
self.value = Some((self.calculation)());
self.value.clone().unwrap()
}
}
}
This is clean and concise and relies on you passing the value as a clone of itself; a worthwhile thing to do when doing this is to check that this clone actually is a small-cost operation.
Your approach had two problems, which stem from your relative lack of experience in Rust (which is fine! We've all started somewhere):
As a result, we are left with the following:
struct Cacher<TCalc, TVal>
where TCalc: Fn() -> TVal
{
calculation: TCalc,
value: Option<TVal>,
}
impl<TCalc, TVal> Cacher<TCalc, TVal>
where TCalc: Fn() -> TVal {
pub fn new(calculation: TCalc) -> Cacher<TCalc, TVal> {
Cacher {
calculation,
value: None
}
}
pub fn get_mut(&mut self) -> &mut TVal {
if self.value.is_none() {
self.value = Some((self.calculation)());
}
self.value.as_mut().unwrap()
}
}
This provides a mutable reference to its value, and creates it beforehand if it does not exist, thus fulfilling the requirements.
There are still problems with this, most notoriously the fact that, if you wanted an immutable reference to the internal value, you'd still need a mutable borrow to the container, and that's something you can solve with an interior mutation structure, but that's a story for another day.
Upvotes: 1