Is `#[inline]` transitive for public non-generic library functions?


#1

My understanding is that one of the valid usages of #[inline] is to mark public non-generic library functions, so that rustc saves their intermediate code in the compiled library, so that such functions could be inlined in downstream crates at all.

What happens in cases like this?

#[inline]
pub fn tiny() {
   smol()
}

fn smol() {
    // ...
}

Should I annotate smol as well to guarantee that rustc can inline both tiny and small in downstream crates?


#2

I’m interested to know if anyone knows this at the generated-assembly level, but my understanding of the inlining process is that no, tiny is a function-call indirection, and so is smol, and giving the compiler the option to inline tiny will allow it to take the instructions that would be located at an address for tiny (including the jmp or call to smol), but doesn’t also ‘unfold’ smol.

I checked this assumption in the playground, and it seems to play out as expected - though do bear in mind that #[inline] doesn’t force, just allows the inlining. I had to include the always qualifier in the playground here for the compiler to inline in such a trivial program.


#3

Use in Cargo.toml for cross library inlining.

[profile.release]
lto=true

Rust (llvm) will usually sort out the best optimisation for inlining. Adding the attribute has a chance of making code slower. So you only every should do so after profiling.


#4

If you want that, yes.

It’s non-transitive because not marking smol as #[inline] can be very useful if tiny only calls it sometimes. For example, tiny might be a cache lookup, where the code to generate the value on a cache miss is intentionally an out-of-line call since duplicating it all over would be a waste.


#5

Interesting! I’ve tested locally and it indeed seems to be the case, which is somewhat counter-intuitive.

Naively I would expect that, when compiling the library crate, smol is inlined (or not) into tiny, and that all that bunch is then inlinable into downstream create.

From my understanding of how current rlibs work, I sort of see why this doesn’t work.