134

Rust has an "inline" attribute that can be used in one of those three flavors:

#[inline]

#[inline(always)]

#[inline(never)]

When should they be used?

In the Rust reference, we see an inline attributes section saying

The compiler automatically inlines functions based on internal heuristics. Incorrectly inlining functions can actually make the program slower, so it should be used with care.

In the Rust internals forum, huon was also conservative about specifying inline.

But we see considerable usage in the Rust source, including the standard library. A lot of inline attributes are added to one-line-functions, which should be easy for the compilers to spot and optimize through heuristics according to the reference. Are those in fact not needed?

1 Answer 1

102

One limitation of the current Rust compiler is that it if you're not using LTO (Link-Time Optimization), it will never inline a function not marked #[inline] across crates. Rust uses a separate compilation model similar to C++ because LLVM's LTO implementation doesn't scale well to large projects. Therefore, small functions exposed to other crates need to be marked by hand. This isn't a great situation, and it's likely to be fixed in the future by some combination of improvements to LTO and MIR inlining.

#[inline(never)] is sometimes useful for debugging (separating a piece of code which isn't working as expected). In theory, it can be used for benchmarking, but that's usually a bad idea: turning off inlining doesn't prevent other inter-procedural optimizations like constant propagation. In terms of normal code, it can reduce codesize if you have a frequently used helper function which is only used for error handling.

#[inline(always)] is generally bad idea; if a function is big enough that the compiler won't inline it by default, it's big enough that the overhead of the call doesn't matter (and excessive inlining increases instruction cache pressure). There are exceptions, but you need performance measurements to justify it. This example is the sort of situation where it's worth considering. #[inline(always)] can also be used to improve -O0 code quality, but that's not usually worth worrying about.

3
  • 35
    note that inline(never) is used on the panic intrinsics to make sure that the optimizer doesn't inline functions that are only called in the panic case.
    – oli_obk
    Jun 6, 2016 at 10:56
  • 8
    -1 because there is something missing to the first point. Generic items can be inlined across crates, because they are effectively compiled when instantiated, so the code needed for inlining is readily available. And this means that a huge number of such items that are not marked, can still be inlined cross-crate. In some kinds of basic library crates, every item is generic!
    – bluss
    Aug 21, 2019 at 17:13
  • 2
    Has the lack of inlining across crates (absent LTO) been fixed since writing? If not, and one has something like #[inline] pub fn f() { g() }, should g also be annotated #[inline] if one wishes g to be inlined into f's caller from another crate?
    – eggyal
    Oct 26, 2021 at 4:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.