Golang Goroutines/Channels vs Rust Async/Await

Hello everyone. I am a (mostly) Golang programmer trying to dabble into Rust.
I am very much familiar with Go Goroutines/Channels concept for multithreading and such. However, Rust does not seem to follow this pattern. Instead, it follows the Async/Await model of JS. This got me confused

  1. Can somebody do an ELI5 for me on the main/biggest/fundamental difference between the two paradigms, at least in term of implementation in Golang and Rust. Their pros/cons...etc.
  2. How do I execute an async function in a non-async function? Or is my programming paradigm is wrong with Rust?
    For example, I want to run this function from a crate public_ip to, well, get IP.
async fn get_ip() {
    // Attempt to get an IP address and print it.
    if let Some(ip) = public_ip::addr().await {
        println!("public ip address: {:?}", ip);
    } else {
        println!("couldn't get an IP address");
    }
}

If I throw this function into my main, for sure I will come across:

 fn main() {
   |    ---- this is not `async`
21 |     get_ip.await;
   |           ^^^^^^ only allowed inside `async` functions and blocks

Do I need to turn the main function into async? Do I need to put and async to function main? Does that make my entire Rust code become asynchronous, like Javascript? Because honestly I am not that familiar with asynchronous programming.
3. Finally, why this way? Like why did Rust choose the paradigm of Async/Await instead of multithreading like Golang? I tried to search for a few Webs Framework, and fundamentally, as far as I can understand, Actix Web, Rocket, Warp and Tide... all developed under the Async/Await pattern, so that makes me wonder is there a really deep reason why Rust was developed this way. And also, is there any Web Framework that follows multithreads paradigm like that of Go?
Thanks in advance.

First things first: You don't have to use async/await to use Rust in a multi-threaded setting. You can simply call std::thread::spawn to make a new thread, and it will run in parallel. But, let's talk about async/await:

In general, the primary difference between what Go does and what async Rust does is whether the scheduling is preemptive or cooperative. To understand what this means, I would encourage you to read this article, which explains what consequences it has that Rust uses cooperative scheduling in async code. The very short version is that in async Rust, your runtime (usually Tokio) will swap out the currently running task on each worker thread whenever it reaches an .await and has to wait for IO or something, but the swap can only happen at an .await. In Go, this is different: The scheduling is preemptive, which means that a task can be swapped at any time. Read this blog post for some more discussion on this difference.

How do I execute an async function in a non-async function?

Tokio provides a function called block_on, which takes an asynchronous future and runs it, then returns its output. However it's important to note that block_on only works outside of a runtime — you can't do stuff like being in an async fn, calling a non-async fn, then calling block_on there, because you're still inside a runtime.

Do I need to put and async to function main ? Does that make my entire Rust code become asynchronous, like Javascript?

Tokio provides the #[tokio::main] macro which you can put on your main function to make it asynchronous. The macro basically just creates a new runtime and calls the block_on function I mentioned above (see it's documentation for the exact expansion).

However you don't have to make your entire project asynchronous if you don't want to. The Tokio website has a page explaining how to isolate async to a small part of your project: Bridging with sync code

Finally, why this way? Like why did Rust choose the paradigm of Async/Await instead of multithreading like Golang?

I think the question here is wrong. It's best to think of Go as a language where everything is async all the time, and it is better to compare Go to async Rust than it is to compare Go to non-async Rust.

In general, the reason that async/await works the way it does is that Rust is that Rust wants to be able to work in settings where everything being async is not acceptable. Therefore async has to be explicit in your code, as opposed to just having everything be implicitly async like what Go does.'

It's true that the async/await syntax in Rust is visually similar to JS, but async Rust really is closer to Go than it is to JS.

6 Likes