Can you go into more details? Most of the criticism I've read tends to be more abstract ("I don't like how ALL my blocking-style calls need to be async"), and doesn't propose an alternative mechanism to async that can provide a similar coding style in the same tight RAM footprint.
I think you'll find details regarding any discussion of async in rust and other languages - I don't mean to casually dismiss your question, but my objections are not unique.
The alternative mechanism is to use interrupts, DMA, multiple cores, distributed devices (eg a CAN network) a state machine, an RTOS, or, it sounds like in context of this thread, PIOs! You get the point. Do these provide a similar coding style? No, and that's the point. The coding style is the objection.
I find the "how else would you do it" style questions that come up frequently re Async rust (embedded or not) amusing. It's as if there is a new method of accomplishing a task, and asking a world that has been accomplishing this task for decades how it's possible to accomplish the task without the new thing!
As I see it, if you need everything to run on a single cpu core, the alternatives are to either implement threads (wasting memory on redundant stacks) or to write the event-driven state machines manually. Whether the state machine is pumped by interrupts or not doesn't change anything IMHO.
Because of RAM constraints, all the bare-metal projects I've worked on have used manually-written state machines, and I'm comfortable enough with this approach. But sometimes these state machines can be hard to understand when the control flow is complicated, and I am seriously considering adding some compiler-generated state machines that will fit nicely into my existing model.
none of this will be news to you, but it's probably of interest to other people reading the discussion. you can do stuff inside the interrupt handler itself, and while what you do there does have to be an event-driven explicit state machine, interrupts introduce two key differences:
- the rest of your program doesn't have to be an explicit state machine; it can use structured control flow with nested loops and conditionals and subroutines
- the interaction between the interrupt handlers and the rest of the program is almost completely asynchronous, because as long as interrupts are enabled, the interrupt can fire between any two instructions of the rest of the program; if you look at it as multitasking, it's preemptive multitasking rather than cooperative multitasking. preemptive multitasking introduces a lot of hairy error cases, and this is only moderately simplified by the fact that the rest of the program can't preempt your interrupt handler, only vice versa. arguably that makes the problem worse rather than better because you can't solve the problem with locks (except by disabling interrupts as a sort of global lock)
> the rest of your program doesn't have to be an explicit state machine; it can use structured control flow with nested loops and conditionals and subroutines
This works well until the requirements change and you have to run two structured control flows simultaneously. If I find myself in such a situation and have no SRAM for a second thread, rust async may be the quickest way to accomplish the goal without a major rewrite into manual event driven code.
i don't have experience with rust async, although i used to maintain a csp protocol stack in python async, which is pretty similar, so tell me if this is wrong
i feel like the same kind of thing can happen even if you start entirely async, because something that was previously synchronous may have to become asynchronous, which leads to having to revalidate all your concurrency assumptions all the way up its (static) call stack. wherever you were depending on not getting preempted, you need to change the code to not depend on that anymore. but if ram is so tight that you're concerned about the sram for a second stack, maybe that's a pretty small task rather than a major rewrite
that said, i don't recall having actually had that problem