Does const fn imply #[inline]?

Do note that "thin LTO" is enabled by default in release profile, which brings back unmarked inlining across crates.

Now I'm just a little confused. I don't expect inlining in debug builds and dont want it either.

And for release it is on by default through some partial LTO?

So if the default would have made a function available for inlining already, does annotating it with inline do anything further? Does it force more inlines than without it?

I miss Java's ability to seemlessly inline lambda regardless of where they were defined. Would even monomorphize ro bimnorphize the code around it too.

without any changes to Cargo.toml, it is my understanding that rust does crate-local thin-lto. That does LTO across codegen units within a crate, but not between different crates.

That is, of the options here, the default is false:
https://doc.rust-lang.org/cargo/reference/profiles.html#lto

Generic functions are always implied #[inline], so the example of a sort function which takes a closure (which is a generic parameter in Rust) would likely be optimized the same.

Note that making #[inline] the default would substantially hurt compile times; it would make most crates have to be compiled sequentially instead of in parallel. You'd have to show a substantial runtime performance improvement to convince people to disregard the compile-time hit (and I don't personally believe the runtime benefit would be substantial)

Adding the whole definition of the function (instead of only declaration) to the header file is a kind of "telling to the compiler", probably?

I woudnt' say that. Its a somewhat date look at the current convention of header-online libraries. It isn't extra work I'm doing, and the compiler has no problem ignoring it to such an extent that isn isn't described that way. It probably depends on perspective to some extent, A lot more goes into headers than used to and it isnt' about inlining much anymore.

Basically, I dont want to makr inline becaue I dont want the compiler to think im saying antyihng abtout that. it will make a better decision than i will most of the time.

i am willing to take compile time hit for rare release builds and have tthe whole world marked as inlineable. there is also a point between those where the compiler auto exports what it thinks it should - that would seem like an appropriate default but it isnt.

If, in C or C++, you have a header only library, the compile time hit isn't just for optimized builds. Rather, the contents of the header are reparsed and recompiled for every source file that (transitively) includes the header. Headers are dumb, pure source inclusion. The equivalent is include!.

If your header only library is entirely templated, then you get equivalent behavior from Rust by default, that is, code is available for inlining by default without any hints as to its fitness for inlining.

If your "header only library" is a lie and requires a singular

#define HEADER_ONLY_LIB_IMPL 1
#include "header_only_lib.h"

then a) it's not header only, you just hid the .c in the header behind macro state, and b) you get the same behavior from Rust by default as if you did the impl include from a standalone .c file.

(Yes I am salty.)

Roughly the state of what we have:

  • Unanotated, non generic functions in a crate are not available for inlining cross-crate. This is equivalent to in C or C++ having the function declaration in the header but no definition.
  • Generic functions are equivalent to templated functions in C++, in that they are compiled in every translation unit, and available for inlining at the default hint level.
  • #[inline] functions are compiled in every translation unit that uses them and are given the LLVM inline hint.
  • #[inline(always)] functions are compiled in every translation unit that uses them and are given the LLVM forceinline hint. (It's still only a hint, though; LLVM can choose not to inline such a function.)

I agree in principle, though, that an #[inline(maybe)] would be useful for some libraries, which makes the function compile in each translation unit (therefore making it available for inlining) but does not apply any inlining hints for LLVM.

However, the default is good, because it allows for separate compilation to be a thing. Plus, true fat LTO gets back most of the advantage of single-translation-unit compilation without the downsides of completely removing separate compilation.

Basically, what you're asking for is for every --release build to be a clean build. For distribution builds, this is perhaps reasonable, and those are the ones using full profile guided optimization and fat LTO. But for normal optimized builds without that level of effort warranted to get the absolute best possible performance, separate compilation is a good thing.

7 Likes