Hacker News new | ask | show | jobs
by aturon 3605 days ago
To be clear, the issue here isn't so much stack vs heap, as much as how many heap allocations are happening.

In practice, you build up a really big combined future on the stack, which has all of the space needed for any state in its state machine, and then when the future is fired off (in a task -- one per connection), that entire thing is moved into the heap in one shot. Thus, generally, you do one big allocation up front per connection -- exactly the same as you'd do when writing state machines by hand.

The follow up posts will go into much more detail on this point :)

1 comments

Fair enough, but op was quite misleading by saying that in rust closure don't need allocation.

So back to future vs coroutine , it seems that they the advantage in term of allocation simply because the context is not distroid when suspending/resuming coroutines

  let mut counter = 0;
  let mut increment = || { counter += 1; counter };

  increment();
  increment();
There is no heap allocation there.
This is missing his point. "Increment" is called outside of its scope when the epoll triggers. If "counter" is stack allocated that would cause a segfault, so it must be heap allocated which is his point.
Well, they're two different points: closures in rust do not inherently have to heap allocate. But that also doesn't mean that they can _not_ be heap allocated either.

And in this example, it's not even really the closures that allocate: it's still one allocation, regardless of the number of closures.

No. they are not two different points. You are artificially restricting the argument, and saying that some parts are a different question by introducing this new idea of "inherent nature" of a closure in rust : Some humans have legs, some do not. Does it mean that having legs is not an "inherent" part of the human experience?

So to go back to the argument, we are trying to compare using a coroutine base approach vs using a future/state machine + closure approach. My point was that because a coroutine allows one to enter and leave a stack frame without destroying it, it can lead to less allocation and therefore be more efficient in those cases (and let's not even talk about the cost of the activation/deactivation of the stack frame)

To use your analogy, I feel like you're saying that only humans that have legs are human. I'm saying that they're all human.

This analogy is weird.

Furthermore, because closures aren't inherently boxed, there might not even be a stack frame to begin with. Closures are syntax sugar for a struct + a trait implementation on that struct, and said call is then very inline-able.

This is also where I'm getting at with the single invocation thing: with these futures, all of these closures are going to be inlined into one big state machine, which is then itself eventually put on the heap. But that heap allocation has nothing to do with the closure, and more to do with the future wanting to not tie its data down to a specific stack frame.

> Some humans have legs, some do not. Does it mean that having legs is not an "inherent" part of the human experience?

I mean - yes, that is what it means.

You don't have to destroy the "stack frame" (by which I assume you mean the state retained between blocking operations) every time you enter and leave. Why do you think you need to?
May I ask, where do you assume a coroutine's stack lives?

i.e. In relation to program memory