Reputation: 9009
In this simple example
#[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));
}
the closure passed to apply
is passed as a 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
}
(apply
actually has a more complicated name in the generated LLVM code.)
This causes two loads to get to each of the captured variables. Why isn't %closure
just {i64, i64}
(which would make the argument to apply
{i64, i64}*
)?
Upvotes: 3
Views: 182
Reputation: 65937
Closures capture by reference by default. You can change that behavior to capture by value by adding the move
keyword before the parameter list:
let f = move |x: i64| x + y + z;
This generates much leaner code:
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
}
Adding the move
keyword means that any value that the closure uses will be moved into the closure's environment. In the case of integers, which are Copy
, it doesn't make much difference, but in the case of other types like String
, it means that you can't use the String
anymore in the outer scope after creating the closure. It's an all-or-nothing deal, but you can manually take references to individual variables outside a move
closure and have the closure use these references instead of the original values to get manual capture-by-reference behavior.
Can you observe the value vs ref difference somehow in this code?
If you take the address of the captured variable, you can observe the difference. Notice how the first and second output lines are the same, and the third is different.
Upvotes: 6