Futures in Rust 134 I/O vs CPU-intensive tasks As you know now, what you normally write are called non-leaf futures. Let's take a look at this async block using pseudo-Rust as an example: let non_leaf = async { let mut stream = Tcp Stream::connect("127. 0. 0. 1:3000"). await. unwrap(); // request a large dataset let result = stream. write(get_dataset_request). await. unwrap(); // wait for the dataset let mut response = vec![]; stream. read(&mut response). await. unwrap(); // do some CPU-intensive analysis on the dataset let report = analyzer::analyze_data(response). unwrap(); // send the results back stream. write(report). await. unwrap(); }; I've highlighted the points where we yield control to the runtime executor. It's important to be aware that the code we write between the yield points runs on the same thread as our executor. That means that while our analyzer is working on the dataset, the executor is busy doing calculations instead of handling new requests. Fortunately, there are a few ways to handle this, and it's not difficult, but it's something you must be aware of: 1. We could create a new leaf future, which sends our task to another thread and resolves when the task is finished. We could await this leaf-future like any other future. 2. The runtime could have some kind of supervisor that monitors how much time different tasks take and moves the executor itself to a different thread so it can continue to run even though our analyzer task is blocking the original executor thread. 3. Y ou can create a reactor yourself that is compatible with the runtime, which does the analysis any way you see fit and returns a future that can be awaited. Now, the first way is the usual way of handling this, but some executors implement the second method as well. The problem with #2 is that if you switch runtime, you need to make sure that it supports this kind of supervision as well or else you will end up blocking the executor.