【问题标题】:Is it possible to avoid a pointless definition of `FnOnce`?是否可以避免对“FnOnce”进行毫无意义的定义?
【发布时间】:2017-05-02 20:10:09
【问题描述】:

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

缓存打算这样使用:

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));
    }
}

我们首先要启用尚未稳定的功能:

#![feature(fn_traits, unboxed_closures)]

use std::collections::HashMap;
use std::hash::Hash;

我们将缓存引入为具有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,)> 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)
    }
}

尽管我觉得FnMut&lt;(T,)&gt; 的语法很奇怪,但这很好,很安全,并且表达的意图很清楚。由于我需要定义函数的返回类型,所以我想把开头写成:

impl<T, R> FnMut<(T,), Output=R> for Cache<T, R>
    where T: Eq + Hash + Copy,
          R: Copy
{}

但是失败并出现错误:

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

我必须像这样实现FnOnce

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!()
    }
}

这是没有意义的,因为call_once 永远不会被调用,而从Associated Types 看来这应该是可能的。但是,它会失败,并出现一个错误,即此处不允许关联类型。

Rust Compiler Error Index 提到语法 Fn(T) -&gt; R 并且还说 Fn&lt;(T,), Output=U&gt; 应该可以工作,但即使我使用的是夜间 Rust 编译器,我也无法使其工作。

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

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

【问题讨论】:

    标签: rust overloading


    【解决方案1】:

    这是没有意义的,因为call_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)
        }
    }
    

    这就是编译器对这些特征的自动实现所做的;如果合适,它还将FnMut 委托给Fn

    另见

    【讨论】:

    • extern "rust-call" 位在这里是否是绝对必要的?
    • @MatthieuM。是的。 trait 定义了一个 extern "rust-call" 函数,并且实现必须匹配 trait 定义。更广泛地说,rust-call 告诉编译器arg 实际上是函数的多个参数,并进行转换,使得每个元组值都是一个单独的参数。据推测,传递给函数的单个大元组的行为与硬件级别的许多单独值不同。
    • @Shepmaster:它可能会根据 ABI 以不同的方式传递(例如,传递单个指针而不是单独传递每个元素)。我没有考虑过,因为在 C++ 中它是一个可变参数模板调用,没有这个问题。
    猜你喜欢
    • 2013-05-30
    • 1970-01-01
    • 1970-01-01
    • 2020-03-27
    • 1970-01-01
    • 2023-02-07
    • 1970-01-01
    • 2020-09-16
    • 2011-11-09
    相关资源
    最近更新 更多