Async Rust tokio signature question

Why do I see "experts" blogs refer to the tokio signature as a footgun?

pub fn spawn(future: T) -> JoinHandle<T::Output>
where
T: Future + Send + 'static,
T::Output: Send + 'static;

There are several arguments one might make about the particular behavior of tokio::spawn() being a bad default; I've seen arguments about:

  • Requiring the future to be 'static
  • Requiring the future to be Send
  • Not cancelling the task when the JoinHandle is dropped

Which one do you mean? And what exact question do you have about it? Every element of this is a design tradeoff, so we cannot just say that tokio::spawn() is incorrect in some way, else it would surely have been fixed.

1 Like

Thanks. When I see that signature, I never get an explanation as to what specifically they are referring.

Can you point to an example of "experts" referring to it as a footgun? There's several choices that Tokio has made that could be considered footguns, but without a bit more context, it's hard to tell which choices the "experts" might be objecting to.

1 Like

I was trying to decipher this at Local Async Executors and Why They Should be the Default as I contemplate taking on Async.

Isn't all of this extra work? After all just calling spawn every time you need to do something in the background is pretty easy, right? Well, I suggest to you, dear reader, that this function signature:

pub fn spawn<T>(future: T) -> JoinHandle<T::Output> where
    T: Future + Send + 'static,
    T::Output: Send + 'static,

is a gun. We give it to all Rustaceans reaching for async, seasoned or new alike, and they all use it to repeatedly shoot themselves in the foot, over, and over, and over again, and most of the time they don't even know that's what they are doing. That's how easy to use it is.

Using multi-threaded spawn reduces the friction of managing multiple tasks across all CPU cores, and it only costs you friction everywhere else in your entire codebase, and quite often performance as wel

1 Like

That article is saying that many applications don't need multi-threaded execution of child futures, and the spawn() function is designed to give you that multi-threaded execution (hence the Send + 'static requirement), and so spawn is more restrictive on the provided future than necessary for those applications, and requires them to pay the cost of using thread-safe (synchronized) types where that could be avoided instead.

The point isn't that spawn() is broken, but that it's a heavyweight thing where a lightweight thing could be used instead (under certain assumptions).

2 Likes

Where does one even begin with Rust Async? Suggestions.

Now that's a big question. What's your background? How much Rust are you familiar with at this point?

My own learning path was through a number of other languages, including both Python and Javascript; by the time I hit Rust's async tools, the ideas behind them were fairly familiar. In that context, I dove in with actix-web (though these days I prefer axum) and started building applications, learning about the underlying pieces as I went.

If I were teaching someone who doesn't have that background, I'd probably have them go through the Tokio tutorial. It solves a trivial and very made-up problem as a means to introduce basic asynchronous operations, then layers on additional complexity as you go.

The key idea behind asynchronous Rust - and most other async programming approaches - is that a program is likely to spend a lot of its running time doing nothing while waiting for things to happen. If you write to a file, for example, a synchronous program suspends entirely while that write is happening, and resumes after the write completes. That time could be put to useful ends, and async Rust gives your program (specifically, its async runtime) a way to intervene in operations that otherwise would suspend the program to do something else.

It happens that the same primitives are also useful for writing multithreaded programs, and the essay you're reading makes the point that Tokio, specifically, conflates the two problems in a way that shapes how programmers solve problems. I tend to agree, though I'm nowhere near as incendiary about as the author is, but if you're just learning: don't worry about it. It's a worthwhile point to explore, but learn the basics first.

I've written a few libraries and other items in Rust (much more with Python and React). One of which is multithreaded. Given the responses here, I can see why the books I have started want to take you deep into futures, queues, etc, as opposed to a how to with a particular library. I guess I'll keep on plodding through the early release O'Reilly book.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.