Closure inlining

I am rather hazy about when Rust closures are inlined and when they are not.
Note that I'm specifically talking about closures: for functions there is precise control using #[inline] and inline(always).

Could anyone explain what the decision to inline a closure or not is based on?

1 Like

I think that closures used via generics (the usual case) are always inlined by necessity due to monomorphisation.

2 Likes

Closures de-sugar to a struct for the environment and a function call for when they get called. So they're transparent in terms of these rules; the optimizer just sees structs and functions.

5 Likes

That makes sense, thanks.

How about the desugared functions? Is there any way to influence their inlining?
As they are a generated result I can't exactly annotate them directly.

I used to think I could just use an attribute on a let binding e.g. #[inline(always)] let my_closure = || { /* stuff*/ };, but recently those seem to have become illegal so now I'm inclined to believe that while it was syntactically valid, there were never any semantics attached at all to using attributes on locals.

1 Like

Yeah, I don't think that that's supported today, though I'm not sure. The compiler will already pretty aggressively inline, the attributes just let you nudge it further. Closures, especially ones that don't capture any environment or do so by ownership, are great candidates for inlining

1 Like

One of the main heuristics compilers use to make inlining decisions is function (code) size. If you’re really paranoid about inlining failures, you can move the closure body to a separate function that has an #[inline(always)] attribute, and have the closure just call the function (or coerce the function to a closure if it doesn’t capture anything).

I’d only do this if you see an inlining failure and the performance is worse as a result.

1 Like

Inline attributes on the let binding never meant anything, but attributes on the closure expression(e.g.,#[inline(always)] || { / stuff */ }`) work. Well, they are currently broken by the same change that prohibited inline attributes on let bindings, but that is a regression, see #[inline] is no longer accepted on closures · Issue #49632 · rust-lang/rust · GitHub

2 Likes

Cool, I wasn't aware that attribute usage on rvalues was even legal.

1 Like

Speaking of, is it possible that using the attribute on expressions is still unstable?

1 Like

If you hit the compiler error, placing the code in a block {...} typically works.

1 Like

While I believe you, it does strike me as quite odd:

Why would using the attribute on a closure-in-block be stable, but applying it directly on a closure be unstable? Doesn't the fact that it works on a block demonstrate that the application on a closure itself is stable enough?

UPDATE: I just tried using #[inline(always)] || { / stuff */ }, or more precisely, #[inline(always)] &move || { / stuff */ }.
It results in a build error (actually there are many because the code lives in a macro, but because of that they're essentially the same build issue, replicated over all macro call sites), error[E0518]: attribute should be applied to function.

1 Like