It is probably because of all my years with Ruby that this seems so off to me, but also, I hear you. Hmm.
The issue in Rust is that "spawn" is generally the first time that a programmer is forced to confront closures--before that they can completely ignore them.
It is a quirk of Rust that someone who "just wants to spawn a thread" suddenly gets all this language overhead dumped on them in a block.
> might be my bias coming from a more functional background before learning Rust
This is exactly the problem. The people I'm dealing with are coming from "imperative land" and haven't had a functional background. Someone stepping up to Rust as "C with memory safety" does not have any of that functional background.
Please do remember that a "closure" is built on a lot of prior abstractions. What is a "scope"? What is "variable capture"? What is "heap" and "stack"? Why does that matter here and not normally?
No programming language is only used by experts, and Rust is no exception.
The issue is that "spawn" smacks you with a bunch of that baggage all at once.
I don't understand how a function that is defined in-place is more clear than a nice function on its own. Perhaps because it gives a sense of continuity (like in async code), but man, it's really painful to read (at least to me). In fact, if I have to write some Rust right now, all my closures would be defined elsewhere, whenever possible.
A lot of embedded code NEVER calls malloc (thus the need for no_std in Rust). Consequently, you can go a long way in embedded without really knowing the difference between stack and heap.
"Embedded programmer" does not imply "Linux Kernel Hacker".
When I started with Rust, I actually thought that the common
spawn(|| …)
syntax was something special, not just a closure with no variables.
However, that example commits the Rust "sin" I always see with "spawn" explanations in that it packs everything into a closure.
Please, please, please ... for the sake of newbies everywhere ... please define a function and call that function from inside the spawn closure. This is one of the Rust things I spend the most time explaining and unpacking for junior engineers.
The separate function makes explicit the variables that are moving/borrowing/cloning around, which names are external/passed/closure-specific, and how it all ties together. It breaks apart the machinery that gets all conflated with the closure. They can always make it succinct again later once they understand the machinery more completely.
This gets particularly bad with "event loops" (I have a loooooong rant I need to write up about event loops and why they are evil--and it's not Rust specific) and deep "builder chains" (which I consider a Rust misfeature that desperately needs to go away).