Concurrency in Rust is not tied to a particular threading module, and may not even be tied to threads at all. It is up to the programmer to choose how to concurrently execute their tasks.
Using futures, it is possible to join multiple futures together so that they execute concurrently from the same thread. When one future is blocked, the next future in the queue will be executed.
// Concurrently execute two tasks on the current threadlet(result1,result2)=join!(future1,future2);// Concurrently execute tasks with a common error type.letresult=try_join!(future1,future2,future3);
It's also possible to distribute them across a M:N thread pool, and interchangeably mix and match both approaches. Spawning a future will schedule it for execution on your executor. The executor may be based on a thread pool, or run on the same thread. Depending on which you choose, they may have Sync / Send restrictions.
task::block_on(asyncmove{// Each spawn returns a JoinHandle future to the result.letfuture1=task::spawn(future1);letfuture2=task::spawn(future2);letfuture3=task::spawn(future3);// Concurrently wait for all three threads to complete.let(result1,result2,result3)=join!(future1,future2,future3);});
Often times there's two different pools to spawn tasks to: non-blocking thread pools, and blocking thread pools. Tasks which block should be spawned on blocking pools so that they avoid blocking tasks on the non-blocking thread pool. The async-std crate provides spawn_blocking for that.
Without futures, you may use crates like rayon to distribute blocking tasks across a thread pool. This used to be the preferred threading model before the introduction of async/await with futures.
There is an issue with your assertion that functions aren't first class. You can accept both functions and closures as input arguments, and return functions and closures as output arguments. Generics is required for returning closures, however, because closures are dynamically-created types.
Concurrency in Rust is not tied to a particular threading module, and may not even be tied to threads at all. It is up to the programmer to choose how to concurrently execute their tasks.
Using futures, it is possible to join multiple futures together so that they execute concurrently from the same thread. When one future is blocked, the next future in the queue will be executed.
It's also possible to distribute them across a M:N thread pool, and interchangeably mix and match both approaches. Spawning a future will schedule it for execution on your executor. The executor may be based on a thread pool, or run on the same thread. Depending on which you choose, they may have
Sync
/Send
restrictions.Often times there's two different pools to spawn tasks to: non-blocking thread pools, and blocking thread pools. Tasks which block should be spawned on blocking pools so that they avoid blocking tasks on the non-blocking thread pool. The
async-std
crate providesspawn_blocking
for that.Without futures, you may use crates like
rayon
to distribute blocking tasks across a thread pool. This used to be the preferred threading model before the introduction of async/await with futures.There is an issue with your assertion that functions aren't first class. You can accept both functions and closures as input arguments, and return functions and closures as output arguments. Generics is required for returning closures, however, because closures are dynamically-created types.
Yes, concurrency in Rust is quite interesting