rust 为什么我不能传递一个泛型函数,如果我指定了正确的Fn界限?

rqqzpn5f  于 9个月前  发布在  其他
关注(0)|答案(4)|浏览(104)

我在回答别人关于尝试命名泛型函数的类型并传递泛型函数的问题,我试着写了这段代码,看起来它符合Rust的类型和特征:

use std::fmt::Display;
fn generic_fn<A: Display>(x: A) -> String { format!("→{}←", 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) -> _`


我理解这意味着编译器要求将fngeneric_fn强制转换为 * 函数指针 *(部分指定的类型fn(f32) -> _).**但是为什么呢?**我听说fn项有唯一的零大小类型-为什么use_twice的参数f不能接受这种类型?如果Rust接受了这段代码,在编译不同的程序时会不会造成一些麻烦(例如类型推断失败)?这是不是还没有实现?
我知道这在逻辑上不是不可能的,因为如果我写自己的trait而不是使用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!("→{}←", 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←"

的字符串
使用不稳定的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!("→{}←", 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的正常函数项不像这样工作?**为什么它们有这个看似可以避免的限制?

xyhw6mcr

xyhw6mcr1#

我认为答案在于结构GenericFn不是泛型类型。
泛型类型提供了一个“配方”,编译器使用它来构造一个实际的具体类型,然后可以使用。
作为Foo<Bar>使用的泛型结构Foo<A>将被编译器视为具体的、新的和 distinct 类型。(编译器实际上不会创建这样的名称,但它看起来更好)。如果你使用Foo<Baz>,那么它将被视为FooBaz,一种独立于FooBar的类型。这被称为单态化,并且是允许泛型类型为零成本的原因,因为它编译为相同的东西,即创建一个新的不同类型。
一个泛型函数几乎是一样的,它为它在. foo<bar>上泛化的每个不同类型创建一个 distinct字体指针 (其中foofn foo<A>(x: A) -> A将给予一个指针,比如1,指向一个fn(bar) -> bar类型的distinct函数。 注意,这个类型是distinct但不是唯一的:identity<bar>将给予不同的指针,但类型相同。foo<baz>将给予不同的指针,比如2,类型为fn(baz) -> baz
现在,一个函数想要一个同时具有trait Fn(bar) -> barFn(baz) -> baz的类型,函数指针不会切割它,因为它只实现一个具体的实现,因为它只指向一个函数:指针1fn(bar) -> bar,并且该类型不实现Fn(baz) -> baz,反之亦然2。您必须传递两个不同的函数指针,而您没有这样做。
你的GenericFn可以工作,因为具体类型实际上实现了PolyFn1<i32>PolyFn2<f32>,只允许传递一种类型。

eqzww0vc

eqzww0vc2#

我的问题可能是:为什么generic_fn的具体类型不提供这一点?
因为没有“generic_fn的具体类型“这样的东西。

use_twice(generic_fn)

字符串
真的很

use_twice(generic_fn::<Something>)


其中Something是由编译器推断的。但是如果Somethingi32,那么generic_fn::<i32>不会实现Fn(f32) -> String,类似地,generic_fn::<f32>不会实现Fn(i32) -> String。所以没有Something可以工作。
也许

where
    F: for<A> Fn(A) -> String,


会工作,但这是不支持(还?).
在工作情况下,你只传递GenericFn,那里没有类型参数可以推断。它需要在f.apply(1)f.apply(2.0)中推断,但这是两个不同的地方,在那里有不同的类型参数没有问题。

wi3ka0sx

wi3ka0sx3#

这里的问题是类型确实是不兼容的。您正在向use_twice传递单个函数,该函数需要具有定义良好的类型,因此它无法处理

F: Fn(i32) -> String,
    F: Fn(f32) -> String,

字符串
因为一个函数不能接收两种不同类型的参数。我假设你希望它在仍然有一个函数的情况下自动应用模板两次,但这不是Rust的工作方式。
所以重温这个错误

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) -> _`


它说它使用了generic_fn::<i32>并试图将其传递给use_twice,但这是不允许的,因为generic_fn::<i32>不传递Fn(f32) -> f32
所以从根本上说,你的函数参数边界是不可行的,你的设计需要改变。

nwnhqdif

nwnhqdif4#

你想要的是一种叫做高级类型的东西(或者它们的函数等价物)。
为了理解这意味着什么,让我们先看一下另一个例子(它不能编译):

trait Foo {
    type Collection;
    fn make_collection<T>(&self) -> Collection<T>;
}

字符串
从理论上讲,我希望在集合类型上是通用的,同时支持VecHashSet,以及其他任何东西,但是我们不能这样做。(在这种情况下,它被称为泛型关联类型):Vec。不,不是Vec<T>,而是Vec。我们可以说Vec是一个“类型的构造函数”,它接受某个类型T作为参数,并吐出另一个类型Vec
请注意,每当这个功能落地时,它实际上看起来像这样(因此,在这种情况下,它被称为泛型关联类型(GAT)):

trait Foo {
    type Collection<T>: FromIterator<T>;
    fn make_collection<T>(&self) -> Collection<T>;
}


从类型地移到功能地,我们可以看到它的情况和你一样:
你想要一个类型比另一个类型更通用.
我不会解释这个答案中的函数项是什么,但请注意,推理同样适用于函数指针:函数的类型(在本例中为F)对输入和输出进行编码。
这就是turbofish存在的原因 *,并且在处理函数时感觉有点奇怪:

foo::<u8>(0u8)


可以重写为

(foo::<u8>)(0u8)


因此,如果我们将fn(u8) { foo }(在fn项的伪代码中)作为F,那么问题就很明显了:唯一要实现的逻辑Fn trait是Fn(u8)。我们不能得到HKT。
然而,这个特性还没有实现,所以你最好只传入两次:

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

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

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

    • :我没有证据证明这就是为什么turbofish for functions存在,但它是有意义的。

相关问题