Mats Kindahl
Mats Kindahl

Reputation: 2060

Is it possible to avoid a pointless definition of `FnOnce`?

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?

Upvotes: 1

Views: 343

Answers (1)

Shepmaster
Shepmaster

Reputation: 430663

Which is kind of pointless since call_once will never be called

That's not up to you to decide; that's up to the caller. They may decide to call the cache in a FnOnce context.

The good news is that there's a perfectly reasonable implementation of FnOnce — just delegate to the FnMut implementation:

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(mut self, arg: (T,))
        -> Self::Output
    {
        self.call_mut(arg)
    }
}

This is what the compiler's automatic implementation of these traits does; it also delegates FnMut to Fn if appropriate.

See also

Upvotes: 5

Related Questions