diff --git a/README.md b/README.md index 2ff16818b5..29c9083adb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ look right, but you _will_ still be able to build the book. To use the plugins, you should run: ```bash -$ cargo install --locked --path packages/mdbook-trpl +$ cargo install --locked --path packages/mdbook-trpl --force ``` ## Building diff --git a/listings/ch17-async-await/listing-17-01/src/main.rs b/listings/ch17-async-await/listing-17-01/src/main.rs index b4d9323f5f..3551352d2e 100644 --- a/listings/ch17-async-await/listing-17-01/src/main.rs +++ b/listings/ch17-async-await/listing-17-01/src/main.rs @@ -12,6 +12,6 @@ async fn page_title(url: &str) -> Option { let response_text = response.text().await; Html::parse(&response_text) .select_first("title") - .map(|title_element| title_element.inner_html()) + .map(|title| title.inner_html()) } // ANCHOR_END: all diff --git a/listings/ch17-async-await/listing-17-02/src/main.rs b/listings/ch17-async-await/listing-17-02/src/main.rs index e9c43b34d7..984c70c8f3 100644 --- a/listings/ch17-async-await/listing-17-02/src/main.rs +++ b/listings/ch17-async-await/listing-17-02/src/main.rs @@ -12,5 +12,5 @@ async fn page_title(url: &str) -> Option { // ANCHOR_END: chaining Html::parse(&response_text) .select_first("title") - .map(|title_element| title_element.inner_html()) + .map(|title| title.inner_html()) } diff --git a/listings/ch17-async-await/listing-17-03/src/main.rs b/listings/ch17-async-await/listing-17-03/src/main.rs index 23290fff4a..1500b23093 100644 --- a/listings/ch17-async-await/listing-17-03/src/main.rs +++ b/listings/ch17-async-await/listing-17-03/src/main.rs @@ -17,5 +17,5 @@ async fn page_title(url: &str) -> Option { let response_text = trpl::get(url).await.text().await; Html::parse(&response_text) .select_first("title") - .map(|title_element| title_element.inner_html()) + .map(|title| title.inner_html()) } diff --git a/listings/ch17-async-await/listing-17-04/src/main.rs b/listings/ch17-async-await/listing-17-04/src/main.rs index 23b2ea99f0..55c873b235 100644 --- a/listings/ch17-async-await/listing-17-04/src/main.rs +++ b/listings/ch17-async-await/listing-17-04/src/main.rs @@ -20,5 +20,5 @@ async fn page_title(url: &str) -> Option { let response_text = trpl::get(url).await.text().await; Html::parse(&response_text) .select_first("title") - .map(|title_element| title_element.inner_html()) + .map(|title| title.inner_html()) } diff --git a/listings/ch17-async-await/listing-17-05/src/main.rs b/listings/ch17-async-await/listing-17-05/src/main.rs index 8a5546afd5..5b7ae2b5b9 100644 --- a/listings/ch17-async-await/listing-17-05/src/main.rs +++ b/listings/ch17-async-await/listing-17-05/src/main.rs @@ -18,15 +18,15 @@ fn main() { println!("{url} returned first"); match maybe_title { - Some(title) => println!("Its page title is: '{title}'"), - None => println!("Its title could not be parsed."), + Some(title) => println!("Its page title was: '{title}'"), + None => println!("It had no title."), } }) } async fn page_title(url: &str) -> (&str, Option) { - let text = trpl::get(url).await.text().await; - let title = Html::parse(&text) + let response_text = trpl::get(url).await.text().await; + let title = Html::parse(&response_text) .select_first("title") .map(|title| title.inner_html()); (url, title) diff --git a/listings/ch17-async-await/listing-17-09/src/main.rs b/listings/ch17-async-await/listing-17-09/src/main.rs index 56daddc10f..e1b8fa3f3d 100644 --- a/listings/ch17-async-await/listing-17-09/src/main.rs +++ b/listings/ch17-async-await/listing-17-09/src/main.rs @@ -9,7 +9,7 @@ fn main() { tx.send(val).unwrap(); let received = rx.recv().await.unwrap(); - println!("Got: {received}"); + println!("received '{received}'"); // ANCHOR_END: channel }); } diff --git a/listings/ch17-async-await/listing-17-28/src/main.rs b/listings/ch17-async-await/listing-17-28/src/main.rs index 8c6348c735..b9d18bb487 100644 --- a/listings/ch17-async-await/listing-17-28/src/main.rs +++ b/listings/ch17-async-await/listing-17-28/src/main.rs @@ -9,7 +9,7 @@ fn main() { "Finally finished" }; - match timeout(slow, Duration::from_millis(10)).await { + match timeout(slow, Duration::from_secs(2)).await { Ok(message) => println!("Succeeded with '{message}'"), Err(duration) => { println!("Failed after {} seconds", duration.as_secs()) diff --git a/listings/ch17-async-await/listing-17-37/src/main.rs b/listings/ch17-async-await/listing-17-37/src/main.rs index bc10dd48c9..b2858db459 100644 --- a/listings/ch17-async-await/listing-17-37/src/main.rs +++ b/listings/ch17-async-await/listing-17-37/src/main.rs @@ -10,9 +10,9 @@ fn main() { let messages = get_messages().timeout(Duration::from_millis(200)); let intervals = get_intervals(); let merged = messages.merge(intervals); - // ANCHOR_END: main while let Some(result) = merged.next().await { + // ANCHOR_END: main match result { Ok(message) => println!("{message}"), Err(reason) => eprintln!("Problem: {reason:?}"), diff --git a/listings/ch17-async-await/listing-17-38/src/main.rs b/listings/ch17-async-await/listing-17-38/src/main.rs index 72da09d75f..9d98a27fd7 100644 --- a/listings/ch17-async-await/listing-17-38/src/main.rs +++ b/listings/ch17-async-await/listing-17-38/src/main.rs @@ -13,9 +13,9 @@ fn main() { .timeout(Duration::from_secs(10)); let merged = messages.merge(intervals); let mut stream = pin!(merged); - // ANCHOR_END: main while let Some(result) = stream.next().await { + // ANCHOR_END: main match result { Ok(message) => println!("{message}"), Err(reason) => eprintln!("Problem: {reason:?}"), diff --git a/nostarch/docx/chapter17.docx b/nostarch/docx/chapter17.docx index 49475203a1..70eb84eeac 100644 Binary files a/nostarch/docx/chapter17.docx and b/nostarch/docx/chapter17.docx differ diff --git a/packages/trpl/src/lib.rs b/packages/trpl/src/lib.rs index fcc7859091..02a1dd8248 100644 --- a/packages/trpl/src/lib.rs +++ b/packages/trpl/src/lib.rs @@ -62,11 +62,18 @@ pub use tokio_stream::{ /// /// - Not *that* far off from what Tokio itself does under the hood in its own /// `tokio::main` macro for supporting `async fn main`. -pub fn run(future: F) -> F::Output { +pub fn block_on(future: F) -> F::Output { let rt = Runtime::new().unwrap(); rt.block_on(future) } +/// This function has been renamed to `block_on`; please see its documentation. +/// This function remains to maintain compatibility with the online versions +/// of the book that use the name `run`. +pub fn run(future: F) -> F::Output { + block_on(future) +} + /// Run two futures, taking whichever finishes first and canceling the other. /// /// Notice that this is built on [`futures::future::select`], which has the @@ -74,9 +81,9 @@ pub fn run(future: F) -> F::Output { /// is that you can work with the first result and then later *also* continue /// waiting for the second future. /// -/// We use the `race` semantics, where the slower future is simply dropped, for -/// the sake of simplicity in the examples: no need to deal with the tuple and -/// intentionally ignore the second future this way! +/// We drop the slower future for the sake of simplicity in the examples: no +/// need to deal with the tuple and intentionally ignore the second future this +/// way! /// /// Note that this only works as “simply” as it does because: /// @@ -84,7 +91,7 @@ pub fn run(future: F) -> F::Output { /// - It internally *pins* the futures. /// - It throws away (rather than returning) the unused future (which is why it /// can get away with pinning them). -pub async fn race(f1: F1, f2: F2) -> Either +pub async fn select(f1: F1, f2: F2) -> Either where F1: Future, F2: Future, @@ -97,6 +104,17 @@ where } } +/// This function has been renamed to `select`; please see its documentation. +/// This function remains to maintain compatibility with the online versions +/// of the book that use the name `race`. +pub async fn race(f1: F1, f2: F2) -> Either +where + F1: Future, + F2: Future, +{ + select(f1, f2).await +} + /// Fetch data from a URL. For more convenient use in _The Rust Programming /// Language_, panics instead of returning a [`Result`] if the request fails. pub async fn get(url: &str) -> Response { diff --git a/packages/trpl/tests/integration/main.rs b/packages/trpl/tests/integration/main.rs index afc5f81864..65fce378ea 100644 --- a/packages/trpl/tests/integration/main.rs +++ b/packages/trpl/tests/integration/main.rs @@ -14,20 +14,30 @@ use std::{pin::Pin, time::Duration}; use futures::Future; use trpl::{Either, Receiver, Sender}; -/// This test is foundational for all the others, as they depend on `run`. +/// We initially named the function `run` and an online version of the async chapter +/// was released with that name, so we want to keep it working. We decided to rename +/// `run` to be `block_on` to more closely match other crates' names, so most of the +/// tests now use the `block_on` name. +#[test] +fn using_run_works() { + let val = trpl::run(async { "Hello" }); + assert_eq!(val, "Hello"); +} + +/// This test is foundational for all the others, as they depend on `block_on`. /// /// If we mess this up, *all* the tests below will fail -- so by the same token, /// if all the tests below are failing, this one probably is too; fix it and the /// others will likely start working again. #[test] -fn re_exported_run_works() { - let val = trpl::run(async { "Hello" }); +fn re_exported_block_on_works() { + let val = trpl::block_on(async { "Hello" }); assert_eq!(val, "Hello"); } #[test] fn re_exported_spawn_works() { - let result = trpl::run(async { + let result = trpl::block_on(async { let handle_a = trpl::spawn_task(async { "Hello" }); let handle_b = trpl::spawn_task(async { "Goodbye" }); vec![handle_a.await.unwrap(), handle_b.await.unwrap()] @@ -38,7 +48,7 @@ fn re_exported_spawn_works() { #[test] fn re_exported_sleep_works() { - let val = trpl::run(async { + let val = trpl::block_on(async { trpl::sleep(Duration::from_micros(1)).await; "Done!" }); @@ -47,7 +57,7 @@ fn re_exported_sleep_works() { #[test] fn re_exported_channel_apis_work() { - trpl::run(async { + trpl::block_on(async { // Explicitly naming the type to confirm the re-exports are aligned. let (tx, mut rx): (Sender<&str>, Receiver<&str>) = trpl::channel(); @@ -67,7 +77,7 @@ mod re_exported_join_apis_work { #[test] fn join_fn() { - let result = trpl::run(async { + let result = trpl::block_on(async { let a = async { 1 }; let b = async { 2 }; trpl::join(a, b).await @@ -78,7 +88,7 @@ mod re_exported_join_apis_work { #[test] fn join3_fn() { - let result = trpl::run(async { + let result = trpl::block_on(async { let a = async { 1 }; let b = async { 2 }; let c = async { 3 }; @@ -91,7 +101,7 @@ mod re_exported_join_apis_work { #[test] fn join_all_fn() { - let result = trpl::run(async { + let result = trpl::block_on(async { let a = async { format!("{}", 1) }; let b = async { "Hello".to_string() }; @@ -117,7 +127,7 @@ mod re_exported_join_apis_work { #[test] fn join_macro() { - let result = trpl::run(async { + let result = trpl::block_on(async { let a = async { 1 }; let b = async { "Hello" }; @@ -132,14 +142,39 @@ mod re_exported_join_apis_work { } #[test] -fn race() { +fn select() { + #[derive(Debug, PartialEq)] + struct Slow; + + #[derive(Debug, PartialEq)] + struct Fast; + + let val = trpl::block_on(async { + let slow = async { + trpl::sleep(Duration::from_millis(1_000)).await; + Slow + }; + + let fast = async { + trpl::sleep(Duration::from_millis(1)).await; + Fast + }; + + trpl::select(slow, fast).await + }); + + assert!(matches!(val, Either::Right(Fast))); +} + +#[test] +fn race_continues_to_work() { #[derive(Debug, PartialEq)] struct Slow; #[derive(Debug, PartialEq)] struct Fast; - let val = trpl::run(async { + let val = trpl::block_on(async { let slow = async { trpl::sleep(Duration::from_millis(1_000)).await; Slow @@ -158,7 +193,7 @@ fn race() { #[test] fn yield_now() { - let result = trpl::run(async { + let result = trpl::block_on(async { trpl::yield_now().await; "done" }); @@ -168,7 +203,7 @@ fn yield_now() { #[test] fn read_to_string() { - let result = trpl::run(async { + let result = trpl::block_on(async { trpl::read_to_string("tests/integration/to-read.txt") .await .unwrap() @@ -181,7 +216,7 @@ fn read_to_string() { fn stream_iter() { use trpl::StreamExt; - let result = trpl::run(async { + let result = trpl::block_on(async { let ns = vec![1, 2, 3]; let mut stream = trpl::stream_from_iter(ns); let mut result = vec![]; @@ -202,7 +237,7 @@ fn receiver_stream() { use trpl::ReceiverStream; use trpl::StreamExt; - let result: Vec = trpl::run(async { + let result: Vec = trpl::block_on(async { println!("startup"); let (tx, rx) = trpl::channel(); let rx_stream = ReceiverStream::new(rx); @@ -220,7 +255,7 @@ fn receiver_stream() { fn re_exported_interval_stream_works() { use trpl::{IntervalStream, StreamExt}; - trpl::run(async { + trpl::block_on(async { let mut interval_stream = IntervalStream::new(trpl::interval(Duration::from_millis(1))) .take(1); diff --git a/src/ch17-01-futures-and-syntax.md b/src/ch17-01-futures-and-syntax.md index 048b2204b7..5263ce1779 100644 --- a/src/ch17-01-futures-and-syntax.md +++ b/src/ch17-01-futures-and-syntax.md @@ -124,7 +124,7 @@ in the document, if there is one. Because there may not be any matching element, `Option::map` method, which lets us work with the item in the `Option` if it’s present, and do nothing if it isn’t. (We could also use a `match` expression here, but `map` is more idiomatic.) In the body of the function we supply to -`map`, we call `inner_html` on the `title_element` to get its content, which is +`map`, we call `inner_html` on the `title` to get its content, which is a `String`. When all is said and done, we have an `Option`. Notice that Rust’s `await` keyword goes _after_ the expression you’re awaiting,