首页 > 解决方案 > 如果我指定了正确的 `Fn` 边界,为什么我不能传递一个通用函数?

问题描述

我正在回答其他人关于尝试命名泛型函数的类型并传递泛型函数的问题,我尝试编写这段代码,这似乎符合 Rust 的类型和特征的精神:

use std::fmt::Display;
fn generic_fn<A: Display>(x: A) -> String { format!("→{}←&quot;, x) }

fn use_twice<F>(f: F) -> String
where
    F: Fn(i32) -> String,
    F: Fn(f32) -> String,
{
    f(1) + &f(2.0)
}

fn main() {
    dbg!(use_twice(generic_fn));
}

但是,它无法编译(在稳定的 Rust 1.52.0 上):

error[E0631]: type mismatch in function arguments
  --> src/main.rs:13:15
   |
1  | fn generic_fn<A>(x: A) -> A { x }
   | --------------------------- found signature of `fn(i32) -> _`
2  | 
3  | fn use_twice<F>(f: F)
   |    --------- required by a bound in this
...
6  |     F: Fn(f32) -> f32,
   |        -------------- required by this bound in `use_twice`
...
13 |     use_twice(generic_fn);
   |               ^^^^^^^^^^ expected signature of `fn(f32) -> _`

我知道这意味着编译器要求将fn项目generic_fn强制转换为函数指针(部分指定的类型fn(f32) -> _)。但为什么?我听说fnitems 有唯一的零大小类型——为什么use_twice's 参数不能f接受那种类型?如果 Rust 接受此代码,它会在编译不同的程序时造成一些麻烦(例如类型推断失败)吗?这只是尚未实施的东西吗?

我知道这不是逻辑上的不可能,因为如果我编写自己的 trait 而不是 using Fn,那么我可以显式定义一个可以作为值传递的通用函数:

trait PolyFn1<A> {
    type Output;
    fn apply(&self, x: A) -> Self::Output;
}

use std::fmt::Display;
struct GenericFn;
impl<A: Display> PolyFn1<A> for GenericFn {
    type Output = String;
    fn apply(&self, x: A) -> String { format!("→{}←&quot;, x) }
}

fn use_twice<F>(f: F) -> String
where
    F: PolyFn1<i32, Output=String>,
    F: PolyFn1<f32, Output=String>,
{
    f.apply(1) + &f.apply(2.0)
}

fn main() {
    dbg!(use_twice(GenericFn));
}
[src/main.rs:22] use_twice(GenericFn) = "→1←→2←&quot;

使用不稳定的 Rust 特性,我什至可以这样实现Fn

#![feature(fn_traits)]
#![feature(unboxed_closures)]
use std::fmt::Display;

struct GenericFn;
impl<A: Display> FnOnce<(A,)> for GenericFn {
    type Output = String;
    extern "rust-call" fn call_once(self, args: (A,)) -> String {
        self.call(args)
    }
}
impl<A: Display> FnMut<(A,)> for GenericFn {
    extern "rust-call" fn call_mut(&mut self, args: (A,)) -> String {
        self.call(args)
    }
}
impl<A: Display> Fn<(A,)> for GenericFn {
    extern "rust-call" fn call(&self, args: (A,)) -> String {
        format!("→{}←&quot;, args.0)
    }
}

fn use_twice<F>(f: F) -> String
where
    F: Fn(i32) -> String,
    F: Fn(f32) -> String,
{
    f(1) + &f(2.0)
}

fn main() {
    dbg!(use_twice(GenericFn));
}

鉴于此,我可以重申我的问题:为什么 Rust 的正常功能项不能像这样工作?为什么他们有这个看似可以避免的限制?

标签: rust

解决方案


我认为答案在于结构GenericFn不是泛型类型。

泛型类型提供了一个“配方”,编译器使用它来构造一个可以使用的实际具体类型。

Foo<A>用作 as 的泛型结构Foo<Bar>将被编译器视为具体的、新的和不同的类型。将其视为编译器创建FooBar(编译器并没有真正创建这样的名称,但更好看)。如果您使用Foo<Baz>, 则将其视为与.FooBaz分开的类型FooBar。这被称为单态化,它允许泛型类型是零成本的,因为它编译成与创建新的不同类型相同的东西。

泛型函数几乎是一样的东西,它为它所泛化的每种不同类型创建一个不同的函数指针foo<bar>(其中fooisfn foo<A>(x: A) -> A将给出一个指针,比如说指向一个不同的1函数类型类型。fn(bar) -> baridentity<bar> foo<baz>2fn(baz) -> baz

现在,一个函数想要一个同时具有 traitFn(bar) -> bar和的类型的函数,Fn(baz) -> baz函数指针不会删除它,因为它只实现一个具体的实现,因为它只指向一个函数:指针1fn(bar) -> bar,而该类型没有实现Fn(baz) -> baz,并且反之亦然2。您将不得不传递两个不同的函数指针,而您不会这样做。

您的GenericFn工作是因为具体类型实际上同时实现了PolyFn1<i32>AND PolyFn2<f32>,只允许传递一种类型。


推荐阅读