In trying to experiment with overloading the call syntax, I introduced a simple cache that can cache the result of an expensive computation. I am a bit confused about the use of a piece of the syntax. I'll introduce the code step by step before the question.
The cache is intended to be used like this:
fn fib(x: i32) -> i32 {
if x < 2 { x } else { fib(x-1) + fib(x-2) }
}
fn main() {
let mut cfib = Cache::new(fib);
// Loop that repeats computation and extracts it from the cache
// the second time.
for x in 1..200 {
let val = 5 * x % 40;
println!("fibc({}) = {}", val, cfib(val));
}
}
We first have the preamble to enable features that are not yet in stable:
#![feature(fn_traits, unboxed_closures)]
use std::collections::HashMap;
use std::hash::Hash;
We introduce the cache as a structure with a HashMap
and a function to compute new values.
struct Cache<T, R> {
cache: HashMap<T, R>,
func: fn(T) -> R,
}
impl<T, R> Cache<T, R>
where T: Eq + Hash + Copy,
R: Copy
{
fn new(func: fn(T) -> R) -> Cache<T, R> {
Cache { cache: HashMap::new(), func: func }
}
fn compute(&mut self, x: T) -> R {
let func = self.func;
let do_insert = || (func)(x);
*self.cache.entry(x).or_insert_with(do_insert)
}
}
I create an implementation of the FnMut
trait since the cache needs to be mutable.
impl<T, R> FnMut<(T,)> for Cache<T, R>
where T: Eq + Hash + Copy,
R: Copy
{
extern "rust-call" fn call_mut(&mut self, args: (T,))
-> Self::Output
{
let (arg,) = args;
self.compute(arg)
}
}
Even though I find the syntax FnMut<(T,)>
quite strange, this is fine and safe and conveys the intentions quite clear. Since I need to define the return type of the function, I would like to write the beginning as:
impl<T, R> FnMut<(T,), Output=R> for Cache<T, R>
where T: Eq + Hash + Copy,
R: Copy
{}
But that fails with an error:
error[E0229]: associated type bindings are not allowed here
--> src/main.rs:55:24
|
55 | impl<T, R> FnMut<(T,), Output=R> for Cache<T, R>
| ^^^^^^^^ associate type not allowed here
I had to implement FnOnce
like this:
impl<T, R> FnOnce<(T,)> for Cache<T,R>
where T: Eq + Hash + Copy,
R: Copy
{
type Output = R;
extern "rust-call" fn call_once(self, _arg: (T,))
-> Self::Output
{
unimplemented!()
}
}
Which is kind of pointless since call_once
will never be called, and from Associated Types it seems like this should be possible. However, it fails with an error that associated types are not allowed there.
The Rust Compiler Error Index mentions the syntax Fn(T) -> R
and also says that Fn<(T,), Output=U>
should work, but I cannot make it work even though I am using nightly Rust compiler.
Since it is desirable to catch as many errors as possible at compile time it would be good to avoid creating the "unimplemented" function in FnOnce
since that will fail at runtime rather than compile time.
Is it possible to implement only FnMut
and provide the return type of the function in some manner?