Rust 有没有可能避免一个毫无意义的“FnOnce”定义?

Rust 有没有可能避免一个毫无意义的“FnOnce”定义?,rust,overloading,Rust,Overloading,在尝试重载调用语法时,我引入了一个简单的缓存,可以缓存昂贵计算的结果。我对一段语法的用法有点困惑。在提问之前,我将逐步介绍代码 缓存的用途如下: 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 extrac

在尝试重载调用语法时,我引入了一个简单的缓存,可以缓存昂贵计算的结果。我对一段语法的用法有点困惑。在提问之前,我将逐步介绍代码

缓存的用途如下:

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));
    }
}
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!()
    }
}
我们将缓存作为一种结构引入,它具有一个
HashMap
和一个计算新值的函数

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)
    }
}
尽管我觉得语法
FnMut
非常奇怪,但这是安全的,并且表达的意图非常清楚。因为我需要定义函数的返回类型,所以我想将开头写为:

impl<T, R> FnMut<(T,), Output=R> for Cache<T, R>
    where T: Eq + Hash + Copy,
          R: Copy
{}
这是毫无意义的,因为永远不会调用
call\u once
,而且从这个角度来看,这应该是可能的。但是,它会失败,并出现一个错误,即不允许存在关联的类型

其中提到了语法
Fn(T)->R
,还说
Fn
应该可以工作,但我不能让它工作,即使我使用的是nightly Rust compiler

由于希望在编译时捕获尽可能多的错误,因此最好避免在
FnOnce
中创建“未实现”函数,因为这将在运行时而不是编译时失败

是否可以仅实现
FnMut
,并以某种方式提供函数的返回类型

这是毫无意义的,因为永远不会调用
call\u once

这不是由你决定的;这取决于打电话的人。他们可能决定在
FnOnce
上下文中调用缓存

好消息是有一个非常合理的
FnOnce
实现-只需委托给
FnMut
实现:

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)
    }
}
impl FnOnce用于缓存
其中T:Eq+Hash+Copy,
R:收到了
{
类型输出=R;
外部“生锈呼叫”fn呼叫一次(多个self,arg:(T,))
->Self::输出
{
self.call_mut(arg)
}
}
这就是编译器对这些特性的自动实现所做的;如果合适,它还将
FnMut
委托给
Fn

另见

这是毫无意义的,因为永远不会调用
call\u once

这不是由你决定的;这取决于打电话的人。他们可能决定在
FnOnce
上下文中调用缓存

好消息是有一个非常合理的
FnOnce
实现-只需委托给
FnMut
实现:

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)
    }
}
impl FnOnce用于缓存
其中T:Eq+Hash+Copy,
R:收到了
{
类型输出=R;
外部“生锈呼叫”fn呼叫一次(多个self,arg:(T,))
->Self::输出
{
self.call_mut(arg)
}
}
这就是编译器对这些特性的自动实现所做的;如果合适,它还将
FnMut
委托给
Fn

另见


外部“生锈呼叫”位在这里是绝对必要的吗?@MatthieuM。是的。trait定义了一个
extern“rust call”
函数,实现必须与trait定义匹配。更广泛地说,
rust call
告诉编译器,
arg
实际上是函数的多个参数,并执行转换,使每个元组值都是一个单独的参数。可能传递给函数的单个大元组的行为与硬件级别的许多单独值不同。@Shepmaster:根据ABI的不同,传递的方式也可能不同(例如,传递单个指针而不是单独传递每个元素)。我没有考虑过,因为在C++中它是一个多变的模板调用,它没有这个问题。在MatthieuM,<代码>外部的“锈调用”< /Cord>位是否严格必要?是的。trait定义了一个
extern“rust call”
函数,实现必须与trait定义匹配。更广泛地说,
rust call
告诉编译器,
arg
实际上是函数的多个参数,并执行转换,使每个元组值都是一个单独的参数。可能传递给函数的单个大元组的行为与硬件级别的许多单独值不同。@Shepmaster:根据ABI的不同,传递的方式也可能不同(例如,传递单个指针而不是单独传递每个元素)。我没有考虑过,因为在C++中它是一个多变的模板调用,它没有这个问题。
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)
    }
}