当前位置: 首页 > 知识库问答 >
问题:

如果我指定了正确的“Fn”边界,为什么我不能传递泛型函数?

芮宇航
2023-03-14

我在回答其他人的问题,试图命名一个泛型函数的类型,并将其传递给其他人,我试图编写这段代码,这似乎符合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) -> _`

我理解这意味着编译器要求将fn泛型_fn强制为函数指针(部分指定的类型fn(f32))-

我知道这在逻辑上并不是不可能的,因为如果我编写自己的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的正常功能项不这样工作?为什么他们有这种看似可以避免的限制?


共有3个答案

施琦
2023-03-14

这里的问题是,这些类型确实不兼容。您将一个函数传递给use_两次,该函数需要有一个定义良好的类型,因此它无法处理

    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::

所以从根本上说,你的函数参数边界是不可行的,你的设计需要改变。

邰伟彦
2023-03-14

我的问题也许是:为什么具体类型的generic_fn不提供这个?

因为没有“generic_fn的具体类型”这样的东西。当你写

use_twice(generic_fn)

真的

use_twice(generic_fn::<Something>)

其中某事由编译器推断。但是如果某事i32,那么generic_fn::

也许吧

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

会工作,但不支持(还?)。

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

钮才哲
2023-03-14

我认为答案在于structGenericFn不是泛型类型。

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

通用结构Foo

泛型函数基本上是一样的,它为它所泛化的每个不同类型创建一个不同的函数指针<代码>foo

现在,一个函数需要一个同时具有traitFn(bar)的类型-

您的GenericFn有效,因为具体类型实际上实现了PolyFn1

 类似资料:
  • 问题内容: 我正在创建一个小型Java Jpanel游戏,其中应该有一个火箭,它通过箭头上下移动,并通过太空射击。 触发方法应按以下方式工作:按下空格键,东西触发并在屏幕上移动,然后当它碰到某个x时,它就会消失。此外,您只能发射一次,直到另一颗子弹消失为止。 我不知道我在做什么错。首先,在我的代码启动后,您会看到子弹在屏幕上飞舞。 2,子弹没有消失。 第三,即使其他子弹仍然可见,它也允许我再次开火

  • 问题内容: 我正在使用泛型编写某些东西,令我惊讶的是,我发现这行不通: 那我不能实例化泛型吗?没有任何方法可以做到这一点吗? 问题答案: 是的,这真是令人讨厌。 我使用的解决方法是强制客户端在构造新类时传递类-即 然后您可以使用。

  • 问题内容: 我定义了一个Java函数: 一种调用方式是这样的: 为什么不能通过显式传递泛型类型参数来调用它?: 我从编译器得到错误。 问题答案: 当Java编译器无法自行推断静态方法的参数类型时,您始终可以使用完整的合格方法名称Class来传递它。<类型> method();

  • 我的老师给了我这个 在一个n边正多边形中,所有边都有相同的长度,所有角都有相同的度数。设计一个名为正多边形的类,它包含:一个名为n的私有int数据栏,它定义了多边形中默认值为3的边的数量。一个名为side的私有双数据栏,它存储了默认值为1的边的长度。一个名为X的私有双数据栏,它定义了默认值为0的多边形中心的x坐标。一个名为Y的私有双数据栏,它定义了默认值为0的多边形中心的y坐标。创建具有指定边数、

  • 问题内容: 我决定尝试一些实验,以了解关于堆栈帧大小以及当前执行的代码在堆栈中的距离的发现。我们可能在这里调查两个有趣的问题: 当前代码有多少层深入堆栈? 当前方法在达到a之前可以达到多少级别的递归? 当前执行代码的堆栈深度 这是我为此能想到的最好的方法: 这似乎有点骇人听闻。它生成并捕获异常,然后查看堆栈跟踪的长度。 不幸的是,它似乎也有一个致命的限制,那就是返回的堆栈跟踪的最大长度为1024。

  • 问题内容: 如果将它们放在document.ready()函数中,则函数的定义为未定义: 为什么会这样?我确定我只需要一些简单的理解即可:) 问题答案: 不确定为什么在范围内定义函数对您很重要,但是您可以通过预先声明使其起作用: 显然,由于代码尚未运行,您之后不能立即从内联脚本中调用,但是您可以稍后再调用该函数。 只要确保在代码运行之前没有任何东西可以尝试调用(或进行无害函数的初始声明)。