在这个简单的例子中
#[inline(never)]
fn apply<F, A, B>(f: F, x: A) -> B
where F: FnOnce(A) -> B {
f(x)
}
fn main() {
let y: i64 = 1;
let z: i64 = 2;
let f = |x: i64| x + y + z;
print!("{}", apply(f, 42));
}
Run Code Online (Sandbox Code Playgroud)
传递给的闭包apply
作为LLVM IR传递{i64*, i64*}*
:
%closure = type { i64*, i64* }
define internal fastcc i64 @apply(%closure* noalias nocapture readonly dereferenceable(16)) unnamed_addr #0 personality i32 (i32, i32, i64, %"8.unwind::libunwind::_Unwind_Exception"*, %"8.unwind::libunwind::_Unwind_Context"*)* @rust_eh_personality {
entry-block:
%1 = getelementptr inbounds %closure, %closure* %0, i64 0, i32 1
%2 = getelementptr inbounds %closure, %closure* %0, i64 0, i32 0
%3 = load i64*, i64** %2, align 8
%4 = load i64*, i64** %1, align 8
%.idx.val.val.i = load i64, i64* %3, align 8, !noalias !1
%.idx1.val.val.i = load i64, i64* %4, align 8, !noalias !1
%5 = add i64 %.idx.val.val.i, 42
%6 = add i64 %5, %.idx1.val.val.i
ret i64 %6
}
Run Code Online (Sandbox Code Playgroud)
(apply
实际上在生成的LLVM代码中有一个更复杂的名称.)
这导致两个负载到达每个捕获的变量.为什么不%closure
只是{i64, i64}
(这会引起争论apply
{i64, i64}*
)?
默认情况下,闭包通过引用捕获.您可以通过move
在参数列表之前添加关键字来更改该行为以按值捕获:
let f = move |x: i64| x + y + z;
Run Code Online (Sandbox Code Playgroud)
这会生成更精简的代码:
define internal fastcc i64 @apply(i64 %.0.0.val, i64 %.0.1.val) unnamed_addr #0 personality i32 (i32, i32, i64, %"8.unwind::libunwind::_Unwind_Exception"*, %"8.unwind::libunwind::_Unwind_Context"*)* @rust_eh_personality {
entry-block:
%0 = add i64 %.0.0.val, 42
%1 = add i64 %0, %.0.1.val
ret i64 %1
}
Run Code Online (Sandbox Code Playgroud)
添加move
关键字意味着闭包使用的任何值都将移动到闭包的环境中.在整数的情况下Copy
,它没有太大的区别,但在其他类型的情况下String
,这意味着String
在创建闭包后不能再在外部范围中使用它.这是一个全有或全无的交易,但你可以手动接受move
闭包之外的各个变量,并让闭包使用这些引用而不是原始值来获得手动捕获引用行为.
您能否在此代码中以某种方式观察值与ref的差异?
如果您获取捕获变量的地址,您可以观察到差异.注意第一和第二输出线是如何相同的,第三条是不同的.