rust - 如果我指定了正确的 `Fn` 边界,为什么我不能传递一个通用函数?
问题描述
我正在回答其他人关于尝试命名泛型函数的类型并传递泛型函数的问题,我尝试编写这段代码,这似乎符合 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
项目generic_fn
强制转换为函数指针(部分指定的类型fn(f32) -> _
)。但为什么?我听说fn
items 有唯一的零大小类型——为什么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!("→{}←", 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 的正常功能项不能像这样工作?为什么他们有这个看似可以避免的限制?
解决方案
我认为答案在于结构GenericFn
不是泛型类型。
泛型类型提供了一个“配方”,编译器使用它来构造一个可以使用的实际具体类型。
Foo<A>
用作 as
的泛型结构Foo<Bar>
将被编译器视为具体的、新的和不同的类型。将其视为编译器创建FooBar
(编译器并没有真正创建这样的名称,但更好看)。如果您使用Foo<Baz>
, 则将其视为与.FooBaz
分开的类型FooBar
。这被称为单态化,它允许泛型类型是零成本的,因为它编译成与创建新的不同类型相同的东西。
泛型函数几乎是一样的东西,它为它所泛化的每种不同类型创建一个不同的函数指针。foo<bar>
(其中foo
isfn foo<A>(x: A) -> A
将给出一个指针,比如说,指向一个不同的1
函数类型类型。fn(bar) -> bar
identity<bar>
foo<baz>
2
fn(baz) -> baz
现在,一个函数想要一个同时具有 traitFn(bar) -> bar
和的类型的函数,Fn(baz) -> baz
函数指针不会删除它,因为它只实现一个具体的实现,因为它只指向一个函数:指针1
是fn(bar) -> bar
,而该类型没有实现Fn(baz) -> baz
,并且反之亦然2
。您将不得不传递两个不同的函数指针,而您不会这样做。
您的GenericFn
工作是因为具体类型实际上同时实现了PolyFn1<i32>
AND PolyFn2<f32>
,只允许传递一种类型。
推荐阅读
- docker - pgAdmin Docker 错误:“gunicorn_config.py”不存在
- background-color - 更改 DITA 中的 custom-attrs.xsl 文件并没有更改我的表格列标题的背景颜色。那我怎么改呢?
- asp.net - Automapper 映射不一致
- javascript - 如何通过特定键在数组中按字母顺序对对象进行分组?
- javascript - React - useEffect 覆盖具有多个数据的道具
- angular - Angular 8 http补丁成功完成但第一次没有提供正确的响应
- c# - 从另一个程序集加载转换器:“在 'System.Windows.StaticResourceExtension' 上提供值引发异常。”
- sql - SQL 将每个月的平均值并排放在列中
- python - 读取大而复杂的 csv 并插入 SQL Server
- python - 在 6 周前运行良好的代码中遇到新的 numpy 溢出