为什么Rust中可以在结构体的类型参数中写入函数类型?
Rust 函数、闭包与函数指针类型的行为答疑
核心前置概念
- 所有顶层函数、闭包都自带唯一、不可手动书写的原生类型:对于
fn foo(i32)->i32和fn bar(i32)->i32来说,二者原生的函数项类型完全不同;每个闭包的原生类型也都是全局唯一的。 - 签名匹配的无捕获闭包、函数项,都可以被强制转换为同签名的函数指针类型
fn(i32) -> i32,这是一个大小固定、可复制的具体类型,所有转换后的实例都属于同一类型。
逐个解答疑问
1. 为什么get_struct_2的返回值可以赋值给get_struct_1初始化的变量x?
你在两个函数的返回类型中都显式指定了泛型参数为fn(i32) -> i32,因此不管内部用的是foo还是bar,返回时都会被自动强制转换为函数指针类型,两个返回值的FooStruct泛型参数完全一致,自然可以互相赋值。
如果不做这个显式指定,直接让编译器推断返回类型,两个函数的返回类型会是分别对应foo、bar原生类型的不同FooStruct实例,就无法互相赋值了。
2. foo和bar属于同一类型吗?
原生类型下不属于,你可以尝试编译这段代码验证:
let mut f = foo; f = bar; // 编译报错,类型不匹配
只有当二者都被强制转换为函数指针类型fn(i32)->i32后,才属于同一类型。
3. 为什么具备唯一不可写类型的闭包也可以赋值给x?
你代码中用到的闭包是无捕获闭包(没有从上下文中借用/持有任何变量),这类闭包天生支持强制转换为签名匹配的函数指针类型。而x的类型已经被固定为FooStruct<fn(i32) -> i32>,赋值时编译器会自动完成闭包到函数指针的类型转换,因此可以赋值成功。
4. 为什么直接用闭包初始化的变量y不能接收其他闭包或函数实例,加类型标注就可以?
没有显式类型标注时,编译器会把y的泛型参数推断为初始化用的那个闭包的原生唯一类型,既然是唯一类型,自然不能接收其他闭包、函数的赋值。
当你显式标注y的类型为FooStruct<fn(i32) -> i32>时,编译器会在初始化阶段就把闭包强制转换为函数指针类型,后续只要是签名匹配的无捕获闭包、函数都可以转换为该指针类型赋值给y,自然就正常工作了。
关于单态化与分派的认知澄清
你之前的认知没有错:默认的FooStruct<F>泛型版本会做单态化,编译时为每个不同的F生成专属代码,调用时直接跳转对应函数地址,没有额外开销。但你示例中的x的泛型参数已经被固定为fn(i32)->i32函数指针类型,这种情况下:
- 整个程序只会生成一份
FooStruct<fn(i32)->i32>的代码,不会为每个函数、闭包单独生成 - 调用
f时是通过函数指针做间接跳转,本质是有开销的间接调用,不是单态化的零成本直接调用 - 如果赋值出现在if分支中,编译器确实不需要知道运行时具体存的是哪个函数,运行时通过指针中存储的地址跳转即可,这和
Box<dyn Fn>的动态分派逻辑类似,只是函数指针只有一个代码地址,比带vtable的dyn trait开销更低。
内容的提问来源于stack exchange,提问作者Harry Braviner
相关产品推荐
相关产品推荐

