[HN Gopher] The Cost of Indirection in Rust
       ___________________________________________________________________
        
       The Cost of Indirection in Rust
        
       Author : sebastianconcpt
       Score  : 79 points
       Date   : 2026-03-09 17:28 UTC (3 days ago)
        
 (HTM) web link (blog.sebastiansastre.co)
 (TXT) w3m dump (blog.sebastiansastre.co)
        
       | cat-whisperer wrote:
       | I wouldn't have agreed with you a year ago. async traits that
       | were built with boxes had real implications on the memory. But,
       | by design the async abstraction that rust provides is pretty
       | good!
        
       | slopinthebag wrote:
       | Cool article but I got turned off by the obvious AI-isms which,
       | because of my limited experience with Rust, has me wondering how
       | true any of the article actually is.
        
         | ramon156 wrote:
         | I don't see anything wrong code-wise, but it's definitely an
         | odd way of making an accumulator. Maybe I'm pedantic
        
       | bombela wrote:
       | I think this long post is saying that if you are afraid that
       | moving code behind a function call will slow it down, you can
       | look at the machine code and run a benchmark to convince yourself
       | that it is fine?
        
         | layer8 wrote:
         | I think it's making a case that normally you shouldn't even
         | bother benchmarking it, unless you know that it's in a critical
         | hot path.
        
           | eptcyka wrote:
           | I must add that code is on the hot path only under two
           | conditions:
           | 
           | - the application is profiled well enough to prove that some
           | piece of code is on the hot path
           | 
           | - the developers are not doing a great job
        
         | antonvs wrote:
         | This long post is demonstrating that Knuth's advice, "premature
         | optimization is the root of all evil," is still one of the
         | first heuristics you should apply.
         | 
         | The article describes a couple of straw men and even claims
         | that they're right in principle:
         | 
         | > Then someone on the team raises an eyebrow. "Isn't that an
         | extra function call? Indirection has a cost." Another member
         | quickly nods.
         | 
         | > They're not wrong in principle.
         | 
         | But they _are_ wrong in principle. There's no excuse for this
         | sort of misinformation. Anyone perpetuating it, including the
         | blog author, clearly has no computer science education and
         | shouldn't be listened to, and should probably be sent to a
         | reeducation camp somewhere to learn the basics of their
         | profession.
         | 
         | Perhaps they don't understand what a compiler does, I don't
         | know, but whatever it is, they need to be broken down and
         | rebuilt from the ground up.
        
       | Scubabear68 wrote:
       | A function call is not necessarily an indirection. Basic premise
       | of the blog is wrong on its face.
        
         | alilleybrinker wrote:
         | Did you read the article? The author makes exactly that point.
        
         | hrmtst93837 wrote:
         | People new to Rust sometimes assume every abstraction is free
         | but that's just not the case, especially with lifetimes and
         | dynamic dispatch. Even a small function call can hide
         | allocations or vtable lookups that add up quickly if you're not
         | watching closely.
        
       | ekidd wrote:
       | We have been able to automatically inline functions for a few
       | decades now. You can even override inlining decisions manually,
       | though that's usually a bad idea unless you're carefully
       | profiling.
       | 
       | Also, it's pointer indirection in data structures that kills you,
       | because uncached memory is brutally slow. Function calls to
       | functions in the cache are normally a much smaller concern except
       | for tiny functions in very hot loops.
        
         | scottlamb wrote:
         | I'm not sure Rust's `async fn` desugaring (which involves a
         | data structure for the state machine) is inlineable. (To be
         | precise: maybe the desugared function can be inlined, but LLVM
         | isn't allowed to change the data structure, so there may be
         | extra setup costs, duplicate `Waker`s, etc.) It's probably true
         | that there is a performance cost. But I agree with the
         | article's point that it's generally insignificant.
         | 
         | For non-async fns, the article already made this point:
         | 
         | > In release mode, with optimizations enabled, the compiler
         | will often inline small extracted functions automatically. The
         | two versions -- inline and extracted -- can produce identical
         | assembly.
        
           | ekidd wrote:
           | I am fairly doubtful that it makes sense to be using async
           | function calls (or waits) inside of a hot loop in Rust.
           | Pretty much anything you'd _do_ with async in Rust is too
           | expensive to be done in a genuinely hot loop where function
           | call overhead would actually matter.
        
       | Sytten wrote:
       | Also to note that the inline directive is optional and the
       | compiler can decide to ignore it (even if you put always if I
       | remember)
        
       | cwillu wrote:
       | > Maintainability and understandability only show up when you're
       | deliberate about them. Extracting meaning into well-named
       | functions is how you practice that. Code aesthetics are a feature
       | and they affect team and agentic coding performance, just not the
       | kind you measure in the runtime.
       | 
       | > And be warned: some will resist this and surrender to the
       | convenience of their current mental context, betting they'll
       | "remember" how they did it. Time will make that bet age badly.
       | It's 2026 -- other AI agents are already in execution loops,
       | disciplined to code better than that."
       | 
       | Hard disagree: separating code from its context is exactly how
       | you end up in the situation of needing to "remember". Yes, helper
       | functions and such _can_ be useful for readability, but it 's
       | easy to overdo it and end up with incomprehensible ravioli code
       | that does nothing terribly complicated in a terribly complicated
       | manner.
        
         | schubart wrote:
         | I'm familiar with spaghetti code and with lasagna code (too
         | many layers) but I'm curious: what's ravioli code?
        
           | tartoran wrote:
           | Think of what ravioli are and apply that to the same code
           | analogy as spagetti or lassagna. The code is split in tiny
           | units and that creates too much indirection, a different
           | indirection than spagetti or ravioli. The architecture feels
           | fragmented even though there's nothing wrong with each piece.
        
           | p1necone wrote:
           | Each part of the codebase is a separate self contained module
           | with its own wrapping (boilerplate), except there's like 30
           | of them and you still have to understand everything as a
           | whole to understand the behaviour of the system anyway.
        
           | fsckboy wrote:
           | a ravioli is a black beige box abstraction to which you pasta
           | arguments interface usually after forking
        
         | chowells wrote:
         | I think I agree with what you're getting at, though I usually
         | phrase it differently: indirection is not abstraction. A good
         | abstraction makes it easier to understand what the code is
         | doing by letting you focus on the important details and ignore
         | the noise. It does this by giving you tools that match your
         | problem space, whatever it may be. This will necessarily
         | involve some amount of indirection when you switch semantic
         | levels, but that's very different from constantly being told
         | "look over there" when you're trying to figure out what the
         | code is saying.
        
           | scottlamb wrote:
           | Agree, and I would add that a bad abstraction, the wrong
           | abstraction for the problem, and/or an abstraction misused is
           | far worse than no abstraction. That was bugging me in another
           | thread earlier today:
           | <https://news.ycombinator.com/item?id=47350533>
        
         | OtomotO wrote:
         | Someone has worked too much on corporate Java Codebases.
         | 
         | I feel your pain. Everything is so convoluted that 7 layers
         | down you ask yourself why you didn't learn anything useful...
        
           | cwillu wrote:
           | Last time was a go shop, and let me tell you: that style
           | mixes with go's error handling like spoiled milk and blended
           | shit.
           | 
           | Oh gee, thank you for this wrapped error result, let me try
           | to solve a logic puzzle to see (a) where the hell it actually
           | came from, and (b) how the hell we got there.
        
       | armchairhacker wrote:
       | A nitpick I have with this specific example: would
       | `handle_suspend` be called by any other code? If not, does it
       | really improve readability and maintainability to extract it?
        
         | rudolph9 wrote:
         | The idea is that performance isn't a reason not to do it. Other
         | considerations may cause you to choose inline, but performance
         | shouldn't be one of them.
        
         | elzbardico wrote:
         | re-use as a criteria for functional decomposability is a very
         | misguided notion
        
       | thezipcreator wrote:
       | seems pointless to extract `handle_suspend` here. There are very
       | few reasons to extract code that isn't duplicated in more than
       | one place; it's probably harder to read to extract the handling
       | of the event than to handle it inline.
        
         | kstrauser wrote:
         | One huge one is so that you can test it in isolation.
        
         | scuff3d wrote:
         | There's extraction for reuse and then theres extraction for
         | readability/maintainability. The second largely comes down to
         | personal taste. I personally tend to lose the signal in the
         | noise, so it's easy for me to follow the logic if some of the
         | larger bits are pushed into appropriately named functions. Goes
         | to the whole self commenting code thing. I know there's a chunk
         | of code behind that function call, I know it does some work
         | based on its name and args, but I don't have to worry about it
         | in the moment. There's a limit of course, moving a couple lines
         | of code out without good cause is infuriating.
         | 
         | Other people prefer to have big blocks of code together in one
         | place, and that's fine too. It just personally makes it harder
         | for me to track stuff.
        
         | skrtskrt wrote:
         | I strongly prefer this sort of code:
         | 
         | ```                   fn does_a_many_step_process():
         | first_step_result_which_is_not_tied_to_details_internal_to_the_
         | step_implementation =
         | well_named_first_step_which_encapsulates_concerns();
         | second_step_result_in_same_manner =
         | well_named_second_step_which_encapsulates_concerns();
         | ...etc
         | 
         | } ```
         | 
         | The logic of process flow is essentially one kind of
         | information. All the implementation details are another. Step
         | functions should not hide further important steps - they should
         | only hide hairy implementation details _that other steps don 't
         | need to know about_.
        
       | hutao wrote:
       | One of the unwritten takeaways of this post is that async/await
       | is a leaky abstraction. It's supposed to allow you to write non-
       | blocking I/O as if it were blocking I/O, and make asynchronous
       | code resemble synchronous code. However, the cost model is
       | different because async/await compiles down to a state machine
       | instead of a simple call and return. The programmer needs to
       | understand this implementation detail instead of pretending that
       | async functions work the same way as sync functions. According to
       | Joel Sposky, all non-trivial abstractions are leaky, and
       | async/await is no different. [0]
       | 
       | The article mixes together two distinct points in a rather
       | muddled way. The first is a standard "premature optimization is
       | the root of all evil" message, reminding us to profile the code
       | before optimizing. The second is a reminder that async functions
       | compile down to a state machine, so the optimization reasoning
       | for sync functions don't apply.
       | 
       | [0] https://www.joelonsoftware.com/2002/11/11/the-law-of-
       | leaky-a...
        
       ___________________________________________________________________
       (page generated 2026-03-12 23:00 UTC)