top | item 46133447

(no title)

thinkharderdev | 2 months ago

> If it were written with async it would likely have enough other baggage that it wouldn't fit or otherwise wouldn't work

I'm unclear what this means. What is the other baggage in this context?

discuss

order

hansvm|2 months ago

In context (embedded programming, which in retrospect is still too big of a field for this comment to make sense by itself; what I meant was embedded programming on devices with very limited RAM or other such significant restrictions), "baggage" is the fact that you don't have many options when converting async high-level code into low-level machine code. The two normal things people write into their languages/compilers/whatever (the first being much more popular, and there do exist more than just these two options) are:

1. Your async/await syntax desugars to a state machine. The set of possible states might only be runtime-known (JS, Python), or it might be comptime-known (Rust, old-Zig, arguably new-Zig if you squint a bit). The concrete value representing the current state of that state machine is only runtime-known, and you have some sort of driver (often called an "event loop", but there are other abstractions) managing state transitions.

2. You restrict the capabilities of async/await to just those which you're able to statically (compile-time) analyze, and you require the driver (the "event loop") to be compile-time known so that you're able to desugar what looks like an async program to the programmer into a completely static, synchronous program.

On sufficiently resource-constrained devices, both of those are unworkable.

In the case of (1) (by far the most common approach, and the thing I had in mind when arguing that async has potential issues for embedded programming), you waste RAM/ROM on a more complicated program involving state machines, you waste RAM/ROM on the driver code, you waste RAM on the runtime-known states in those state machines, and you waste RAM on the runtime-known boxing of events you intend to run later. The same program (especially in an embedded context where programs tend to be simpler) can easily be written by a skilled developer in a way which avoids that overhead, but reaching for async/await from the start can prevent you from reaching your goals for the project. It's that RAM/ROM/CPU overhead that I'm talking about in the word "baggage."

In the case of (2), there are a couple potential flaws. One is just that not all reasonable programs can be represented that way (it's the same flaw with pure, non-unsafe Rust and with attempts to create languages which are known to terminate), so the technique might literally not work for your project. A second is that the compiler's interpretation of the particular control flow and jumps you want to execute will often differ from the high-level plan you had in mind, potentially creating more physical bytecode or other issues. Details matter in constrained environments.

thinkharderdev|2 months ago

That makes sense. I don't know anything about embedded programming really but I thought that it really fundamentally requires async (in the conceptual sense). So you have to structure your program as an event loop no matter what. Wasn't the alleged goal of rust async to be zero-cost in the sense that the program transformation of a future ends up being roughly what you would write by hand if you have to hand-roll a state machine? Of course the runtime itself requires a runtime and I get why something like Tokio would be a non-started in embedded environments, but you can still hand-roll the core runtime and structure the rest of the code with async/await right? Or are you saying that the generated code even without the runtime is too heavy for an embedded environment?