当类型参数受类型限制时,这意味着什么Fn?
fn call<F: Fn() -> u64>(f: F) -> u64 {
f()
}
Run Code Online (Sandbox Code Playgroud)
Rust Book 说带有类型参数的函数是单态的:https://doc.rust-lang.org/stable/book/ch10-01-syntax.html ?highlight=generic%20function#performance-of-code-using-仿制药。
如果是这种情况,那么我希望call为实例化的每个新闭包类型生成一个新版本。
根据 Rust 参考,即使函数体相同,每个闭包类型也是不同的: https: //doc.rust-lang.org/reference/types/closure.html。
call因此,当我查看该程序的编译输出时,我预计当我使用1799999999999999999不同的实例进行调用时,与仅四个实例相比,编译工件会更大F,但事实并非如此。
(1)
// `call` is defined above
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
fn main() {
let results = (0..17999999999999999999).map(make_closure).map(call);
for r in results {
println!("{}", r)
}
}
Run Code Online (Sandbox Code Playgroud)
那么什么才是正确的心智模型fn call<F: Fn() -> u64> 呢?我是否应该将代码膨胀的缺乏视为仅仅是一种优化?
然而,当闭包是根据用户输入构建时,我很难维持这种心理模型:
(2)
fn main() {
let mut buf = String::new();
std::io::stdin().read_line(&mut buf).unwrap();
let n = buf.trim().parse::<u64>().unwrap();
println!("{}", call(make_closure(n)));
}
Run Code Online (Sandbox Code Playgroud)
那么思考手段签名的正确方法是什么call?
添加更多信息,以便我们可以从评论中引用它:
(3)
Rust参考资料说:
闭包表达式生成一个具有无法写出的唯一匿名类型的闭包值。闭包类型大致相当于包含捕获变量的结构。
(4) 下面第一行被 rustc 接受,第二行不被接受:
vec![make_closure(1), make_closure(2)]; // OK
vec![(|| 1), (|| 1)]; // Error: mismatched types
Run Code Online (Sandbox Code Playgroud)
我认为你在这里混淆了概念。事实上,Rust 中的每个函数都有自己的类型,每个闭包也是如此,即使它们具有相同的主体。所以在这个例子中:
fn id0(x: u64) -> u64 { x }
fn id1(x: u64) -> u64 { x }
fn main() {
let id2 = || 1;
let id3 = || 1;
}
Run Code Online (Sandbox Code Playgroud)
每个id0、id1、id2和id3都有不同的类型,它们都实现了该Fn() -> u64特征。如果您编写call(id0)或call(id1)您将获得该函数的单态版本,该函数静态且直接地调用id0或id1,而不进行任何间接函数指针调用。
由于它们有一个空的捕获集,因此它们也可以被强制转换为非泛型函数类型:fn() -> u64。但如果你强制这样做,那么你就会失去这种单态性:call(id0 as fn() -> u64)并且call(id1 as fn() -> u64)两者都调用该call()函数的相同实例,该实例使用给定的指针间接调用内部闭包。
然后,在你的例子中:
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
Run Code Online (Sandbox Code Playgroud)
只有一个关闭。您可以使用任何数字调用此函数,并且该数字由闭包捕获,但这不会改变返回值的类型。该类型是在编译时确定的,始终相同。
如果您使用常量泛型参数,情况会有所不同:
fn make_closure_gen<const N: u64>() -> impl Fn() -> u64 {
|| N
}
Run Code Online (Sandbox Code Playgroud)
现在,对于每个N函数,您都有该通用函数的实例化,并且每个函数都有不同的返回类型。但是您不能创建实例化 1000000 个这些函数的运行时循环,因为N必须是常量。(不过,您可以尝试编译时循环)。
无论如何,即使您设法创建同一函数的数百万个实例,如果编译器检测到多个函数的内部代码相同,它也可以将它们合并为一个函数(有时称为代码重复数据删除),以便最终的结果可执行文件不会爆炸。详细信息因编译器版本而异,甚至可能取决于所使用的链接器。它甚至可以应用于非泛型函数!
就像普通类型一样,闭包类型也可以包含多个值。move || n具有相同的类型,无论 的值是什么n(闭包类型由闭包主体决定,而不是捕获的值是什么)。因此,17999999999999999999尽管 的捕获值不同,但您的闭包都是相同类型的n,因此只需要一个 的实例call。如果您实际上有 2 个不同的闭包,如以下代码所示:
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
fn make_closure2(n: u64) -> impl Fn() -> u64 {
move || n + 1
}
fn call<F: Fn() -> u64>(f: F) -> u64 {
f()
}
pub fn main() {
call(make_closure(5));
call(make_closure2(5));
}
Run Code Online (Sandbox Code Playgroud)
example::call在反编译中可以看到两个不同版本的。