Hacker News new | past | comments | ask | show | jobs | submit login
Tokio: Runtime for writing reliable asynchronous applications with Rust (github.com/tokio-rs)
143 points by based2 on April 27, 2020 | hide | past | favorite | 58 comments



Hi! Tokio maintainer here. I'm surprised (in a good way) to see this posted to HN today. Nothing big is happening this week.

That said, I'm always happy to answer questions.


One of the things that puts me off investing in Rust day-to-day is the lack of v1 packages in areas like this. I notice I'm not alone based on the results of the annual Rust survey. What do you imagine v1 will look like for Tokio compared to today and when would you envisage it landing?


We're aiming for 1.0 by Q3 2020. I wrote a bit about it here: https://tokio.rs/blog/2019-11-tokio-0-2/#a-roadmap-to-1-0 That said, io_uring may end up impacting that target by a little bit.

I understand the sentiment re v1. In reality, Tokio v0.1 was pretty much 1.0. We never cut 1.0 because async/await was in the works and it was unclear when it would be released. Now that async/await is out, 0.2 was a big change. We need some time to stabilize our APIs and collect user feedback. This process is happening now and going well.


As someone who has dabbled in Rust, I think the Rustaceans are just too careful to bump something up to v1 before it's near perfect. In other languages the package ecosystem looks more mature because people are less careful in bumping up numbers.


You aren't wrong. In hindsight, we should have shipped 1.0 a year or so after v0.1. I don't think we can ship 2.0 now. v0.3 is going to happen soon to fix errors in v0.2. async/await in Rust is very new. We are still figuring things out.


I'm sure they look more mature, but they might leave a bad impression. Rust already has "bad PR" with the borrow checker, so libraries being more conservative before bumping versions might work in its favour. Otherwise you might get a bad impression from the language and apparently immature libraries.


For me the borrow checker is what I find interesting about rust.

I wouldn't call it bad pr.


The people talking about it generally complain about it. Those comfortable with it generally aren't talking about it. I don't know what else to call that than bad PR. Just the nature of the beast.


Have you discussed elsewhere about io_uring plans? It isn't mentioned at all in the linked roadmap


We are still figuring that out. When I wrote that post, uring was not yet working with sockets. Things have changed. We are exploring the space.


Is there anywhere I can read about the plans for io_uring integration?


BTW, Rust ecosystem has a fear of calling things 1.0. In the Rust world "1.0" often doesn't mean the first stable release, but more like "done".

There are plenty of crates that are stable and production-ready, but with 0.x versions.

In some cases ironically authors don't want to bump the version to 1.0, because the crates are so widely used and stable, that the mere version bump would be an unnecessarily big change (e.g. the most used libc crate will likely stay as v0.2 forever).


async-std, their main competitor, is 1.5.0


what's the case with async-std? Do the tokio devs have meetings with async-std regarding common problems they are facing, and is there any chance of merging those 2 projects, since in my humble opinion, seems like they solve the same problem pretty much ?

edit: my question may not be formatted correctly, but basically I am asking whether there is cooperation between the 2 projects, or they are competing each other


Great question. I'm not thinking about competing. All I care about is users and building a great library. I think the best way to advance the state of the ecosystem is by experimenting and shipping improvements. IMO Ideas are best shared as working code.

The Tokio team has been very active on that front. Recently, we've shipped a new strategy to improve the cooperative scheduler (https://tokio.rs/blog/2020-04-preemption/), and a bunch of other new utilities (https://github.com/tokio-rs/tokio/releases/tag/tokio-0.2.12, https://github.com/tokio-rs/tokio/releases/tag/tokio-0.2.11).

Standardization can be extracted once proven. This is how `std::future` came to be.


The purpose of tokio was to add asynchronous support to Rust in the absence of async features (it predates them). It has since added std-like async interfaces. It is the most mature.

async-std aims to do that last sentence as cleanly as possible, without having to support a legacy interface. I found it builds faster because of that and that's why I chose it.

smol (the new kid in the block) aims to async-ify network types by simply wrapping them in Async<T>. It also aims to be as few lines of code as possible.

They are all different approaches to the same problem. The ability to experiment with different approaches is precisely why Rust doesn't have a prescribed async runtime (or prescribed error types, or many other things).

If merely solving the problem is all you care about, pick tokio. It should be able to use async-std crates, but async-std can't use it. smol can use anything but is a bit unproven.


Hi there, thought I might chime in as someone who used to work on tokio and is now working on async-std and smol. :)

A month ago I wrote a blog post about the evolution of async Rust and its async runtimes. Hopefully this answers your questions!

https://stjepang.github.io/2020/04/03/why-im-building-a-new-...


Why is "does not depend on mio" a desirable feature? Do you find mio too complex? Or are you just trying to minimize dependence on other crates on principle for this project?


I'm a Rust noob, and I don't understand the relationship of Tokio and the language's async/await syntax. Is there a "reference implementation" of the runtime? Is Tokio or another runtime required for those keywords to do anything?

I also found this blurb from the website to be confusing:

> Zero-cost abstractions Tokio's run-time model adds no overhead compared to an equivalent system written entirely by hand.

This sounds like Tokio's impl is comparable to other impl's of "the same" system, but how does it compare to "naked Rust"?


> and I don't understand the relationship of Tokio and the language's async/await syntax.

One of the surprising things with rust here, is that it define only the syntax and some bare traits AND ALLOW TO PLUG ANY RUNTIME.

This mean you can "swap" runtime implementations and even build your own.

One recent example is:

https://www.reddit.com/r/rust/comments/g917ad/smol_stjepang_...

This is in contrast with Go/Erlang/.NET, etc where the runtime for it is made for you, but can't be changed. This is cool where things are mostly fine all the time, but rust being a SYSTEM lang allow this is neat (example: Embebed scenarios where a regular runtime is too heavy).

Note: This template show in other places, where for example you can swap the allocator or the (default) hash function.


I should also note, for balance, that this isn't perfect today. There are still some missing bits to make it truly "plug anything anywhere," but it's pretty close.


The main thing I don't understand about Rust's async/await situation is how it's not very inefficient on the CPU given that it fundamentally uses polling? https://doc.rust-lang.org/std/future/trait.Future.html


The model is very similar to how epoll works. The executor does not poll futures in a loop. Between polls, the executor waits for a readiness notification.

If `Future::poll` returns Pending, once it becomes ready to do more work, it notifies the executor, the executor then schedules the future to be polled again.


It doesn't poll all the time. The context parameter contains a walker, and you have to tell the executor to wake you up using that waker when you are ready to continue.


I suspect that a big part of the design of this api (and why it take time) was exactly to be as performant as possible.

The compiler desugar the async/await to state machines and that is melted away by regular means. Also, the quality of the runtime elected must take some credit.


Was Python the first language to do async that way? I know within the Python ecosystem you've got asyncio, Trio, Curio, etc.


Active Oberon, Ada and C++ are system languages and have default implementations as part of the standard.


I don't know about the others, but C++ don't have a default implementation as part of the standard. If you want an event loop, you need to use one of the many libraries that provide one.


C++20 surely has one.


No, it hasn't.

Actually C++20 comes with only the minimal support for coroutines in the std library. If you want to do anything useful, you need to implement a promise type yourself which is very hard to do without using one library that does that.


From the looks of it, and what I am already able to do with Visual C++ I bet it still is more functional than what Rust currently offers.

And that I won't need to rewrite it when I try out the same code with another C++20 compiler.


So it's sort of like the javax API?


"system" here is intended to refer to the application using tokio, i.e. if you got rid of tokio and did everything by hand you wouldn't be able to make your application faster.


Ahhh; thank you!


The standard library currently only contains what is necessary to define _what_ a Future is. It contains no tools for actually executing them, and this is the role of Tokio.


Very cool, so I can choose my own impl. Does Tokio have a true competitor today? From my cursory look at some projects, Tokio shows up everywhere.


There are some other executors too such as async-std, smol and bastion, but Tokio is the one with the largest ecosystem.


There's even been experiments with binding futures to glib or various GUI event loops!


I have also heard some talk on building executors for embedded devices where you can't allocate memory on the heap.


Yes, this was a big constraint on the design of the overall system; there's been a couple that have popped up.


How's async looking in Rust these days? About a year ago I wrote one service that's in production. I ended up doing a lot of the futures stuff by hand. Never really understood how the error mapping/conversions worked, and usually just fiddled with them until it compiled. I remember the docs being decent, but there wasn't a single book/site that had everything in a comprehensive and approachable manner. In spite of all that, the service has chugged along pretty nicely.

Ultimately I've just been more productive in Go for the time being, but I remember definitely see the potential down the road for great things.

Has async/await permeated the ecosystem? Is the book more fleshed out?


It gets better every day. async/await is relatively new (stabilized end of last year). All the misc libs in the Tokio stack have been updated. There is Tonic for gRPC, Hyper/reqwest/warp for HTTP, ... these all work with async/await now.

For docs, there are some now and I expect it to improve a lot throughout the year.

We also just posted mini-redis as a larger "real world" example: https://github.com/tokio-rs/mini-redis


Very nice. I used warp (awesome) for my service. Maybe I should update it to use async/await as an exercise.


It's better but it suffers from the same poison aspect all async/await implementations do.

https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...


Can't say I find that argument all that compelling. Using the logic from that article, functions that perform side-effects have a different colour from those that don't, therefore we should prevent all functions with side-effects to enable better interoperation? I suspect the author and most other programmers might have some disagreements with their new monadic overlords.

The point is that "colour" is introduced to make a meaningful distinction in order to achieve a desirable property. He's enamored by Go which erases this distinction, but it does so by giving up nearly all of the benefits of the event-driven model: very small captured state for resuming the continuation.


> Using the logic from that article, functions that perform side-effects have a different colour from those that don't, therefore we should prevent all functions with side-effects to enable better interoperation?

The argument is the reverse: it's too hard to explicitly manage side effects, so we should make our functions implicitly, pervasively side-effecting. E.g. allowing any function to throw an exception without declaring this in their signature is widely agreed to be a better approach than Java-style checked exceptions; the argument goes that we should also allow any function to be async without declaring this in its signature.

(I think there's some merit to the argument in languages that aren't powerful enough to express functions that are polymorphic over async-ness. Personally I use languages with higher-kinded types and then you get the best of both worlds: you have an explicit distinction between async and not, but you can write functions that work with both)


I very much disagree that it does. The core pitfalls are pretty specific to JS, and don't exist in Rust.

In Rust you can easily call an "async colored" function from a "sync colored" one by running it to completion with the executor of your choice. With that you can prevent async bleeding all over your codebase.


Yes. Engineering is all about tradeoffs. This is a downside, but it is made up for by all the upsides.


I heard that Zig sidesteps this, but I'm not aware of the details.


Was there a new release? Just raising awareness of it's existence?


legit name


[flagged]


While this is true, they are spelled differently.


This assembly programmer wants to know what bare-metal performance means in a HLL.


Tokio should not add any overhead compared to writing an equivalent by hand with no abstractions.

I'm a programmer, not a marketer :) Happy to take suggestions on the copy.


The same that an Assembly language that gets translated into CPU micro-ops and executed via microcode.


Just nitpicking, but that's not how a modern CPU works. The microcode does not execute the micro-ops, the microcode is used to generate micro-ops to be executed, and in fact most instructions don't even go through the microcode, which is used only for complex or rarely used instructions. Simpler instructions are decoded directly into micro-ops.


No VM, no GC. Mostly zero cost abstractions.


Yes, it’s a strange phrase. Especially as we tend to execute software on silicon crystals, etched by light. The metal is just the dumb wiring.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: