I did manage to get this compile in the end - does anyone else find that the process of asking the question well on a public forum organizes their thoughts well enough to solve the problem?
Anyway, the solution was to make Cacher::new supply some actual PhantomData
which was a little weird but fair enough I guess. Then the new function didn't need additional parameters because they are "hardcoded" into the new method definition.
If you didn't have a new method you would still need to pass the data when building Cacher
though... Something like:
let cacher = Cacher {
calculation: some_expensive_function,
value: None,
_phantom: PhantomData {},
};
which is ugly, but hey, the new method was a good idea anyway.
Cacher
looks like this in my code now:
use std::marker::PhantomData;
pub struct Cacher<F,U,V>
where F: Fn(U) -> V, V: Copy
{
calculation: F,
value: Option<V>,
_phantom: PhantomData<U>,
}
impl<F,U,V> Cacher<F,U,V>
where F: Fn(U) -> V, V: Copy
{
pub fn new(calculation: F) -> Cacher<F,U,V> {
Cacher {
calculation,
value: None,
_phantom: PhantomData {},
}
}
pub fn get(&mut self, arg: U) -> V {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}
This seems like a hack to me, but from the RFC linked to in the SO answer maybe this will be prettier in the future? Maaaybe?
pub struct Cacher<F,U,V>
where F: Fn(U) -> V, V: Copy
{
calculation: F,
value: Option<V>,
phantom U
}