There are sync rust multithreading libs like rayon that are a joy to use, and there are even blocking versions of popular http libraries like reqwest: https://docs.rs/reqwest/latest/reqwest/blocking/index.html
They are usually doing some ugly stuff internally to make this work, but as a pure library user you don't have to care.
If you want to write something small that e.g. pulls some data via http, performs some computation, then pushes the result, it is totally possible to do this fully in sync rust.
And if you are writing a highly concurrent web server, looking into async might not be the worst idea.
Ideally library authors should provide a sync facade so people don't have to deal with this, like reqwest does.
It’s repetitive but it’s simple.
Also, the other way around might be simpler. If the library wants to be async, make it async-only, and let callers block_on it. https://docs.rs/tokio/latest/tokio/runtime/struct.Runtime.ht...
I find the Go's approach of "everything is synchronous, except when made asynchronous with the 'go' keyword" much more pleasant and much less error prone. Is there a Rust library that does this - that provides coroutine-style asychronous behavior, but with synchronous Go-like syntax?
That means that the interop story for golang is horrible. Golang can somewha work with libraries with C bindings. But you can not publish a golang lib as a lib.so with C bindings because of the runtime.
You can certainly publish lib.so with Go, here is an example,
https://www.ardanlabs.com/blog/2020/07/extending-python-with...
Just because Go has a runtime and has a go keyword, doesn't in itself mean that runtime is necessary for the go keyword to exist.
You could argue that all go code is async (in the Rust sense), and because of this uniformity, there’s no need for the syntactic distinction that Rust requires you to make.
Rust could have done the same (and at one point in time they did have green threads), Rust decided that the convenience to Rust programmers wasn’t worth the cost elsewhere (the inclusion of a runtime, performance overhead, C interop, etc).
Then you will have some ugliness at the boundary. You might get by with just spawning a current thread runtime to use your async lib, or do something like blocking channels if your low level library spawns long lived tasks.
If your low level libraries are all sync, then there won't be much ugliness.
Which dependency is forcing async Rust on you? Alternatives are usually available.
Middleware/library writers that touch on anything that could be async (db, SPI, network etc.) will now have to write two versions of their API and duplicate most code.
But in general the dependency story with Rust is better than with C++. I don't miss the integration story with C++; just bringing in a dep in the first place is a roll of the dice on whether it's going to work with your build system. And then whether it brings with it some other lifestyle assumptions (exceptions, some third party deps you don't want or can't link to, work with your compiler or not, etc.)
Honestly, I work in systems & embedded stuff, and it's not hard to avoid async in Rust. It's only once you start poking at web-adjacent stuff that you run into the wall, and then when you do you find yourself covered in the taint of people who brought their NodeJS Stockholm Syndrome over to Rust, but that's another rant...
Nowadays it is impensable to do a Java project without Maven/Gradle, yet it took about 10 years for Ant to be relevant (counting from 1996), and couple more for Maven, and yet another few for Gradle.
Similarly with NuGET and MSBuild evolution.
Yet they were eventually adopted, same is happening with vcpkg/conan.
tokio::spawn_blocking[1], see it's not that hard. But sure to use another language you must learn it, an it requires a bit if effort…
[1] assuming you want to use tokio like post people do, but other executors should have the same kind of functions to do that as well if need be.
You could argue that this mismatch exists even with rust itself, where you have Drop, which is sync, and you might have to do something in your drop that requires async, like closing a network connection.
You have to pass in some runtime handle that you can use to spawn a task to do what you have to do. This is definitely not simple and beginner friendly. Or even worse - say goodbye to RAII and tell people that they have to explicitly call an async shutdown fn.
Since when has it become acceptable in rust to have fns that just panic depending on the state of some thread local? That is one thing I really dislike about tokio.
The thread local runtime is convenience over correctness, which is antithetical to what rust usually favours.
tokio::spawn_blocking doesn't exist in my codebase because I'm not using tokio, nor should I have to in order to use library code from a crate.
This is the problem with async in a nutshell; it's viral, and it brings with it a lot of lifestyle assumptions. Assumptions that I think people who are used to working in the web framework world are maybe comfortable with, but which should not have been allowed to spread to the crates ecosystem broadly.
async is fine as an implementation option for your service or binary; but its propagation into library code means now people using that library are tied into requiring a framework to host it with. And this is frankly just bad hygiene, and in a way seems contrary to Rust philosophy generally.
Most crate writers are polite enough to offer async and non-async bundling of their code. But on more than one occasion recently I've run into dependencies that did not do this, or did not do this thoroughly (offering sync versions with less features, for example).
If you're trying to use a library that uses Futures, then you need an executor to poll that futures to completion. But then it's not the same situation as the one I'm responding to (where it was about using blocking code in an async context).
If you have blocking code and need a way to poll third-party futures to completion then it's tokio::block_on or equivalent that you need.
> This is the problem with async in a nutshell; it's viral
That's the biggest misunderstanding about async/await: it's not async that's viral, it's IO. If your library does IO, then you do IO and have to deal with the fact. And you have to actively deal with it no matter what, because IO is slow and you definitely do not want your UI thread be frozen[1] because something under the hood is doing IO. The only difference is that async makes it explicit in the typesystem, where blocking does not and you need to guess where to spawn new threads.
[1] or your thread that's listening on incomming sockets.
But it's also a concern for me that a language feature was added that requires runtime framework support to function. This is bad separation of concerns.
It (async) is viral syntactic sugar that doesn't need to exist; asynchronous programming has been done for decades without it.