It really isn't Sure, at first glance it seems that requiring an extra unused field just to be able to carry a type parameter looks cumbersome and unneeded, but it turns out that is paramount to have such a construction as soon as the language offers subtyping and variance. These are quite advanced topics, on which you may find (my) one-post-explanation here: Looking for a deeper understanding of PhantomData - #4 by Yandros
The other option would have been not to have any variance whatsoever, and that would have been incredibly cumbersome. So it is a lesser inconvenience.
That being said, in your case you could get away without PhantomData
, if you allowed "invalid" unusable Cacher
s to be constructed, by moving the bounds onto the get()
function:
It may look simpler for the library writer, but imho having the Fn
bound since the beginning like you did is way healthier for a user of the library
Finally, here are a few tips:
-
You can loosen
V : Copy
toV : Clone
, and use an explicit.clone()
call -
You can loosen
F : Fn...
toF : FnOnce
since you will only be calling the function once. This gets tricky since you need owned access to the closure field to call anFnOnce
, but your.get()
method takes aself
receiver by unique reference (&'_ mut Self
). The trick to go from&mut T
toT
is tomem::replace
the value with some arbitrary default sentinel, and in Rust the type that lets you do that in a very idiomatic way isOption
, and its.take()
method. -
Option
offers a neat method / idiom for the "match
anOption
and onNone
compute" pattern:.get_or_insert_with
pub struct Cacher<F, U, V> where F : FnOnce(U) -> V, V : Clone, { calculation: Option<F>, value: Option<V>, _phantom: ::std::marker::PhantomData<U>, } impl<F, U, V> Cacher<F, U, V> where F : FnOnce(U) -> V, V : Clone, { pub fn new (calculation: F) -> Cacher<F, U, V> { Cacher { calculation: Some(calculation), value: None, _phantom: Default::default(), } } pub fn get (self: &'_ mut Self, arg: U) -> V { let calculation = &mut self.calculation; self.value .get_or_insert_with(|| { calculation.take().unwrap()(arg) }) .clone() } }
The last one is just to show an example of the utilities present in Rust most pervasive data structures: know that the version with the match
is just as good