From a3bd2db0ab0cae179589bb5e57e3eea8a3316949 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 13:57:47 -0800 Subject: [PATCH 1/7] Shuffle Table of contents - Move tutorial overview page which had tokio general information but no tutorial specific info to /tokio/introduction - Move tutorial setup page to /tokio/tutorial from /tutorial/setup and add a redirect for seo-juice. --- content/tokio/introduction.md | 120 ++++++++++++++++++++ content/tokio/tutorial/index.md | 187 ++++++++++++++------------------ content/tokio/tutorial/setup.md | 92 ---------------- netlify.toml | 5 + pages/[...slug].tsx | 2 +- 5 files changed, 206 insertions(+), 200 deletions(-) create mode 100644 content/tokio/introduction.md delete mode 100644 content/tokio/tutorial/setup.md diff --git a/content/tokio/introduction.md b/content/tokio/introduction.md new file mode 100644 index 00000000..071baae9 --- /dev/null +++ b/content/tokio/introduction.md @@ -0,0 +1,120 @@ +--- +title: "Introduction" +subtitle: "Overview" +--- + +Tokio is an asynchronous runtime for the Rust programming language. It provides +the building blocks needed for writing networking applications. It gives the +flexibility to target a wide range of systems, from large servers with dozens of +cores to small embedded devices. + +At a high level, Tokio provides a few major components: + +- A multi-threaded runtime for executing asynchronous code. +- An asynchronous version of the standard library. +- A large ecosystem of libraries. + +# Tokio's role in your project + +When you write your application in an asynchronous manner, you enable it to +scale much better by reducing the cost of doing many things at the same time. +However, asynchronous Rust code does not run on its own, so you must choose a +runtime to execute it. The Tokio library is the most widely used runtime, +surpassing all other runtimes in usage combined. + +Additionally, Tokio provides many useful utilities. When writing asynchronous +code, you cannot use the ordinary blocking APIs provided by the Rust standard +library, and must instead use asynchronous versions of them. These alternate +versions are provided by Tokio, mirroring the API of the Rust standard library +where it makes sense. + +# Advantages of Tokio + +This section will outline some advantages of Tokio. + +## Fast + +Tokio is _fast_, built on top of the Rust programming language, which itself is +fast. This is done in the spirit of Rust with the goal that you should not be +able to improve the performance by writing equivalent code by hand. + +Tokio is _scalable_, built on top of the async/await language feature, which +itself is scalable. When dealing with networking, there's a limit to how fast +you can handle a connection due to latency, so the only way to scale is to +handle many connections at once. With the async/await language feature, +increasing the number of concurrent operations becomes incredibly cheap, +allowing you to scale to a large number of concurrent tasks. + +## Reliable + +Tokio is built using Rust, which is a language that empowers everyone +to build reliable and efficient software. A [number][microsoft] of +[studies][chrome] have found that roughly ~70% of high severity security bugs +are the result of memory unsafety. Using Rust eliminates this entire class of +bugs in your applications. + +Tokio also focuses heavily on providing consistent behaviour with no surprises. +Tokio's major goal is to allow users to deploy predictable software that will +perform the same day in and day out with reliable response times and no +unpredictable latency spikes. + +[microsoft]: https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/ +[chrome]: https://www.chromium.org/Home/chromium-security/memory-safety + +## Easy + +With Rust's async/await feature, the complexity of writing asynchronous +applications has been lowered substantially. Paired with Tokio's utilities and +vibrant ecosystem, writing applications is a breeze. + +Tokio follows the standard library's naming convention when it makes sense. This +allows easily converting code written with only the standard library to code +written with Tokio. With the strong type system of Rust, the ability to deliver +correct code easily is unparalleled. + +## Flexible + +Tokio provides multiple variations of the runtime. Everything from a +multi-threaded, [work-stealing] runtime to a light-weight, single-threaded +runtime. Each of these runtimes come with many knobs to allow users to tune them +to their needs. + +[work-stealing]: https://en.wikipedia.org/wiki/Work_stealing + +# When not to use Tokio + +Although Tokio is useful for many projects that need to do a lot of things +simultaneously, there are also some use-cases where Tokio is not a good fit. + +- Speeding up CPU-bound computations by running them in parallel on several + threads. Tokio is designed for IO-bound applications where each individual + task spends most of its time waiting for IO. If the only thing your + application does is run computations in parallel, you should be using + [rayon]. That said, it is still possible to "mix & match" + if you need to do both. See [this blog post for a practical example][rayon-example]. +- Reading a lot of files. Although it seems like Tokio would be useful for + projects that simply need to read a lot of files, Tokio provides no advantage + here compared to an ordinary threadpool. This is because operating systems + generally do not provide asynchronous file APIs. +- Sending a single web request. The place where Tokio gives you an advantage is + when you need to do many things at the same time. If you need to use a + library intended for asynchronous Rust such as [reqwest], but you don't need + to do a lot of things at once, you should prefer the blocking version of that + library, as it will make your project simpler. Using Tokio will still work, + of course, but provides no real advantage over the blocking API. If the + library doesn't provide a blocking API, see [the chapter on + bridging with sync code][bridging]. + +[rayon]: https://docs.rs/rayon/ +[rayon-example]: https://ryhl.io/blog/async-what-is-blocking/#the-rayon-crate +[reqwest]: https://docs.rs/reqwest/ +[bridging]: /tokio/topics/bridging + +# Getting Help + +At any point, if you get stuck, you can always get help on [Discord] or [GitHub +discussions][disc]. Don't worry about asking "beginner" questions. We all start +somewhere and are happy to help. + +[discord]: https://discord.gg/tokio +[disc]: https://github.com/tokio-rs/tokio/discussions diff --git a/content/tokio/tutorial/index.md b/content/tokio/tutorial/index.md index 36898235..d2470718 100644 --- a/content/tokio/tutorial/index.md +++ b/content/tokio/tutorial/index.md @@ -1,114 +1,23 @@ --- title: "Tutorial" -subtitle: "Overview" +subtitle: "Setup" --- -Tokio is an asynchronous runtime for the Rust programming language. It provides -the building blocks needed for writing networking applications. It gives the -flexibility to target a wide range of systems, from large servers with dozens of -cores to small embedded devices. - -At a high level, Tokio provides a few major components: - - - A multi-threaded runtime for executing asynchronous code. - - An asynchronous version of the standard library. - - A large ecosystem of libraries. - -# Tokio's role in your project - -When you write your application in an asynchronous manner, you enable it to -scale much better by reducing the cost of doing many things at the same time. -However, asynchronous Rust code does not run on its own, so you must choose a -runtime to execute it. The Tokio library is the most widely used runtime, -surpassing all other runtimes in usage combined. - -Additionally, Tokio provides many useful utilities. When writing asynchronous -code, you cannot use the ordinary blocking APIs provided by the Rust standard -library, and must instead use asynchronous versions of them. These alternate -versions are provided by Tokio, mirroring the API of the Rust standard library -where it makes sense. - -# Advantages of Tokio - -This section will outline some advantages of Tokio. - -## Fast - -Tokio is _fast_, built on top of the Rust programming language, which itself is -fast. This is done in the spirit of Rust with the goal that you should not be -able to improve the performance by writing equivalent code by hand. - -Tokio is _scalable_, built on top of the async/await language feature, which -itself is scalable. When dealing with networking, there's a limit to how fast -you can handle a connection due to latency, so the only way to scale is to -handle many connections at once. With the async/await language feature, -increasing the number of concurrent operations becomes incredibly cheap, -allowing you to scale to a large number of concurrent tasks. - -## Reliable - -Tokio is built using Rust, which is a language that empowers everyone -to build reliable and efficient software. A [number][microsoft] of -[studies][chrome] have found that roughly ~70% of high severity security bugs -are the result of memory unsafety. Using Rust eliminates this entire class of -bugs in your applications. - -Tokio also focuses heavily on providing consistent behaviour with no surprises. -Tokio's major goal is to allow users to deploy predictable software that will -perform the same day in and day out with reliable response times and no -unpredictable latency spikes. - -[microsoft]: https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/ -[chrome]: https://www.chromium.org/Home/chromium-security/memory-safety - -## Easy - -With Rust's async/await feature, the complexity of writing asynchronous -applications has been lowered substantially. Paired with Tokio's utilities and -vibrant ecosystem, writing applications is a breeze. - -Tokio follows the standard library's naming convention when it makes sense. This -allows easily converting code written with only the standard library to code -written with Tokio. With the strong type system of Rust, the ability to deliver -correct code easily is unparalleled. - -## Flexible - -Tokio provides multiple variations of the runtime. Everything from a -multi-threaded, [work-stealing] runtime to a light-weight, single-threaded -runtime. Each of these runtimes come with many knobs to allow users to tune them -to their needs. - -[work-stealing]: https://en.wikipedia.org/wiki/Work_stealing - -# When not to use Tokio - -Although Tokio is useful for many projects that need to do a lot of things -simultaneously, there are also some use-cases where Tokio is not a good fit. - - - Speeding up CPU-bound computations by running them in parallel on several - threads. Tokio is designed for IO-bound applications where each individual - task spends most of its time waiting for IO. If the only thing your - application does is run computations in parallel, you should be using - [rayon]. That said, it is still possible to "mix & match" - if you need to do both. See [this blog post for a practical example][rayon-example]. - - Reading a lot of files. Although it seems like Tokio would be useful for - projects that simply need to read a lot of files, Tokio provides no advantage - here compared to an ordinary threadpool. This is because operating systems - generally do not provide asynchronous file APIs. - - Sending a single web request. The place where Tokio gives you an advantage is - when you need to do many things at the same time. If you need to use a - library intended for asynchronous Rust such as [reqwest], but you don't need - to do a lot of things at once, you should prefer the blocking version of that - library, as it will make your project simpler. Using Tokio will still work, - of course, but provides no real advantage over the blocking API. If the - library doesn't provide a blocking API, see [the chapter on - bridging with sync code][bridging]. - -[rayon]: https://docs.rs/rayon/ -[rayon-example]: https://ryhl.io/blog/async-what-is-blocking/#the-rayon-crate -[reqwest]: https://docs.rs/reqwest/ -[bridging]: /tokio/topics/bridging +This tutorial will take you step by step through the process of building a +[Redis] client and server. We will start with the basics of asynchronous +programming with Rust and build up from there. We will implement a subset of +Redis commands but will get a comprehensive tour of Tokio. + +# Mini-Redis + +The project that you will build in this tutorial is available as [Mini-Redis on +GitHub][mini-redis]. Mini-Redis is designed with the primary goal of learning +Tokio, and is therefore very well commented, but this also means that Mini-Redis +is missing some features you would want in a real Redis library. You can find +production-ready Redis libraries on [crates.io](https://crates.io/). + +We will use Mini-Redis directly in the tutorial. This allows us to use parts of +Mini-Redis in the tutorial before we implement them later in the tutorial. # Getting Help @@ -118,3 +27,67 @@ somewhere and are happy to help. [discord]: https://discord.gg/tokio [disc]: https://github.com/tokio-rs/tokio/discussions + +# Prerequisites + +Readers should already be familiar with [Rust]. The [Rust book][book] is an +excellent resource to get started with. + +While not required, some experience with writing networking code using the [Rust +standard library][std] or another language can be helpful. + +No pre-existing knowledge of Redis is required. + +[rust]: https://rust-lang.org +[book]: https://doc.rust-lang.org/book/ +[std]: https://doc.rust-lang.org/std/ + +## Rust + +Before getting started, you should make sure that you have the +[Rust][install-rust] toolchain installed and ready to go. If you don't have it, +the easiest way to install it is using [rustup]. + +This tutorial requires a minimum of Rust version `1.45.0`, but the most +recent stable version of Rust is recommended. + +To check that Rust is installed on your computer, run the following: + +```bash +rustc --version +``` + +You should see output like `rustc 1.46.0 (04488afe3 2020-08-24)`. + +## Mini-Redis server + +Next, install the Mini-Redis server. This will be used to test our client as we +build it. + +```bash +cargo install mini-redis +``` + +Make sure that it was successfully installed by starting the server: + +```bash +mini-redis-server +``` + +Then, in a separate terminal window, try to get the key `foo` using `mini-redis-cli` + +```bash +mini-redis-cli get foo +``` + +You should see `(nil)`. + +# Ready to go + +That's it, everything is ready to go. Go to the next page to write your first +asynchronous Rust application. + +[redis]: https://redis.io +[mini-redis]: https://github.com/tokio-rs/mini-redis +[install-rust]: https://www.rust-lang.org/tools/install +[rustup]: https://rustup.rs/ diff --git a/content/tokio/tutorial/setup.md b/content/tokio/tutorial/setup.md deleted file mode 100644 index 437e4e4e..00000000 --- a/content/tokio/tutorial/setup.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: "Setup" ---- - -This tutorial will take you step by step through the process of building a -[Redis] client and server. We will start with the basics of asynchronous -programming with Rust and build up from there. We will implement a subset of -Redis commands but will get a comprehensive tour of Tokio. - -# Mini-Redis - -The project that you will build in this tutorial is available as [Mini-Redis on -GitHub][mini-redis]. Mini-Redis is designed with the primary goal of learning -Tokio, and is therefore very well commented, but this also means that Mini-Redis -is missing some features you would want in a real Redis library. You can find -production-ready Redis libraries on [crates.io](https://crates.io/). - -We will use Mini-Redis directly in the tutorial. This allows us to use parts of -Mini-Redis in the tutorial before we implement them later in the tutorial. - -# Getting Help - -At any point, if you get stuck, you can always get help on [Discord] or [GitHub -discussions][disc]. Don't worry about asking "beginner" questions. We all start -somewhere and are happy to help. - -[discord]: https://discord.gg/tokio -[disc]: https://github.com/tokio-rs/tokio/discussions - -# Prerequisites - -Readers should already be familiar with [Rust]. The [Rust book][book] is an -excellent resource to get started with. - -While not required, some experience with writing networking code using the [Rust -standard library][std] or another language can be helpful. - -No pre-existing knowledge of Redis is required. - -[rust]: https://rust-lang.org -[book]: https://doc.rust-lang.org/book/ -[std]: https://doc.rust-lang.org/std/ - -## Rust - -Before getting started, you should make sure that you have the -[Rust][install-rust] toolchain installed and ready to go. If you don't have it, -the easiest way to install it is using [rustup]. - -This tutorial requires a minimum of Rust version `1.45.0`, but the most -recent stable version of Rust is recommended. - -To check that Rust is installed on your computer, run the following: - -```bash -$ rustc --version -``` - -You should see output like `rustc 1.46.0 (04488afe3 2020-08-24)`. - -## Mini-Redis server - -Next, install the Mini-Redis server. This will be used to test our client as we -build it. - -```bash -$ cargo install mini-redis -``` - -Make sure that it was successfully installed by starting the server: - -```bash -$ mini-redis-server -``` - -Then, in a separate terminal window, try to get the key `foo` using `mini-redis-cli` - -```bash -$ mini-redis-cli get foo -``` - -You should see `(nil)`. - -# Ready to go - -That's it, everything is ready to go. Go to the next page to write your first -asynchronous Rust application. - -[redis]: https://redis.io -[mini-redis]: https://github.com/tokio-rs/mini-redis -[install-rust]: https://www.rust-lang.org/tools/install -[rustup]: https://rustup.rs/ diff --git a/netlify.toml b/netlify.toml index cf5d72b3..b10fb130 100644 --- a/netlify.toml +++ b/netlify.toml @@ -29,6 +29,11 @@ NETLIFY_NEXT_PLUGIN_SKIP = "true" from = "/docs/overview" to = "/tokio/tutorial" +# Previous setup steps were moved to the main tutorial page +[[redirects]] +from = "/tokio/tutorial/setup" +to = "/tokio/tutorial" + # Redirect previous rss feed location [[redirects]] from = "/_next/static/feed.xml" diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx index f40c54f8..96d25b45 100644 --- a/pages/[...slug].tsx +++ b/pages/[...slug].tsx @@ -5,9 +5,9 @@ const menu = { tokio: { title: "Tokio", nested: { + introduction: {}, tutorial: { nested: [ - "setup", "hello-tokio", "spawning", "shared-state", From ee90cd07860d28497783d72a4db76dd88dc55d82 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 14:25:26 -0800 Subject: [PATCH 2/7] Bump rust version requirement to 1.70. This is the MSRV of latest Tokio release. Added more up to date rust version number. --- content/tokio/tutorial/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/tokio/tutorial/index.md b/content/tokio/tutorial/index.md index d2470718..28ea3152 100644 --- a/content/tokio/tutorial/index.md +++ b/content/tokio/tutorial/index.md @@ -48,7 +48,7 @@ Before getting started, you should make sure that you have the [Rust][install-rust] toolchain installed and ready to go. If you don't have it, the easiest way to install it is using [rustup]. -This tutorial requires a minimum of Rust version `1.45.0`, but the most +This tutorial requires a minimum of Rust version `1.70.0`, but the most recent stable version of Rust is recommended. To check that Rust is installed on your computer, run the following: @@ -57,7 +57,7 @@ To check that Rust is installed on your computer, run the following: rustc --version ``` -You should see output like `rustc 1.46.0 (04488afe3 2020-08-24)`. +You should see output like `rustc 1.81.0 (eeb90cda1 2024-09-04)`. ## Mini-Redis server From 08574b44d06a1bbc4ad6c8eafb9581fa54c51ee9 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 15:40:22 -0800 Subject: [PATCH 3/7] Extract async and feature-flags from hello-tokio Move these parts of the article out of the hello-tokio article into their own standalone topics. This way they don't break the flow of the tutorial, and can be referenced and linked outside of that flow more easily. Rewrote some of the parts of the hello-world for clarity and to update versions. Added more internal links and conclusion section. Related to: https://github.com/tokio-rs/website/issues/401 --- content/tokio/topics/async.md | 134 ++++++++++++++++ content/tokio/topics/feature-flags.md | 17 ++ content/tokio/tutorial/hello-tokio.md | 213 +++++++++----------------- pages/[...slug].tsx | 2 + 4 files changed, 227 insertions(+), 139 deletions(-) create mode 100644 content/tokio/topics/async.md create mode 100644 content/tokio/topics/feature-flags.md diff --git a/content/tokio/topics/async.md b/content/tokio/topics/async.md new file mode 100644 index 00000000..5b868c36 --- /dev/null +++ b/content/tokio/topics/async.md @@ -0,0 +1,134 @@ +--- +title: Asynchronous Programming +--- + +# What is Asynchronous Programming? + +Most computer programs are executed in the same order in which they are written. +The first line executes, then the next, and so on. With synchronous programming, +when a program encounters an operation that cannot be completed immediately, it +will block until the operation completes. For example, establishing a TCP +connection requires an exchange with a peer over the network, which can take a +sizeable amount of time. During this time, the thread is blocked. + +With asynchronous programming, operations that cannot complete immediately are +suspended to the background. The thread is not blocked, and can continue running +other things. Once the operation completes, the task is unsuspended and continues +processing from where it left off. Our example from before only has one task, so +nothing happens while it is suspended, but asynchronous programs typically have +many such tasks. + +Although asynchronous programming can result in faster applications, it often +results in much more complicated programs. The programmer is required to track +all the state necessary to resume work once the asynchronous operation +completes. Historically, this is a tedious and error-prone task. + +# Asynchronous Functions + +Rust implements asynchronous programming using a feature called [`async/await`]. +Functions that perform asynchronous operations are labeled with the `async` +keyword. In the tutorial example, we used the `mini_redis::connect` function. It +is defined like this: + +```rust +use mini_redis::Result; +use mini_redis::client::Client; +use tokio::net::ToSocketAddrs; + +pub async fn connect(addr: T) -> Result { + // ... +# unimplemented!() +} +``` + +The `async fn` definition looks like a regular synchronous function, but +operates asynchronously. Rust transforms the `async fn` at **compile** time into +a routine that operates asynchronously. Any calls to `.await` within the `async +fn` yield control back to the thread. The thread may do other work while the +operation processes in the background. + +> **warning** +> Although other languages implement [`async/await`] too, Rust takes a unique +> approach. Primarily, Rust's async operations are **lazy**. This results in +> different runtime semantics than other languages. + +[`async/await`]: https://en.wikipedia.org/wiki/Async/await + +# Using `async/await` + +Async functions are called like any other Rust function. However, calling these +functions does not result in the function body executing. Instead, calling an +`async fn` returns a value representing the operation. This is conceptually +analogous to a zero-argument closure. To actually run the operation, you should +use the `.await` operator on the return value. + +For example, the given program + +```rust +async fn say_world() { + println!("world"); +} + +#[tokio::main] +async fn main() { + // Calling `say_world()` does not execute the body of `say_world()`. + let op = say_world(); + + // This println! comes first + println!("hello"); + + // Calling `.await` on `op` starts executing `say_world`. + op.await; +} +``` + +outputs: + +```text +hello +world +``` + +The return value of an `async fn` is an anonymous type that implements the +[`Future`] trait. + +[`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html + +# Async `main` function + +The main function used to launch the application differs from the usual one +found in most of Rust's crates. + +1. It is an `async fn` +2. It is annotated with `#[tokio::main]` + +An `async fn` is used as we want to enter an asynchronous context. However, +asynchronous functions must be executed by a [runtime]. The runtime contains the +asynchronous task scheduler, provides evented I/O, timers, etc. The runtime does +not automatically start, so the main function needs to start it. + +[runtime]: https://docs.rs/tokio/1/tokio/runtime/index.html + +The `#[tokio::main]` function is a macro. It transforms the `async fn main()` +into a synchronous `fn main()` that initializes a runtime instance and executes +the async main function. + +For example, the following: + +```rust +#[tokio::main] +async fn main() { + println!("hello"); +} +``` + +gets transformed into: + +```rust +fn main() { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + println!("hello"); + }) +} +``` diff --git a/content/tokio/topics/feature-flags.md b/content/tokio/topics/feature-flags.md new file mode 100644 index 00000000..932403dd --- /dev/null +++ b/content/tokio/topics/feature-flags.md @@ -0,0 +1,17 @@ +--- +title: Cargo Feature Flags +--- + +When depending on Tokio for the tutorial, the `full` feature flag was enabled: + +```toml +tokio = { version = "1", features = ["full"] } +``` + +Tokio has a lot of functionality (TCP, UDP, Unix sockets, timers, sync utilities, multiple scheduler +types, etc). Not all applications need all functionality. When attempting to optimize compile time +or the end application footprint, the application can decide to opt into **only** the features it +uses. + +More information about the available flags is available in the API docs at: + diff --git a/content/tokio/tutorial/hello-tokio.md b/content/tokio/tutorial/hello-tokio.md index 72ff89e1..1d77c26b 100644 --- a/content/tokio/tutorial/hello-tokio.md +++ b/content/tokio/tutorial/hello-tokio.md @@ -13,19 +13,36 @@ then read back the key. This will be done using the Mini-Redis client library. Let's start by generating a new Rust app: ```bash -$ cargo new my-redis -$ cd my-redis +cargo new my-redis +cd my-redis ``` ## Add dependencies -Next, open `Cargo.toml` and add the following right below `[dependencies]`: +Next, add [tokio] and [mini-redis] to the `[dependencies]` section in the +`Cargo.toml` manifest: + +[tokio]: https://crates.io/crates/tokio +[mini-redis]: https://crates.io/crates/mini-redis + +```bash +cargo add tokio --features full +cargo add mini-redis +``` + +This will result in something similar to the following (the versions may be +later than this): ```toml -tokio = { version = "1", features = ["full"] } -mini-redis = "0.4" +[dependencies] +tokio = { version = "1.41.1", features = ["full"] } +mini-redis = "0.4.1" ``` +We use the `full` feature flag in the example for simplicity. For more +information about the available feature flags see the [feature flags +topic](/tokio/topics/feature-flags). + ## Write the code Then, open `main.rs` and replace the contents of the file with: @@ -45,23 +62,25 @@ async fn main() -> Result<()> { // Get key "hello" let result = client.get("hello").await?; - println!("got value from the server; result={:?}", result); + println!("got value from the server; result={result:?}"); Ok(()) } # } ``` +## Run the code + Make sure the Mini-Redis server is running. In a separate terminal window, run: ```bash -$ mini-redis-server +mini-redis-server ``` If you have not already installed mini-redis, you can do so with ```bash -$ cargo install mini-redis +cargo install mini-redis ``` Now, run the `my-redis` application: @@ -82,6 +101,30 @@ You can find the full code [here][full]. Let's take some time to go over what we just did. There isn't much code, but a lot is happening. +## Async `main` function + +```rust +#[tokio::main] +async fn main() -> Result<()> { + // ... + + Ok(()) +} +``` + +The main function is an asynchronous function. This is indicated by the `async` keyword +before the function definition. It returns a `Result`. The `Ok(())` value indicates that the +program completed successfully. The `#[tokio::main]` macro, wraps the asynchronous function +in a standard synchronous function and runs the asynchronous code on the tokio runtime. + +For more information on this see the [Async `main` function] section of the +[Asynchronous Programming] topic. + +[Async `main` function]: /tokio/topics/async#async-main-function +[Asynchronous Programming]: /tokio/topics/async + +## Connecting to redis + ```rust # use mini_redis::client; # async fn dox() -> mini_redis::Result<()> { @@ -94,155 +137,47 @@ The [`client::connect`] function is provided by the `mini-redis` crate. It asynchronously establishes a TCP connection with the specified remote address. Once the connection is established, a `client` handle is returned. Even though the operation is performed asynchronously, the code we write **looks** -synchronous. The only indication that the operation is asynchronous is the -`.await` operator. - -[`client::connect`]: https://docs.rs/mini-redis/0.4/mini_redis/client/fn.connect.html - -## What is asynchronous programming? - -Most computer programs are executed in the same order in which they are written. -The first line executes, then the next, and so on. With synchronous programming, -when a program encounters an operation that cannot be completed immediately, it -will block until the operation completes. For example, establishing a TCP -connection requires an exchange with a peer over the network, which can take a -sizeable amount of time. During this time, the thread is blocked. - -With asynchronous programming, operations that cannot complete immediately are -suspended to the background. The thread is not blocked, and can continue running -other things. Once the operation completes, the task is unsuspended and continues -processing from where it left off. Our example from before only has one task, so -nothing happens while it is suspended, but asynchronous programs typically have -many such tasks. - -Although asynchronous programming can result in faster applications, it often -results in much more complicated programs. The programmer is required to track -all the state necessary to resume work once the asynchronous operation -completes. Historically, this is a tedious and error-prone task. - -## Compile-time green-threading - -Rust implements asynchronous programming using a feature called [`async/await`]. -Functions that perform asynchronous operations are labeled with the `async` -keyword. In our example, the `connect` function is defined like this: - -```rust -use mini_redis::Result; -use mini_redis::client::Client; -use tokio::net::ToSocketAddrs; - -pub async fn connect(addr: T) -> Result { - // ... -# unimplemented!() -} -``` - -The `async fn` definition looks like a regular synchronous function, but -operates asynchronously. Rust transforms the `async fn` at **compile** time into -a routine that operates asynchronously. Any calls to `.await` within the `async -fn` yield control back to the thread. The thread may do other work while the -operation processes in the background. +synchronous. To actually run the connect operation, on the runtime, you need to +add the `await` keyword. -> **warning** -> Although other languages implement [`async/await`] too, Rust takes a unique -> approach. Primarily, Rust's async operations are **lazy**. This results in -> different runtime semantics than other languages. +We use the `?` operator on this call as the method may fail if the underlying +connection cannot be established (e.g. if the server is not running). -[`async/await`]: https://en.wikipedia.org/wiki/Async/await +[`client::connect`]: https://docs.rs/mini-redis/latest/mini_redis/client/fn.connect.html -If this doesn't quite make sense yet, don't worry. We will explore `async/await` -more throughout the guide. +## Setting and getting a key -## Using `async/await` +To set a key, we use the [`Client::set`] method, passing the key and value. This +method is asynchronous, so we use `await`. -Async functions are called like any other Rust function. However, calling these -functions does not result in the function body executing. Instead, calling an -`async fn` returns a value representing the operation. This is conceptually -analogous to a zero-argument closure. To actually run the operation, you should -use the `.await` operator on the return value. - -For example, the given program +[`Client::set`]: https://docs.rs/mini-redis/latest/mini_redis/client/struct.Client.html#method.set ```rust -async fn say_world() { - println!("world"); -} - -#[tokio::main] -async fn main() { - // Calling `say_world()` does not execute the body of `say_world()`. - let op = say_world(); - - // This println! comes first - println!("hello"); - - // Calling `.await` on `op` starts executing `say_world`. - op.await; -} -``` - -outputs: - -```text -hello -world +client.set("hello", "world".into()).await?; ``` -The return value of an `async fn` is an anonymous type that implements the -[`Future`] trait. - -[`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html - -## Async `main` function - -The main function used to launch the application differs from the usual one -found in most of Rust's crates. - -1. It is an `async fn` -2. It is annotated with `#[tokio::main]` - -An `async fn` is used as we want to enter an asynchronous context. However, -asynchronous functions must be executed by a [runtime]. The runtime contains the -asynchronous task scheduler, provides evented I/O, timers, etc. The runtime does -not automatically start, so the main function needs to start it. - -The `#[tokio::main]` function is a macro. It transforms the `async fn main()` -into a synchronous `fn main()` that initializes a runtime instance and executes -the async main function. +To get the value of a key, we use the [`Client::get`] method, which also requires `await`. -For example, the following: +[`Client::get`]: https://docs.rs/mini-redis/latest/mini_redis/client/struct.Client.html#method.get ```rust -#[tokio::main] -async fn main() { - println!("hello"); -} +let result = client.get("hello").await?; ``` -gets transformed into: +The result is an `Option`, which will be `Some(Bytes)` if the key exists +or `None` if it does not. We print the Debug format of the result. ```rust -fn main() { - let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - println!("hello"); - }) -} +println!("got value from the server; result={result:?}"); ``` -The details of the Tokio runtime will be covered later. - -[runtime]: https://docs.rs/tokio/1/tokio/runtime/index.html +# Conclusion -## Cargo features +Congratulations on writing your first Tokio application! In the next section, +we will explore Tokio's asynchronous programming model in more detail. -When depending on Tokio for this tutorial, the `full` feature flag is enabled: - -```toml -tokio = { version = "1", features = ["full"] } -``` +If you have any questions, feel free to ask on the [Tokio Discord server] or +[GitHub Discussions]. -Tokio has a lot of functionality (TCP, UDP, Unix sockets, timers, sync -utilities, multiple scheduler types, etc). Not all applications need all -functionality. When attempting to optimize compile time or the end application -footprint, the application can decide to opt into **only** the features it uses. +[Tokio Discord server]: https://discord.gg/tokio +[GitHub Discussions]: https://github.com/tokio-rs/tokio/discussions diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx index 96d25b45..327888fa 100644 --- a/pages/[...slug].tsx +++ b/pages/[...slug].tsx @@ -21,6 +21,8 @@ const menu = { }, topics: { nested: [ + "async", + "feature-flags", "bridging", "shutdown", "tracing", From b36aeb383a31ad8317ccbd5ec9d05ad9e9f5cb1b Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 15:44:53 -0800 Subject: [PATCH 4/7] Add troubleshooting section to hello-tokio Fixes: https://github.com/tokio-rs/website/issues/461 --- content/tokio/tutorial/hello-tokio.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/content/tokio/tutorial/hello-tokio.md b/content/tokio/tutorial/hello-tokio.md index 1d77c26b..9ef326b0 100644 --- a/content/tokio/tutorial/hello-tokio.md +++ b/content/tokio/tutorial/hello-tokio.md @@ -96,6 +96,24 @@ You can find the full code [here][full]. [full]: https://github.com/tokio-rs/website/blob/master/tutorial-code/hello-tokio/src/main.rs +# Troubleshooting + +A common mistake is to forget to add `.await` on calls to asynchronous +functions. This is particularly easy to forget for functions where the return +value is not used by the application. The compiler warns you and teaches you how +to fix this error. + +```plain +warning: unused implementer of `std::future::Future` that must be used + --> examples/hello-redis.rs:12:5 + | +12 | client.set("hello", "world".into()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: futures do nothing unless you `.await` or poll them + = note: `#[warn(unused_must_use)]` on by default +``` + # Breaking it down Let's take some time to go over what we just did. There isn't much code, but a From 0326c578e1f8595a844ae77d78cfbc218cbf9000 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 15:50:11 -0800 Subject: [PATCH 5/7] Make hero page get started link point at intro --- components/hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/hero.tsx b/components/hero.tsx index 9d1db320..47b9fcde 100644 --- a/components/hero.tsx +++ b/components/hero.tsx @@ -1,6 +1,6 @@ import React, { FC } from "react"; -const gettingStarted = "/tokio/tutorial"; +const gettingStarted = "/tokio/introduction"; const icons = [ "bytes", From 1342d7bdeb8fc75cd1f188622cd02ef67cafe1bb Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 26 Nov 2024 16:24:05 -0800 Subject: [PATCH 6/7] Fix doc-tests --- content/tokio/tutorial/hello-tokio.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/content/tokio/tutorial/hello-tokio.md b/content/tokio/tutorial/hello-tokio.md index 9ef326b0..e6c88520 100644 --- a/content/tokio/tutorial/hello-tokio.md +++ b/content/tokio/tutorial/hello-tokio.md @@ -122,6 +122,7 @@ lot is happening. ## Async `main` function ```rust +# use mini_redis::{Result}; #[tokio::main] async fn main() -> Result<()> { // ... @@ -130,10 +131,11 @@ async fn main() -> Result<()> { } ``` -The main function is an asynchronous function. This is indicated by the `async` keyword -before the function definition. It returns a `Result`. The `Ok(())` value indicates that the -program completed successfully. The `#[tokio::main]` macro, wraps the asynchronous function -in a standard synchronous function and runs the asynchronous code on the tokio runtime. +The main function is an asynchronous function. This is indicated by the `async` +keyword before the function definition. It returns `mini_redis::Result`. The +`Ok(())` value indicates that the program completed successfully. The +`#[tokio::main]` macro, wraps the asynchronous function in a standard +synchronous function and runs the asynchronous code on the tokio runtime. For more information on this see the [Async `main` function] section of the [Asynchronous Programming] topic. @@ -171,7 +173,12 @@ method is asynchronous, so we use `await`. [`Client::set`]: https://docs.rs/mini-redis/latest/mini_redis/client/struct.Client.html#method.set ```rust +# use mini_redis::client; +# async fn dox() -> mini_redis::Result<()> { +# let mut client = client::connect("127.0.0.1:6379").await?; client.set("hello", "world".into()).await?; +# Ok(()) +# } ``` To get the value of a key, we use the [`Client::get`] method, which also requires `await`. @@ -179,13 +186,19 @@ To get the value of a key, we use the [`Client::get`] method, which also require [`Client::get`]: https://docs.rs/mini-redis/latest/mini_redis/client/struct.Client.html#method.get ```rust +# use mini_redis::client; +# async fn dox() -> mini_redis::Result<()> { +# let mut client = client::connect("127.0.0.1:6379").await?; let result = client.get("hello").await?; +# Ok(()) +# } ``` The result is an `Option`, which will be `Some(Bytes)` if the key exists or `None` if it does not. We print the Debug format of the result. -```rust +```rust,ignore +# let result = 42; println!("got value from the server; result={result:?}"); ``` From cc332d5107459961be4a84fdec7def02e2ccc611 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 29 Nov 2024 19:12:03 -0800 Subject: [PATCH 7/7] Update content/tokio/tutorial/hello-tokio.md Co-authored-by: Alice Ryhl --- content/tokio/tutorial/hello-tokio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/tokio/tutorial/hello-tokio.md b/content/tokio/tutorial/hello-tokio.md index e6c88520..8c3b6a60 100644 --- a/content/tokio/tutorial/hello-tokio.md +++ b/content/tokio/tutorial/hello-tokio.md @@ -39,7 +39,7 @@ tokio = { version = "1.41.1", features = ["full"] } mini-redis = "0.4.1" ``` -We use the `full` feature flag in the example for simplicity. For more +We use the `full` feature flag for simplicity. For more information about the available feature flags see the [feature flags topic](/tokio/topics/feature-flags).