Why doesn't the Default trait of Vec use the inline attribute?

instand of:

#[stable(feature = "rust1", since = "1.0.0")]
impl<T> Default for Vec<T> {
    /// Creates an empty `Vec<T>`.
    ///
    /// The vector will not allocate until elements are pushed onto it.
    #[inline]
    fn default() -> Vec<T> {
        Vec::new()
    }
}

Generic functions are already eligible for cross-crate inlining, so it shouldn't need the attribute. There are a few generic Default impls with #[inline] which are harmless inconsistency, but I also found this non-generic impl for Box<Cstr> without #[inline] which is probably a mistake.

2 Likes

Adding the inline attribute would be a premature optimization and unnecessary step, since the compiler is very smart and will generally automatically inline small functions when possible.

Using the Compiler Explorer at godbolt.org you can compare the generated code for functions that use either Vec::new() and Vec::default() and the result is that both are inlined at the call site.

Note that #[inline] doesn't mean that the function will be inlined. That would be #[inline(always)]. Even that is not a hard guarantee, but it does make inlining extremely likely.

Experts generally recommend against using it, though. The compiler's inlining heuristics are generally good enough, and the benefits of inlining are very mixed. You code may easily become faster on a microbenchmark of this specific function, but much slower overall due to extra code bloat.

#[inline] just makes a function eligible for cross-crate inlining. This is always true for most generic functions (over types and constant values, but note that generic lifetime parameters don't matter). For non-generic functions, you should still generally avoid it, because it bloats compile times for generally marginal benefits.

1 Like

Note that this isn't even needed any more for simple-enough functions.

Thanks to some work a few months ago, functions that are simple enough are now automatically made available for cross-crate inlining even if they're concrete and not explicitly marked #[inline].

So if you have a function that just calls another function, you no longer need to bother marking it #[inline].

(Also, if you're really worried about inlining details for stuff like the example in the OP, then you probably want to run LTO, which cares even less about whether you marked something #[inline].)

4 Likes

Sounds like bare #[inline] could just be soft-deprecated. I struggle to think of an example where I would even use it now. Simple functions are already inlined, complex ones probably shouldn't, and even if they should LTO and PGO would do it much better.

You are right, I found that the Default impl of HashMap and HashSet comes with #[inline].
But this doesn’t explain why Vec doesn’t have inline attr, or why HashMap and HashSet do have inline attr. What are the conditions for “a few”?

Since #[inline] is no longer needed in the current Rust version (see discussion above), isn't this question less relevant?

Got it, thank you for your answers.

1 Like

"Are simple enough" isn't the same as "always when you want it", and going so far as deprecation would be a form of guaranteed optimization,[1] which is generally best avoided since it ties the compiler devs hands moving forward.


  1. or pessimization or "will compile this way" more generally ↩︎

Vec is the one that makes sense. It doesn't have #[inline] because it would have no effect. There is no practical reason HashMap or HashSet have #[inline]. You could remove it and nothing would happen.

That's a bit strong. The current "automatic inline" is quite conservative -- I think even two ifs (in addition to the call) is enough to keep it from triggering.

A couple examples where you might want to do it:

  • Something with a loop that you expect can often fully-unroll to something small for certain monomorphizations
  • Splitting a function into some hot-path checks and setup that you do want to inline, then the rest of it that you don't (example)

But yes, with the automatic cross-crate for functions that are just -> &Foo{ &self.foo }, the right answer is to type #[inline] far less often than used to be the suggestion.

2 Likes