When should I use #[inline]?


#1

Inline attributes can be added to functions and methods. As a library author, when should I use them?

I feel like the compile probably knows better than me when inlining is beneficial or not, so I don’t use #[inline(always)] or #[inline(never)]. (I guess I only would if measured evidence shows that it helps significantly when trying to optimize something.) But #[inline] is less clear-cut. There can be no cross-crate inlining without it, and cross-crate is the main usage of a library. So I tend to add it to functions that I feel are “small enough”, but that’s kinda random.

Is any use of #[inline] without benchmarks premature optimization? Or should I #[inline] all the things and let the compiler figure it out? Or is there a better criteria for something in between?


#2

Without knowing anything about the compiler internals, I’d lean on using it everywhere unless binary size or compile time is a concern.

I feel like rustc should allow cross-crate inlining by default, with maybe an #[inline(seldom)] anti-hint and a bunch of command line options.


#3

#[inline] has non-trivial compilation time costs (a function with #[inline] is recompiled in every crate that uses it, like a generic function), and, more importantly, it can easily be a pessimisation, as it is giving an inline-hint to override LLVM’s better judgement by increasing the cost threshold under which the function will be inlined.

If you really want the fastest binary the compiler can make, you can/should use link-time optimisation (rustc -C lto) which has access to all of the crate’s Rust dependencies, in inlinable form (including things without #[inline] attributes). This is very slow to compile, but it’s isomorphic to adding #[inline] to everything and I’d guess that the attributes on everything is even slower than -C lto.

#[inline] should be preferred to be used only on performance-critical things; e.g. putting #[inline] on most functions doing IO will be absolutely pointless for run-time performance (and just drag compile-time performance down the drain).


#4

What if I’m making a library for other people to use, and I don’t know what they really want?


#5

From the reference:

#[inline] and #[inline(always)] always causes the function to be serialized into crate metadata to allow cross-crate inlining.

This is ambiguous on what happens if you don’t specify #[inline]. Can the compiler still decide to serialize the function into crate metadata, e.g. if it’s small enough? If so, should the guideline be “don’t use any inline attribute and let the compiler do its job”?


#6

Link-time optimisation is something that application authors opt-in to, it requires no effort on the part of library authors. It was unclear of me to say “you” in that context. Rephrased: to get the fastest binary available, an application author can use LTO and that will suck in (and possibly inline) functions from all the Rust libraries that they depend on, whether or not there is #[inline] or #[inline(always)] annotations in those libraries. That is, -C lto is a little like a retroactive #[inline] on steroids.

(In fact, AIUI, library authors cannot do anything with -C lto: it only works when producing a final artifact, that is, a binary or static-lib.)

The compiler currently does not automatically serialize to metadata (that is, only generic, #[inline] and #[inline(always)] functions are in metadata)… but, as you say, it’s not explicitly disallowed.


#7

In a PR I just posted, @strcat notes about generic code:

Since it’s all generic code, inlining across crates is already possible. That may change in the future if Rust ever supports re-exporting instantiations of generics like C++11.


#8

Is there an update to this? After all, this topic is over two years old.


#9

Nothing has really changed in two years.


#10

https://bugzilla.mozilla.org/show_bug.cgi?id=1351737 is about the generated code size in Stylo. A single symbol is 544 KB. In related files, hundreds of functions and methods have #[inline] added to them, probably out of habit.