diff --git a/listings/ch08-common-collections/listing-08-19/src/main.rs b/listings/ch08-common-collections/listing-08-19/src/main.rs index fc08e9ceaa..44b3b5bb9a 100644 --- a/listings/ch08-common-collections/listing-08-19/src/main.rs +++ b/listings/ch08-common-collections/listing-08-19/src/main.rs @@ -1,6 +1,6 @@ fn main() { // ANCHOR: here - let s1 = String::from("hello"); + let s1 = String::from("hi"); let h = s1[0]; // ANCHOR_END: here } diff --git a/listings/ch14-more-about-cargo/listing-14-07/add/Cargo.toml b/listings/ch14-more-about-cargo/listing-14-07/add/Cargo.toml index 9e542690c7..2bc25b73d2 100644 --- a/listings/ch14-more-about-cargo/listing-14-07/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/listing-14-07/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder", "add_one"] diff --git a/listings/ch14-more-about-cargo/no-listing-01-workspace/add/Cargo.toml b/listings/ch14-more-about-cargo/no-listing-01-workspace/add/Cargo.toml index 61bdb9cbf2..daa8c449cc 100644 --- a/listings/ch14-more-about-cargo/no-listing-01-workspace/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/no-listing-01-workspace/add/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -resolver = "2" +resolver = "3" diff --git a/listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml b/listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml index 9e542690c7..2bc25b73d2 100644 --- a/listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder", "add_one"] diff --git a/listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/Cargo.toml b/listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/Cargo.toml index 9e542690c7..2bc25b73d2 100644 --- a/listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder", "add_one"] diff --git a/listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/Cargo.toml b/listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/Cargo.toml index 9e542690c7..2bc25b73d2 100644 --- a/listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder", "add_one"] diff --git a/listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml b/listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml index 38ea3f5655..448a0383ab 100644 --- a/listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder"] diff --git a/listings/ch14-more-about-cargo/output-only-02-add-one/add/Cargo.toml b/listings/ch14-more-about-cargo/output-only-02-add-one/add/Cargo.toml index 8c9ab0e120..8d9f501247 100644 --- a/listings/ch14-more-about-cargo/output-only-02-add-one/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/output-only-02-add-one/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = [ "add_one","adder"] diff --git a/listings/ch14-more-about-cargo/output-only-03-use-rand/add/Cargo.toml b/listings/ch14-more-about-cargo/output-only-03-use-rand/add/Cargo.toml index 9e542690c7..2bc25b73d2 100644 --- a/listings/ch14-more-about-cargo/output-only-03-use-rand/add/Cargo.toml +++ b/listings/ch14-more-about-cargo/output-only-03-use-rand/add/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -resolver = "2" +resolver = "3" members = ["adder", "add_one"] diff --git a/listings/ch19-patterns-and-matching/listing-19-09/src/main.rs b/listings/ch19-patterns-and-matching/listing-19-09/src/main.rs index e378c37032..e974935eb5 100644 --- a/listings/ch19-patterns-and-matching/listing-19-09/src/main.rs +++ b/listings/ch19-patterns-and-matching/listing-19-09/src/main.rs @@ -1,8 +1,8 @@ fn main() { let some_option_value: Option = None; // ANCHOR: here - if let Some(x) = some_option_value { - println!("{x}"); - } + let Some(x) = some_option_value else { + return; + }; // ANCHOR_END: here } diff --git a/listings/ch19-patterns-and-matching/listing-19-10/src/main.rs b/listings/ch19-patterns-and-matching/listing-19-10/src/main.rs index 2073948e7d..6186890be2 100644 --- a/listings/ch19-patterns-and-matching/listing-19-10/src/main.rs +++ b/listings/ch19-patterns-and-matching/listing-19-10/src/main.rs @@ -1,7 +1,7 @@ fn main() { // ANCHOR: here - if let x = 5 { - println!("{x}"); + let x = 5 else { + return; }; // ANCHOR_END: here } diff --git a/listings/ch19-patterns-and-matching/listing-19-15/src/main.rs b/listings/ch19-patterns-and-matching/listing-19-15/src/main.rs index 7edf7f2bb8..65ff116504 100644 --- a/listings/ch19-patterns-and-matching/listing-19-15/src/main.rs +++ b/listings/ch19-patterns-and-matching/listing-19-15/src/main.rs @@ -19,7 +19,7 @@ fn main() { println!("Text message: {text}"); } Message::ChangeColor(r, g, b) => { - println!("Change the color to red {r}, green {g}, and blue {b}"); + println!("Change color to red {r}, green {g}, and blue {b}"); } } } diff --git a/listings/ch19-patterns-and-matching/listing-19-19/src/main.rs b/listings/ch19-patterns-and-matching/listing-19-19/src/main.rs index e28dab111c..43220b8243 100644 --- a/listings/ch19-patterns-and-matching/listing-19-19/src/main.rs +++ b/listings/ch19-patterns-and-matching/listing-19-19/src/main.rs @@ -4,7 +4,7 @@ fn main() { match numbers { (first, _, third, _, fifth) => { - println!("Some numbers: {first}, {third}, {fifth}") + println!("Some numbers: {first}, {third}, {fifth}"); } } // ANCHOR_END: here diff --git a/listings/ch20-advanced-features/listing-20-11/output.txt b/listings/ch20-advanced-features/listing-20-11/output.txt index d9b4301028..bf64b3806c 100644 --- a/listings/ch20-advanced-features/listing-20-11/output.txt +++ b/listings/ch20-advanced-features/listing-20-11/output.txt @@ -1,5 +1,5 @@ $ cargo +nightly miri run Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s - Running `/Users/chris/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/unsafe-example` + Running `file:///home/.rustup/toolchains/nightly/bin/cargo-miri runner target/miri/debug/unsafe-example` COUNTER: 3 diff --git a/listings/ch20-advanced-features/listing-20-12/src/main.rs b/listings/ch20-advanced-features/listing-20-12/src/main.rs index 885c1aa1d8..2301b0cac6 100644 --- a/listings/ch20-advanced-features/listing-20-12/src/main.rs +++ b/listings/ch20-advanced-features/listing-20-12/src/main.rs @@ -1,3 +1,4 @@ +// ANCHOR: here unsafe trait Foo { // methods go here } @@ -5,5 +6,6 @@ unsafe trait Foo { unsafe impl Foo for i32 { // method implementations go here } +// ANCHOR_END: here fn main() {} diff --git a/listings/ch20-advanced-features/listing-20-29/Cargo.lock b/listings/ch20-advanced-features/listing-20-29/Cargo.lock index b2d9257545..b2327c755a 100644 --- a/listings/ch20-advanced-features/listing-20-29/Cargo.lock +++ b/listings/ch20-advanced-features/listing-20-29/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "macros-example" +name = "functions-example" version = "0.1.0" diff --git a/listings/ch20-advanced-features/listing-20-29/Cargo.toml b/listings/ch20-advanced-features/listing-20-29/Cargo.toml index 76689ec01b..3644460993 100644 --- a/listings/ch20-advanced-features/listing-20-29/Cargo.toml +++ b/listings/ch20-advanced-features/listing-20-29/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "macros-example" +name = "functions-example" version = "0.1.0" edition = "2024" diff --git a/listings/ch20-advanced-features/no-listing-15-map-closure/src/main.rs b/listings/ch20-advanced-features/listing-20-29/src/main.rs similarity index 100% rename from listings/ch20-advanced-features/no-listing-15-map-closure/src/main.rs rename to listings/ch20-advanced-features/listing-20-29/src/main.rs diff --git a/listings/ch20-advanced-features/no-listing-15-map-closure/Cargo.lock b/listings/ch20-advanced-features/listing-20-30/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-15-map-closure/Cargo.lock rename to listings/ch20-advanced-features/listing-20-30/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-15-map-closure/Cargo.toml b/listings/ch20-advanced-features/listing-20-30/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-15-map-closure/Cargo.toml rename to listings/ch20-advanced-features/listing-20-30/Cargo.toml diff --git a/listings/ch20-advanced-features/no-listing-16-map-function/src/main.rs b/listings/ch20-advanced-features/listing-20-30/src/main.rs similarity index 100% rename from listings/ch20-advanced-features/no-listing-16-map-function/src/main.rs rename to listings/ch20-advanced-features/listing-20-30/src/main.rs diff --git a/listings/ch20-advanced-features/listing-20-31/Cargo.lock b/listings/ch20-advanced-features/listing-20-31/Cargo.lock index 39afcf282f..b2327c755a 100644 --- a/listings/ch20-advanced-features/listing-20-31/Cargo.lock +++ b/listings/ch20-advanced-features/listing-20-31/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "hello_macro" +name = "functions-example" version = "0.1.0" diff --git a/listings/ch20-advanced-features/listing-20-31/Cargo.toml b/listings/ch20-advanced-features/listing-20-31/Cargo.toml index aaaf3bf827..3644460993 100644 --- a/listings/ch20-advanced-features/listing-20-31/Cargo.toml +++ b/listings/ch20-advanced-features/listing-20-31/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_macro" +name = "functions-example" version = "0.1.0" edition = "2024" diff --git a/listings/ch20-advanced-features/listing-20-31/src/main.rs b/listings/ch20-advanced-features/listing-20-31/src/main.rs index 468c30aa48..60fb730057 100644 --- a/listings/ch20-advanced-features/listing-20-31/src/main.rs +++ b/listings/ch20-advanced-features/listing-20-31/src/main.rs @@ -1,9 +1,10 @@ -use hello_macro::HelloMacro; -use hello_macro_derive::HelloMacro; - -#[derive(HelloMacro)] -struct Pancakes; - fn main() { - Pancakes::hello_macro(); + // ANCHOR: here + enum Status { + Value(u32), + Stop, + } + + let list_of_statuses: Vec = (0u32..20).map(Status::Value).collect(); + // ANCHOR_END: here } diff --git a/listings/ch20-advanced-features/no-listing-16-map-function/Cargo.lock b/listings/ch20-advanced-features/listing-20-32/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-16-map-function/Cargo.lock rename to listings/ch20-advanced-features/listing-20-32/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-16-map-function/Cargo.toml b/listings/ch20-advanced-features/listing-20-32/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-16-map-function/Cargo.toml rename to listings/ch20-advanced-features/listing-20-32/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-32/src/lib.rs b/listings/ch20-advanced-features/listing-20-32/src/lib.rs new file mode 100644 index 0000000000..7679cc7c0a --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-32/src/lib.rs @@ -0,0 +1,3 @@ +fn returns_closure() -> impl Fn(i32) -> i32 { + |x| x + 1 +} diff --git a/listings/ch20-advanced-features/no-listing-17-map-initializer/Cargo.lock b/listings/ch20-advanced-features/listing-20-33/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-17-map-initializer/Cargo.lock rename to listings/ch20-advanced-features/listing-20-33/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-17-map-initializer/Cargo.toml b/listings/ch20-advanced-features/listing-20-33/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-17-map-initializer/Cargo.toml rename to listings/ch20-advanced-features/listing-20-33/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-33/output.txt b/listings/ch20-advanced-features/listing-20-33/output.txt new file mode 100644 index 0000000000..e8259121eb --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-33/output.txt @@ -0,0 +1,20 @@ +$ cargo build + Compiling functions-example v0.1.0 (file:///projects/functions-example) + error[E0308]: mismatched types + --> src/main.rs:4:9 + | + 4 | returns_initialized_closure(123) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type + ... + 12 | fn returns_closure() -> impl Fn(i32) -> i32 { + | ------------------- the expected opaque type + ... + 16 | fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 { + | ------------------- the found opaque type + | + = note: expected opaque type `impl Fn(i32) -> i32` (opaque type at ) + found opaque type `impl Fn(i32) -> i32` (opaque type at ) + = note: distinct uses of `impl Trait` result in different opaque types + + For more information about this error, try `rustc --explain E0308`. + error: could not compile `functions-example` (bin "functions-example") due to 1 previous error diff --git a/listings/ch20-advanced-features/listing-20-33/src/main.rs b/listings/ch20-advanced-features/listing-20-33/src/main.rs new file mode 100644 index 0000000000..d7db7a0b47 --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-33/src/main.rs @@ -0,0 +1,15 @@ +fn main() { + let handlers = vec![returns_closure(), returns_initialized_closure(123)]; + for handler in handlers { + let output = handler(5); + println!("{output}"); + } +} + +fn returns_closure() -> impl Fn(i32) -> i32 { + |x| x + 1 +} + +fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 { + move |x| x + init +} diff --git a/listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/Cargo.lock b/listings/ch20-advanced-features/listing-20-34/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/Cargo.lock rename to listings/ch20-advanced-features/listing-20-34/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/Cargo.toml b/listings/ch20-advanced-features/listing-20-34/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/Cargo.toml rename to listings/ch20-advanced-features/listing-20-34/Cargo.toml diff --git a/listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/src/main.rs b/listings/ch20-advanced-features/listing-20-34/src/main.rs similarity index 91% rename from listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/src/main.rs rename to listings/ch20-advanced-features/listing-20-34/src/main.rs index f033ea716c..975a7f54b8 100644 --- a/listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/src/main.rs +++ b/listings/ch20-advanced-features/listing-20-34/src/main.rs @@ -6,6 +6,7 @@ fn main() { } } +// ANCHOR: here fn returns_closure() -> Box i32> { Box::new(|x| x + 1) } @@ -13,3 +14,4 @@ fn returns_closure() -> Box i32> { fn returns_initialized_closure(init: i32) -> Box i32> { Box::new(move |x| x + init) } +// ANCHOR_END: here diff --git a/listings/ch20-advanced-features/listing-20-35/Cargo.lock b/listings/ch20-advanced-features/listing-20-35/Cargo.lock new file mode 100644 index 0000000000..b2d9257545 --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-35/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "macros-example" +version = "0.1.0" + diff --git a/listings/ch20-advanced-features/listing-20-35/Cargo.toml b/listings/ch20-advanced-features/listing-20-35/Cargo.toml new file mode 100644 index 0000000000..76689ec01b --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-35/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "macros-example" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/listings/ch20-advanced-features/listing-20-29/src/lib.rs b/listings/ch20-advanced-features/listing-20-35/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-29/src/lib.rs rename to listings/ch20-advanced-features/listing-20-35/src/lib.rs diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/Cargo.lock b/listings/ch20-advanced-features/listing-20-37/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/Cargo.lock rename to listings/ch20-advanced-features/listing-20-37/Cargo.lock diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/Cargo.toml b/listings/ch20-advanced-features/listing-20-37/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/Cargo.toml rename to listings/ch20-advanced-features/listing-20-37/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-37/src/main.rs b/listings/ch20-advanced-features/listing-20-37/src/main.rs new file mode 100644 index 0000000000..468c30aa48 --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-37/src/main.rs @@ -0,0 +1,9 @@ +use hello_macro::HelloMacro; +use hello_macro_derive::HelloMacro; + +#[derive(HelloMacro)] +struct Pancakes; + +fn main() { + Pancakes::hello_macro(); +} diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/Cargo.lock b/listings/ch20-advanced-features/listing-20-38/hello_macro/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/Cargo.lock rename to listings/ch20-advanced-features/listing-20-38/hello_macro/Cargo.lock diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/Cargo.toml b/listings/ch20-advanced-features/listing-20-38/hello_macro/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/Cargo.toml rename to listings/ch20-advanced-features/listing-20-38/hello_macro/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/src/lib.rs b/listings/ch20-advanced-features/listing-20-38/hello_macro/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/src/lib.rs rename to listings/ch20-advanced-features/listing-20-38/hello_macro/src/lib.rs diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/Cargo.lock b/listings/ch20-advanced-features/listing-20-39/hello_macro/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/Cargo.lock rename to listings/ch20-advanced-features/listing-20-39/hello_macro/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/Cargo.toml b/listings/ch20-advanced-features/listing-20-39/hello_macro/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/Cargo.toml rename to listings/ch20-advanced-features/listing-20-39/hello_macro/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/src/lib.rs b/listings/ch20-advanced-features/listing-20-39/hello_macro/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/src/lib.rs rename to listings/ch20-advanced-features/listing-20-39/hello_macro/src/lib.rs diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/Cargo.lock b/listings/ch20-advanced-features/listing-20-39/pancakes/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/Cargo.lock rename to listings/ch20-advanced-features/listing-20-39/pancakes/Cargo.lock diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/Cargo.toml b/listings/ch20-advanced-features/listing-20-39/pancakes/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/Cargo.toml rename to listings/ch20-advanced-features/listing-20-39/pancakes/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/src/main.rs b/listings/ch20-advanced-features/listing-20-39/pancakes/src/main.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/src/main.rs rename to listings/ch20-advanced-features/listing-20-39/pancakes/src/main.rs diff --git a/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.lock b/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.lock new file mode 100644 index 0000000000..39afcf282f --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "hello_macro" +version = "0.1.0" + diff --git a/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.toml b/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.toml new file mode 100644 index 0000000000..aaaf3bf827 --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-40/hello_macro/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hello_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/Cargo.lock b/listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/Cargo.lock rename to listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/Cargo.lock diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/Cargo.toml b/listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/Cargo.toml rename to listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/src/lib.rs b/listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/src/lib.rs rename to listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/src/lib.rs diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/src/lib.rs b/listings/ch20-advanced-features/listing-20-40/hello_macro/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/src/lib.rs rename to listings/ch20-advanced-features/listing-20-40/hello_macro/src/lib.rs diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/src/main.rs b/listings/ch20-advanced-features/listing-20-40/hello_macro/src/main.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/src/main.rs rename to listings/ch20-advanced-features/listing-20-40/hello_macro/src/main.rs diff --git a/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.lock b/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.lock new file mode 100644 index 0000000000..39afcf282f --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "hello_macro" +version = "0.1.0" + diff --git a/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.toml b/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.toml new file mode 100644 index 0000000000..aaaf3bf827 --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-42/hello_macro/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hello_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/Cargo.lock b/listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/Cargo.lock similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/Cargo.lock rename to listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/Cargo.lock diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/Cargo.toml b/listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/Cargo.toml similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/Cargo.toml rename to listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/Cargo.toml diff --git a/listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/src/lib.rs b/listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/src/lib.rs similarity index 100% rename from listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/src/lib.rs rename to listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/src/lib.rs diff --git a/listings/ch20-advanced-features/listing-20-42/hello_macro/src/lib.rs b/listings/ch20-advanced-features/listing-20-42/hello_macro/src/lib.rs new file mode 100644 index 0000000000..e74793184b --- /dev/null +++ b/listings/ch20-advanced-features/listing-20-42/hello_macro/src/lib.rs @@ -0,0 +1,3 @@ +pub trait HelloMacro { + fn hello_macro(); +} diff --git a/listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs b/listings/ch20-advanced-features/listing-20-42/hello_macro/src/main.rs similarity index 100% rename from listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs rename to listings/ch20-advanced-features/listing-20-42/hello_macro/src/main.rs diff --git a/listings/ch20-advanced-features/no-listing-17-map-initializer/src/main.rs b/listings/ch20-advanced-features/no-listing-17-map-initializer/src/main.rs deleted file mode 100644 index 60fb730057..0000000000 --- a/listings/ch20-advanced-features/no-listing-17-map-initializer/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -fn main() { - // ANCHOR: here - enum Status { - Value(u32), - Stop, - } - - let list_of_statuses: Vec = (0u32..20).map(Status::Value).collect(); - // ANCHOR_END: here -} diff --git a/nostarch/appendix.md b/nostarch/appendix.md index 98432a1dac..78fdc40759 100644 --- a/nostarch/appendix.md +++ b/nostarch/appendix.md @@ -6,75 +6,80 @@ directory, so all fixes need to be made in `/src/`. [TOC] +# Appendix + +The following sections contain reference material you may find useful in your +Rust journey. + ## Appendix A: Keywords -The following lists contain keywords that are reserved for current or future +The following list contains keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except -as raw identifiers, as we’ll discuss in “Raw Identifiers” on page XX). -*Identifiers* are names of functions, variables, parameters, struct fields, -modules, crates, constants, macros, static values, attributes, types, traits, -or lifetimes. +as raw identifiers as we’ll discuss in the “Raw +Identifiers” section). Identifiers are names +of functions, variables, parameters, struct fields, modules, crates, constants, +macros, static values, attributes, types, traits, or lifetimes. -## Keywords Currently in Use +### Keywords Currently in Use The following is a list of keywords currently in use, with their functionality described. -* **`as` **: perform primitive casting, disambiguate the specific trait -containing an item, or rename items in `use` statements -* **`async` **: return a `Future` instead of blocking the current thread -* **`await` **: suspend execution until the result of a `Future` is ready -* **`break` **: exit a loop immediately -* **`const` **: define constant items or constant raw pointers -* **`continue` **: continue to the next loop iteration -* **`crate` **: in a module path, refers to the crate root -* **`dyn` **: dynamic dispatch to a trait object -* **`else` **: fallback for `if` and `if let` control flow constructs -* **`enum` **: define an enumeration -* **`extern` **: link an external function or variable -* **`false` **: Boolean false literal -* **`fn` **: define a function or the function pointer type -* **`for` **: loop over items from an iterator, implement a trait, or specify a -higher-ranked lifetime -* **`if` **: branch based on the result of a conditional expression -* **`impl` **: implement inherent or trait functionality -* **`in` **: part of `for` loop syntax -* **`let` **: bind a variable -* **`loop` **: loop unconditionally -* **`match` **: match a value to patterns -* **`mod` **: define a module -* **`move` **: make a closure take ownership of all its captures -* **`mut` **: denote mutability in references, raw pointers, or pattern bindings -* **`pub` **: denote public visibility in struct fields, `impl` blocks, or -modules -* **`ref` **: bind by reference -* **`return` **: return from function -* **`Self` **: a type alias for the type we are defining or implementing -* **`self` **: method subject or current module -* **`static` **: global variable or lifetime lasting the entire program -execution -* **`struct` **: define a structure -* **`super` **: parent module of the current module -* **`trait` **: define a trait -* **`true` **: Boolean true literal -* **`type` **: define a type alias or associated type -* **`union` **: define a union; is a keyword only when used in a union -declaration -* **`unsafe` **: denote unsafe code, functions, traits, or implementations -* **`use` **: bring symbols into scope -* **`where` **: denote clauses that constrain a type -* **`while` **: loop conditionally based on the result of an expression - -## Keywords Reserved for Future Use +* `as` - perform primitive casting, disambiguate the specific trait containing + an item, or rename items in `use` statements +* `async` - return a `Future` instead of blocking the current thread +* `await` - suspend execution until the result of a `Future` is ready +* `break` - exit a loop immediately +* `const` - define constant items or constant raw pointers +* `continue` - continue to the next loop iteration +* `crate` - in a module path, refers to the crate root +* `dyn` - dynamic dispatch to a trait object +* `else` - fallback for `if` and `if let` control flow constructs +* `enum` - define an enumeration +* `extern` - link an external function or variable +* `false` - Boolean false literal +* `fn` - define a function or the function pointer type +* `for` - loop over items from an iterator, implement a trait, or specify a + higher-ranked lifetime +* `if` - branch based on the result of a conditional expression +* `impl` - implement inherent or trait functionality +* `in` - part of `for` loop syntax +* `let` - bind a variable +* `loop` - loop unconditionally +* `match` - match a value to patterns +* `mod` - define a module +* `move` - make a closure take ownership of all its captures +* `mut` - denote mutability in references, raw pointers, or pattern bindings +* `pub` - denote public visibility in struct fields, `impl` blocks, or modules +* `ref` - bind by reference +* `return` - return from function +* `Self` - a type alias for the type we are defining or implementing +* `self` - method subject or current module +* `static` - global variable or lifetime lasting the entire program execution +* `struct` - define a structure +* `super` - parent module of the current module +* `trait` - define a trait +* `true` - Boolean true literal +* `type` - define a type alias or associated type +* `union` - define a union; is only a keyword when used + in a union declaration +* `unsafe` - denote unsafe code, functions, traits, or implementations +* `use` - bring symbols into scope; specify precise captures for generic and + lifetime bounds +* `where` - denote clauses that constrain a type +* `while` - loop conditionally based on the result of an expression + +### Keywords Reserved for Future Use The following keywords do not yet have any functionality but are reserved by -Rust for potential future use: +Rust for potential future use. * `abstract` * `become` * `box` * `do` * `final` +* `gen` * `macro` * `override` * `priv` @@ -84,7 +89,7 @@ Rust for potential future use: * `virtual` * `yield` -## Raw Identifiers +### Raw Identifiers *Raw identifiers* are the syntax that lets you use keywords where they wouldn’t normally be allowed. You use a raw identifier by prefixing a keyword with `r#`. @@ -130,14 +135,14 @@ This code will compile without any errors. Note the `r#` prefix on the function name in its definition as well as where the function is called in `main`. Raw identifiers allow you to use any word you choose as an identifier, even if -that word happens to be a reserved keyword. This gives us more freedom to -choose identifier names, as well as lets us integrate with programs written in -a language where these words aren’t keywords. In addition, raw identifiers -allow you to use libraries written in a different Rust edition than your crate -uses. For example, `try` isn’t a keyword in the 2015 edition but is in the 2018 -and 2021 editions. If you depend on a library that is written using the 2015 +that word happens to be a reserved keyword. This gives us more freedom to choose +identifier names, as well as lets us integrate with programs written in a +language where these words aren’t keywords. In addition, raw identifiers allow +you to use libraries written in a different Rust edition than your crate uses. +For example, `try` isn’t a keyword in the 2015 edition but is in the 2018, 2021, +and 2024 editions. If you depend on a library that’s written using the 2015 edition and has a `try` function, you’ll need to use the raw identifier syntax, -`r#try` in this case, to call that function from your 2021 edition code. See +`r#try` in this case, to call that function from your 2018 edition code. See Appendix E for more information on editions. ## Appendix B: Operators and Symbols @@ -146,7 +151,7 @@ This appendix contains a glossary of Rust’s syntax, including operators and other symbols that appear by themselves or in the context of paths, generics, trait bounds, macros, attributes, comments, tuples, and brackets. -## Operators +### Operators Table B-1 contains the operators in Rust, an example of how the operator would appear in context, a short explanation, and whether that operator is @@ -155,72 +160,66 @@ overload that operator is listed. Table B-1: Operators -| Operator | Example | Explanation | Overloadable? | -|---|---|---|---| -| `!` | `ident!(...)`, `ident!{...}`, `ident![...]` | Macro expansion | | -| `!` | `!expr` | Bitwise or logical complement | `Not` | -| `!=` | `expr != expr` | Nonequality comparison | `PartialEq` | -| `% | `expr % expr` | Arithmetic remainder | `Rem` | -| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemAssign` | -| `& | `&expr`, `&mut expr` | Borrow | | -| `&` | `&type`, `&mut type`, `&'a type`, `&'a mut type` | Borrowed pointer -type | | -| `&` | `expr & expr` | Bitwise AND | `BitAnd` | -| `&=` | `var &= expr` | Bitwise AND and assignment | `BitAndAssign` | -| `&&` | `expr && expr` | Short-circuiting logical AND | | -| `* | `expr * expr` | Arithmetic multiplication | `Mul` | -| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulAssign` -| -| `*` | `*expr` | Dereference | `Deref` | -| `*` | `*const type`, `*mut type | Raw pointer | | -| `+ | `trait + trait`, `'a + trait` | Compound type constraint | | -| `+ | `expr + expr` | Arithmetic addition | `Add` | -| `+=` | `var += expr` | Arithmetic addition and assignment | `AddAssign` | -| `,` | `expr, expr` | Argument and element separator | | -| `- | `- expr` | Arithmetic negation | `Neg` | -| `- | `expr - expr` | Arithmetic subtraction | `Sub` | -| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubAssign` | -| `-> | `fn(...) -> type`, `|…| -> type` | Function and closure return type | | -| `. | `expr.ident` | Member access | | -| `..` | `..`, `expr..`, `..expr`, `expr..expr` | Right-exclusive range literal -| `PartialOrd` | -| `..=` | `..=expr`, `expr..=expr` | Right-inclusive range literal | -`PartialOrd` | -| `..` | `..expr` | Struct literal update syntax | | -| `..` | `variant(x, ..)`, `struct_type { x, .. }` | “And the rest” pattern -binding | | -| `...` | `expr...expr` | (Deprecated, use `..=` instead) In a pattern: -inclusive range pattern | | -| `/ | `expr / expr` | Arithmetic division | `Div` | -| `/=` | `var /= expr` | Arithmetic division and assignment | `DivAssign` | -| `: | `pat: type`, `ident: type` | Constraints | | -| `:` | `ident: expr` | Struct field initializer | | -| `:` | `'a: loop {...}` | Loop label | | -| `; | `expr;` | Statement and item terminator | | -| `;` | `[...; len]` | Part of fixed-size array syntax | | -| `<<` | `expr << expr` | Left-shift | `Shl` | -| `<<=` | `var <<= expr` | Left-shift and assignment | `ShlAssign` | -| `<` | `expr < expr` | Less than comparison | `PartialOrd` | -| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | -| `=` | `var = expr`, `ident = type` | Assignment/equivalence | | -| `==` | `expr == expr` | Equality comparison | `PartialEq` | -| `=>` | `pat => expr` | Part of match arm syntax | | -| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | -| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | -| `>>` | `expr >> expr` | Right-shift | `Shr` | -| `>>=` | `var >>= expr` | Right-shift and assignment | `ShrAssign` | -| `@ | `ident @ pat` | Pattern binding | | -| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | -| `^=` | `var ^= expr` | Bitwise exclusive OR and assignment | `BitXorAssign` | -| `| | `pat | pat` | Pattern alternatives | | -| `|` | `expr | expr` | Bitwise OR | `BitOr` | -| `|=` | `var |= expr` | Bitwise OR and assignment | `BitOrAssign` | -| `||` | `expr || expr` | Short-circuiting logical OR | | -| `? | `expr?` | Error propagation | | - -## Non-operator Symbols - -The following tables contain all symbols that don’t function as operators; that +|Operator|Example|Explanation|Overloadable?| +|--------|-------|-----------|-------------| +|`!`|`ident!(...)`, `ident!{...}`, `ident![...]`|Macro expansion|| +|`!`|`!expr`|Bitwise or logical complement|`Not`| +|`!=`|`expr != expr`|Nonequality comparison|`PartialEq`| +|`%`|`expr % expr`|Arithmetic remainder|`Rem`| +|`%=`|`var %= expr`|Arithmetic remainder and assignment|`RemAssign`| +|`&`|`&expr`, `&mut expr`|Borrow|| +|`&`|`&type`, `&mut type`, `&'a type`, `&'a mut type`|Borrowed pointer type|| +|`&`|`expr & expr`|Bitwise AND|`BitAnd`| +|`&=`|`var &= expr`|Bitwise AND and assignment|`BitAndAssign`| +|`&&`|`expr && expr`|Short-circuiting logical AND|| +|`*`|`expr * expr`|Arithmetic multiplication|`Mul`| +|`*=`|`var *= expr`|Arithmetic multiplication and assignment|`MulAssign`| +|`*`|`*expr`|Dereference|`Deref`| +|`*`|`*const type`, `*mut type`|Raw pointer|| +|`+`|`trait + trait`, `'a + trait`|Compound type constraint|| +|`+`|`expr + expr`|Arithmetic addition|`Add`| +|`+=`|`var += expr`|Arithmetic addition and assignment|`AddAssign`| +|`,`|`expr, expr`|Argument and element separator|| +|`-`|`- expr`|Arithmetic negation|`Neg`| +|`-`|`expr - expr`|Arithmetic subtraction|`Sub`| +|`-=`|`var -= expr`|Arithmetic subtraction and assignment|`SubAssign`| +|`->`|`fn(...) -> type`, \|…\| -> type|Function and closure return type|| +|`.`|`expr.ident`|Member access|| +|`..`|`..`, `expr..`, `..expr`, `expr..expr`|Right-exclusive range literal|`PartialOrd`| +|`..=`|`..=expr`, `expr..=expr`|Right-inclusive range literal|`PartialOrd`| +|`..`|`..expr`|Struct literal update syntax|| +|`..`|`variant(x, ..)`, `struct_type { x, .. }`|“And the rest” pattern binding|| +|`...`|`expr...expr`|(Deprecated, use `..=` instead) In a pattern: inclusive range pattern|| +|`/`|`expr / expr`|Arithmetic division|`Div`| +|`/=`|`var /= expr`|Arithmetic division and assignment|`DivAssign`| +|`:`|`pat: type`, `ident: type`|Constraints|| +|`:`|`ident: expr`|Struct field initializer|| +|`:`|`'a: loop {...}`|Loop label|| +|`;`|`expr;`|Statement and item terminator|| +|`;`|`[...; len]`|Part of fixed-size array syntax|| +|`<<`|`expr << expr`|Left-shift|`Shl`| +|`<<=`|`var <<= expr`|Left-shift and assignment|`ShlAssign`| +|`<`|`expr < expr`|Less than comparison|`PartialOrd`| +|`<=`|`expr <= expr`|Less than or equal to comparison|`PartialOrd`| +|`=`|`var = expr`, `ident = type`|Assignment/equivalence|| +|`==`|`expr == expr`|Equality comparison|`PartialEq`| +|`=>`|`pat => expr`|Part of match arm syntax|| +|`>`|`expr > expr`|Greater than comparison|`PartialOrd`| +|`>=`|`expr >= expr`|Greater than or equal to comparison|`PartialOrd`| +|`>>`|`expr >> expr`|Right-shift|`Shr`| +|`>>=`|`var >>= expr`|Right-shift and assignment|`ShrAssign`| +|`@`|`ident @ pat`|Pattern binding|| +|`^`|`expr ^ expr`|Bitwise exclusive OR|`BitXor`| +|`^=`|`var ^= expr`|Bitwise exclusive OR and assignment|`BitXorAssign`| +|\||pat \| pat|Pattern alternatives|| +|\||expr \| expr|Bitwise OR|`BitOr`| +|\|=|var \|= expr|Bitwise OR and assignment|`BitOrAssign`| +|\|\||expr \|\| expr|Short-circuiting logical OR|| +|`?`|`expr?`|Error propagation|| + +### Non-operator Symbols + +The following list contains all symbols that don’t function as operators; that is, they don’t behave like a function or method call. Table B-2 shows symbols that appear on their own and are valid in a variety of @@ -228,149 +227,129 @@ locations. Table B-2: Stand-Alone Syntax -| Symbol | Explanation | -|---|---| -| `'ident | Named lifetime or loop label | -| `...u8`, `...i32`, `...f64`, `...usize`, and so on | Numeric literal of -specific type | -| `"..." | String literal | -| `r"..."`, `r#"..."#`, `r##"..."##`, and so on | Raw string literal; escape -characters not processed | -| `b"..."` | Byte string literal; constructs an array of bytes instead of a -string | -| `br"..."`, `br#"..."#`, `br##"..."##`, and so on | Raw byte string literal; -combination of raw and byte string literal | -| `'...' | Character literal | -| `b'...' | ASCII byte literal | -| `|…| expr | Closure | -| `! | Always-empty bottom type for diverging functions | -| `_ | “Ignored” pattern binding; also used to make integer literals readable | +|Symbol|Explanation| +|------|-----------| +|`'ident`|Named lifetime or loop label| +|`...u8`, `...i32`, `...f64`, `...usize`, etc.|Numeric literal of specific type| +|`"..."`|String literal| +|`r"..."`, `r#"..."#`, `r##"..."##`, etc.|Raw string literal, escape characters not processed| +|`b"..."`|Byte string literal; constructs an array of bytes instead of a string| +|`br"..."`, `br#"..."#`, `br##"..."##`, etc.|Raw byte string literal, combination of raw and byte string literal| +|`'...'`|Character literal| +|`b'...'`|ASCII byte literal| +|\|…\| expr|Closure| +|`!`|Always empty bottom type for diverging functions| +|`_`|“Ignored” pattern binding; also used to make integer literals readable| Table B-3 shows symbols that appear in the context of a path through the module hierarchy to an item. Table B-3: Path-Related Syntax -| Symbol | Explanation | -|---|---| -| `ident::ident | Namespace path | -| `::path` | Path relative to the crate root (that is, an explicitly absolute -path) | -| `self::path` | Path relative to the current module (that is, an explicitly -relative path) | -| `super::path` | Path relative to the parent of the current module | -| `type::ident`, `::ident | Associated constants, functions, and -types | -| `::...` | Associated item for a type that cannot be directly named (for -example, `<&T>::...`, `<[T]>::...`, and so on) | -| `trait::method(...)` | Disambiguating a method call by naming the trait that -defines it | -| `type::method(...)` | Disambiguating a method call by naming the type for -which it’s defined | -| `::method(...)` | Disambiguating a method call by naming the -trait and type | +|Symbol|Explanation| +|------|-----------| +|`ident::ident`|Namespace path| +|`::path`|Path relative to the extern prelude, where all other crates are rooted (i.e., an explicitly absolute path including crate name)| +|`self::path`|Path relative to the current module (i.e., an explicitly relative path).| +|`super::path`|Path relative to the parent of the current module| +|`type::ident`, `::ident`|Associated constants, functions, and types| +|`::...`|Associated item for a type that cannot be directly named (e.g., `<&T>::...`, `<[T]>::...`, etc.)| +|`trait::method(...)`|Disambiguating a method call by naming the trait that defines it| +|`type::method(...)`|Disambiguating a method call by naming the type for which it’s defined| +|`::method(...)`|Disambiguating a method call by naming the trait and type| Table B-4 shows symbols that appear in the context of using generic type parameters. Table B-4: Generics -| Symbol | Explanation | -|---|---| -| `path<...>` | Specifies parameters to a generic type in a type (for example, -`Vec`) | -| `path::<...>, method::<...>` | Specifies parameters to a generic type, -function, or method in an expression; often referred to as turbofish (for -example, `"42".parse::()`) | -| `fn ident<...> ...` | Define generic function | -| `struct ident<...> ...` | Define generic structure | -| `enum ident<...> ...` | Define generic enumeration | -| `impl<...> ...` | Define generic implementation | -| `for<...> type` | Higher-ranked lifetime bounds | -| `type` | A generic type where one or more associated types have -specific assignments (for example, `Iterator`) | +|Symbol|Explanation| +|------|-----------| +|`path<...>`|Specifies parameters to generic type in a type (e.g., `Vec`)| +|`path::<...>`, `method::<...>`|Specifies parameters to generic type, function, or method in an expression; often referred to as turbofish (e.g., `"42".parse::()`)| +|`fn ident<...> ...`|Define generic function| +|`struct ident<...> ...`|Define generic structure| +|`enum ident<...> ...`|Define generic enumeration| +|`impl<...> ...`|Define generic implementation| +|`for<...> type`|Higher-ranked lifetime bounds| +|`type`|A generic type where one or more associated types have specific assignments (e.g., `Iterator`)| Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds. Table B-5: Trait Bound Constraints -| Symbol | Explanation | -|---|---| -| T: U` | Generic parameter `T` constrained to types that implement `U` | -| `T: 'a` | Generic type `T` must outlive lifetime `'a` (meaning the type -cannot transitively contain any references with lifetimes shorter than `'a`) | -| `T: 'static` | Generic type `T` contains no borrowed references other than -`'static` ones | -| `'b: 'a` | Generic lifetime `'b` must outlive lifetime `'a` | -| `T: ?Sized` | Allow generic type parameter to be a dynamically sized type | -| `'a + trait`, `trait + trait` | Compound type constraint | +|Symbol|Explanation| +|------|-----------| +|`T: U`|Generic parameter `T` constrained to types that implement `U`| +|`T: 'a`|Generic type `T` must outlive lifetime `'a` (meaning the type cannot transitively contain any references with lifetimes shorter than `'a`)| +|`T: 'static`|Generic type `T` contains no borrowed references other than `'static` ones| +|`'b: 'a`|Generic lifetime `'b` must outlive lifetime `'a`| +|`T: ?Sized`|Allow generic type parameter to be a dynamically sized type| +|`'a + trait`, `trait + trait`|Compound type constraint| Table B-6 shows symbols that appear in the context of calling or defining macros and specifying attributes on an item. Table B-6: Macros and Attributes -| Symbol | Explanation | -|---|---| -| `#[meta]` | Outer attribute | -| `#![meta]` | Inner attribute | -| `$ident` | Macro substitution | -| `$ident:kind` | Macro capture | -| `$(…)…` | Macro repetition | -| `ident!(...)`, `ident!{...}`, `ident![...]` | Macro invocation | +|Symbol|Explanation| +|------|-----------| +|`#[meta]`|Outer attribute| +|`#![meta]`|Inner attribute| +|`$ident`|Macro substitution| +|`$ident:kind`|Macro capture| +|`$(…)…`|Macro repetition| +|`ident!(...)`, `ident!{...}`, `ident![...]`|Macro invocation| Table B-7 shows symbols that create comments. Table B-7: Comments -| Symbol | Explanation | -|---|---| -| `//` | Line comment | -| `//!` | Inner line doc comment | -| `///` | Outer line doc comment | -| `/*...*/` | Block comment | -| `/*!...*/` | Inner block doc comment | -| `/**...*/` | Outer block doc comment | +|Symbol|Explanation| +|------|-----------| +|`//`|Line comment| +|`//!`|Inner line doc comment| +|`///`|Outer line doc comment| +|`/*...*/`|Block comment| +|`/*!...*/`|Inner block doc comment| +|`/**...*/`|Outer block doc comment| Table B-8 shows symbols that appear in the context of using tuples. Table B-8: Tuples -| Symbol | Explanation | -|---|---| -| `() | Empty tuple (aka unit), both literal and type | -| `(expr)` | Parenthesized expression | -| `(expr,)` | Single-element tuple expression | -| `(type,)` | Single-element tuple type | -| `(expr, ...)` | Tuple expression | -| `(type, ...)` | Tuple type | -| `expr(expr, ...)` | Function call expression; also used to initialize tuple -`struct`s and tuple `enum` variants | -| `expr.0`, `expr.1`, and so on | Tuple indexing | +|Symbol|Explanation| +|------|-----------| +|`()`|Empty tuple (aka unit), both literal and type| +|`(expr)`|Parenthesized expression| +|`(expr,)`|Single-element tuple expression| +|`(type,)`|Single-element tuple type| +|`(expr, ...)`|Tuple expression| +|`(type, ...)`|Tuple type| +|`expr(expr, ...)`|Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants| +|`expr.0`, `expr.1`, etc.|Tuple indexing| -Table B-9 shows the contexts in which curly brackets are used. +Table B-9 shows the contexts in which curly braces are used. Table B-9: Curly Brackets -| Context | Explanation | -|---|---| -| `{...}` | Block expression | -| `Type {...}` | `struct` literal | +|Context|Explanation| +|-------|-----------| +|`{...}`|Block expression| +|`Type {...}`|`struct` literal| Table B-10 shows the contexts in which square brackets are used. Table B-10: Square Brackets -| Context | Explanation | -|---|---| -| `[...]` | Array literal | -| `[expr; len]` | Array literal containing `len` copies of `expr` | -| `[type; len]` | Array type containing `len` instances of `type` | -| `expr[expr]` | Collection indexing; overloadable (`Index`, `IndexMut`) | -| `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | Collection indexing -pretending to be collection slicing, using `Range`, `RangeFrom`, `RangeTo`, or -`RangeFull` as the “index” | +|Context|Explanation| +|-------|-----------| +|`[...]`|Array literal| +|`[expr; len]`|Array literal containing `len` copies of `expr`| +|`[type; len]`|Array type containing `len` instances of `type`| +|`expr[expr]`|Collection indexing. Overloadable (`Index`, `IndexMut`)| +|`expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]`|Collection indexing pretending to be collection slicing, using `Range`, `RangeFrom`, `RangeTo`, or `RangeFull` as the “index”| ## Appendix C: Derivable Traits @@ -389,10 +368,10 @@ library that you can use with `derive`. Each section covers: * Examples of operations that require the trait If you want different behavior from that provided by the `derive` attribute, -consult the standard library documentation for each trait for details on how to -manually implement them. +consult the standard library documentation +for each trait for details of how to manually implement them. -The traits listed here are the only ones defined by the standard library that +These traits listed here are the only ones defined by the standard library that can be implemented on your types using `derive`. Other traits defined in the standard library don’t have sensible default behavior, so it’s up to you to implement them in the way that makes sense for what you’re trying to accomplish. @@ -406,10 +385,11 @@ it can’t provide appropriate default behavior for you. The list of derivable traits provided in this appendix is not comprehensive: libraries can implement `derive` for their own traits, making the list of -traits you can use `derive` with truly open ended. Implementing `derive` -involves using a procedural macro, which is covered in “Macros” on page XX. +traits you can use `derive` with truly open-ended. Implementing `derive` +involves using a procedural macro, which is covered in the +“Macros” section of Chapter 20. -## Debug for Programmer Output +### Debug for Programmer Output The `Debug` trait enables debug formatting in format strings, which you indicate by adding `:?` within `{}` placeholders. @@ -418,12 +398,11 @@ The `Debug` trait allows you to print instances of a type for debugging purposes, so you and other programmers using your type can inspect an instance at a particular point in a program’s execution. -The `Debug` trait is required, for example, in the use of the `assert_eq!` -macro. This macro prints the values of instances given as arguments if the -equality assertion fails so programmers can see why the two instances weren’t -equal. +The `Debug` trait is required, for example, in using the `assert_eq!` macro. +This macro prints the values of instances given as arguments if the equality +assertion fails so programmers can see why the two instances weren’t equal. -## PartialEq and Eq for Equality Comparisons +### PartialEq and Eq for Equality Comparisons The `PartialEq` trait allows you to compare instances of a type to check for equality and enables use of the `==` and `!=` operators. @@ -440,14 +419,14 @@ for equality. The `Eq` trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The `Eq` trait can only be applied to types that also implement `PartialEq`, although not all types that -implement `PartialEq` can implement `Eq`. One example of this is floating-point -number types: the implementation of floating-point numbers states that two +implement `PartialEq` can implement `Eq`. One example of this is floating point +number types: the implementation of floating point numbers states that two instances of the not-a-number (`NaN`) value are not equal to each other. -An example of when `Eq` is required is for keys in a `HashMap` so that -the `HashMap` can tell whether two keys are the same. +An example of when `Eq` is required is for keys in a `HashMap` so the +`HashMap` can tell whether two keys are the same. -## PartialOrd and Ord for Ordering Comparisons +### PartialOrd and Ord for Ordering Comparisons The `PartialOrd` trait allows you to compare instances of a type for sorting purposes. A type that implements `PartialOrd` can be used with the `<`, `>`, @@ -457,9 +436,9 @@ that also implement `PartialEq`. Deriving `PartialOrd` implements the `partial_cmp` method, which returns an `Option` that will be `None` when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though -most values of that type can be compared, is the not-a-number (`NaN`) floating -point value. Calling `partial_cmp` with any floating-point number and the `NaN` -floating-point value will return `None`. +most values of that type can be compared, is the `NaN` floating point value. +Calling `partial_cmp` with any floating point number and the `NaN` floating +point value will return `None`. When derived on structs, `PartialOrd` compares two instances by comparing the value in each field in the order in which the fields appear in the struct @@ -481,12 +460,13 @@ implementation for `partial_cmp` does with `PartialOrd`. An example of when `Ord` is required is when storing values in a `BTreeSet`, a data structure that stores data based on the sort order of the values. -## Clone and Copy for Duplicating Values +### Clone and Copy for Duplicating Values The `Clone` trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap -data. See “Variables and Data Interacting with Clone” on page XX for more -information on `Clone`. +data. See Variables and Data Interacting with +Clone” in Chapter 4 +for more information on `Clone`. Deriving `Clone` implements the `clone` method, which when implemented for the whole type, calls `clone` on each of the parts of the type. This means all the @@ -495,11 +475,12 @@ fields or values in the type must also implement `Clone` to derive `Clone`. An example of when `Clone` is required is when calling the `to_vec` method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from `to_vec` will need to own its instances, so `to_vec` calls -`clone` on each item. Thus the type stored in the slice must implement `Clone`. +`clone` on each item. Thus, the type stored in the slice must implement `Clone`. The `Copy` trait allows you to duplicate a value by only copying bits stored on -the stack; no arbitrary code is necessary. See “Stack-Only Data: Copy” on page -XX for more information on `Copy`. +the stack; no arbitrary code is necessary. See “Stack-Only Data: +Copy” in Chapter 4 for more information on +`Copy`. The `Copy` trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code @@ -507,7 +488,7 @@ is being run. That way, all programmers can assume that copying a value will be very fast. You can derive `Copy` on any type whose parts all implement `Copy`. A type that -implements `Copy` must also implement `Clone` because a type that implements +implements `Copy` must also implement `Clone`, because a type that implements `Copy` has a trivial implementation of `Clone` that performs the same task as `Copy`. @@ -518,7 +499,7 @@ the code more concise. Everything possible with `Copy` you can also accomplish with `Clone`, but the code might be slower or have to use `clone` in places. -## Hash for Mapping a Value to a Value of Fixed Size +### Hash for Mapping a Value to a Value of Fixed Size The `Hash` trait allows you to take an instance of a type of arbitrary size and map that instance to a value of fixed size using a hash function. Deriving @@ -529,7 +510,7 @@ meaning all fields or values must also implement `Hash` to derive `Hash`. An example of when `Hash` is required is in storing keys in a `HashMap` to store data efficiently. -## Default for Default Values +### Default for Default Values The `Default` trait allows you to create a default value for a type. Deriving `Default` implements the `default` function. The derived implementation of the @@ -538,9 +519,10 @@ meaning all fields or values in the type must also implement `Default` to derive `Default`. The `Default::default` function is commonly used in combination with the struct -update syntax discussed in “Creating Instances from Other Instances with Struct -Update Syntax” on page XX. You can customize a few fields of a struct and then -set and use a default value for the rest of the fields by using +update syntax discussed in “Creating Instances From Other Instances With Struct +Update +Syntax” in Chapter 5. You can customize a few fields of a struct and then set +and use a default value for the rest of the fields by using `..Default::default()`. The `Default` trait is required when you use the method `unwrap_or_default` on @@ -548,23 +530,26 @@ The `Default` trait is required when you use the method `unwrap_or_default` on `unwrap_or_default` will return the result of `Default::default` for the type `T` stored in the `Option`. -## Appendix D: Useful Development Tools +## Appendix D - Useful Development Tools In this appendix, we talk about some useful development tools that the Rust project provides. We’ll look at automatic formatting, quick ways to apply warning fixes, a linter, and integrating with IDEs. -## Automatic Formatting with rustfmt +### Automatic Formatting with rustfmt The `rustfmt` tool reformats your code according to the community code style. Many collaborative projects use `rustfmt` to prevent arguments about which style to use when writing Rust: everyone formats their code using the tool. -Rust installations include `rustfmt` by default, so you should already have the -programs `rustfmt` and `cargo-fmt` on your system. These two commands are -analagous to `rustc` and `cargo` in that `rustfmt` allows finer-grained control -and `cargo-fmt` understands conventions of a project that uses Cargo. To format -any Cargo project, enter the following: +To install `rustfmt`, enter the following: + +``` +$ rustup component add rustfmt +``` + +This command gives you `rustfmt` and `cargo-fmt`, similar to how Rust gives you +both `rustc` and `cargo`. To format any Cargo project, enter the following: ``` $ cargo fmt @@ -574,46 +559,41 @@ Running this command reformats all the Rust code in the current crate. This should only change the code style, not the code semantics. For more information on `rustfmt`, see its documentation at *https://github.com/rust-lang/rustfmt*. -## Fix Your Code with rustfix +### Fix Your Code with rustfix -The `rustfix` tool is included with Rust installations and can automatically -fix compiler warnings that have a clear way to correct the problem that’s -likely what you want. You’ve probably seen compiler warnings before. For -example, consider this code: +The rustfix tool is included with Rust installations and can automatically fix +compiler warnings that have a clear way to correct the problem that’s likely +what you want. It’s likely you’ve seen compiler warnings before. For example, +consider this code: Filename: src/main.rs ``` -fn do_something() {} - fn main() { - for i in 0..100 { - do_something(); - } + let mut x = 42; + println!("{x}"); } ``` -Here, we’re calling the `do_something` function 100 times, but we never use the -variable `i` in the body of the `for` loop. Rust warns us about that: +Here, we’re defining variable `x` as mutable, but we never actually mutate it. +Rust warns us about that: ``` $ cargo build Compiling myprogram v0.1.0 (file:///projects/myprogram) -warning: unused variable: `i` - --> src/main.rs:4:9 +warning: variable does not need to be mutable + --> src/main.rs:2:9 | -4 | for i in 0..100 { - | ^ help: consider using `_i` instead +2 | let mut x = 0; + | ----^ + | | + | help: remove this `mut` | - = note: #[warn(unused_variables)] on by default - - Finished dev [unoptimized + debuginfo] target(s) in 0.50s + = note: `#[warn(unused_mut)]` on by default ``` -The warning suggests that we use `_i` as a name instead: the underscore -indicates that we intend for this variable to be unused. We can automatically -apply that suggestion using the `rustfix` tool by running the command `cargo -fix`: +The warning suggests that we remove the `mut` keyword. We can automatically +apply that suggestion using the `rustfix` tool by running the command `cargo fix`: ``` $ cargo fix @@ -628,25 +608,27 @@ code: Filename: src/main.rs ``` -fn do_something() {} - fn main() { - for _i in 0..100 { - do_something(); - } + let x = 42; + println!("{x}"); } ``` -The `for` loop variable is now named `_i`, and the warning no longer appears. +The `x` variable is now immutable, and the warning no longer appears. You can also use the `cargo fix` command to transition your code between -different Rust editions. Editions are covered in Appendix E. +different Rust editions. Editions are covered in Appendix E at *appendix-05-editions.md*. -## More Lints with Clippy +### More Lints with Clippy The Clippy tool is a collection of lints to analyze your code so you can catch -common mistakes and improve your Rust code. Clippy is included with standard -Rust installations. +common mistakes and improve your Rust code. + +To install Clippy, enter the following: + +``` +$ rustup component add clippy +``` To run Clippy’s lints on any Cargo project, enter the following: @@ -678,15 +660,13 @@ error: approximate value of `f{32, 64}::consts::PI` found | = note: `#[deny(clippy::approx_constant)]` on by default = help: consider using the constant directly - = help: for further information visit https://rust-lang.github.io/rust- -clippy/master/index.html#approx_constant + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant ``` This error lets you know that Rust already has a more precise `PI` constant defined, and that your program would be more correct if you used the constant -instead. You would then change your code to use the `PI` constant. - -The following code doesn’t result in any errors or warnings from Clippy: +instead. You would then change your code to use the `PI` constant. The +following code doesn’t result in any errors or warnings from Clippy: Filename: src/main.rs @@ -698,24 +678,22 @@ fn main() { } ``` -For more information on Clippy, see its documentation at -*https://github.com/rust-lang/rust-clippy**.* +For more information on Clippy, see its documentation at *https://github.com/rust-lang/rust-clippy*. -## IDE Integration Using rust-analyzer +### IDE Integration Using rust-analyzer -To help with IDE integration, the Rust community recommends using -`rust-analyzer`. This tool is a set of compiler-centric utilities that speak -Language Server Protocol, which is a specification for IDEs and programming -languages to communicate with each other. Different clients can use -`rust-analyzer`, such as the Rust analyzer plug-in for Visual Studio Code at -*https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer*. +To help IDE integration, the Rust community recommends using +`rust-analyzer`. This tool is a set of +compiler-centric utilities that speaks the Language Server Protocol, which is a specification for IDEs and programming languages to +communicate with each other. Different clients can use `rust-analyzer`, such as +the Rust analyzer plug-in for Visual Studio Code at *https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer*. -Visit the `rust-analyzer` project’s home page at -*https://rust-analyzer.github.io* for installation instructions, then install -the language server support in your particular IDE. Your IDE will gain -capabilities such as autocompletion, jump to definition, and inline errors +Visit the `rust-analyzer` project’s home page +for installation instructions, then install the language server support in your +particular IDE. Your IDE will gain abilities such as autocompletion, jump to +definition, and inline errors. -## Appendix E: Editions +## Appendix E - Editions In Chapter 1, you saw that `cargo new` adds a bit of metadata to your *Cargo.toml* file about an edition. This appendix talks about what that means! @@ -735,14 +713,15 @@ six-week release process. Editions serve different purposes for different people: * For active Rust users, a new edition brings together incremental changes into -an easy-to-understand package. + an easy-to-understand package. * For non-users, a new edition signals that some major advancements have -landed, which might make Rust worth another look. + landed, which might make Rust worth another look. * For those developing Rust, a new edition provides a rallying point for the -project as a whole. + project as a whole. -At the time of this writing, three Rust editions are available: Rust 2015, Rust -2018, and Rust 2021. This book is written using Rust 2021 edition idioms. +At the time of this writing, four Rust editions are available: Rust 2015, Rust +2018, Rust 2021, and Rust 2024. This book is written using Rust 2024 edition +idioms. The `edition` key in *Cargo.toml* indicates which edition the compiler should use for your code. If the key doesn’t exist, Rust uses `2015` as the edition @@ -768,8 +747,240 @@ made. However, in some cases, mainly when new keywords are added, some new features might only be available in later editions. You will need to switch editions if you want to take advantage of such features. -For more details, *The* *Edition Guide* at -*https://doc.rust-lang.org/stable/edition-guide* is a complete book about -editions that enumerates the differences between editions and explains how to -automatically upgrade your code to a new edition via `cargo fix`. +For more details, the *Edition Guide* at *https://doc.rust-lang.org/stable/edition-guide/* is a complete book +about editions that enumerates the differences between editions and explains +how to automatically upgrade your code to a new edition via `cargo fix`. + +## Appendix F: Translations of the Book + +For resources in languages other than English. Most are still in progress; see +the Translations label at *https://github.com/rust-lang/book/issues?q=is%3Aopen+is%3Aissue+label%3ATranslations* to help or let us know about a new translation! + +* Português at *https://github.com/rust-br/rust-book-pt-br* (BR) +* Português at *https://github.com/nunojesus/rust-book-pt-pt* (PT) +* 简体中文: KaiserY/trpl-zh-cn at *https://github.com/KaiserY/trpl-zh-cn*, gnu4cn/rust-lang-Zh_CN at *https://github.com/gnu4cn/rust-lang-Zh_CN* +* 正體中文 at *https://github.com/rust-tw/book-tw* +* Українська at *https://rust-lang-ua.github.io/rustbook_ukrainian* +* Español at *https://github.com/thecodix/book*, alternate at *https://github.com/ManRR/rust-book-es*, Español por RustLangES at *https://github.com/RustLangES/rust-book-es* +* Русский at *https://github.com/rust-lang-ru/book* +* 한국어 at *https://github.com/rust-kr/doc.rust-kr.org* +* 日本語 at *https://github.com/rust-lang-ja/book-ja* +* Français at *https://github.com/Jimskapt/rust-book-fr* +* Polski at *https://github.com/paytchoo/book-pl* +* Cebuano at *https://github.com/agentzero1/book* +* Tagalog at *https://github.com/josephace135/book* +* Esperanto at *https://github.com/psychoslave/Rust-libro* +* ελληνική at *https://github.com/TChatzigiannakis/rust-book-greek* +* Svenska at *https://github.com/sebras/book* +* Farsi at *https://github.com/RustFarsi/book*, Persian (FA) at *https://github.com/persian-rust/book* +* Deutsch at *https://github.com/rust-lang-de/rustbook-de* +* हिंदी at *https://github.com/venkatarun95/rust-book-hindi* +* ไทย at *https://github.com/rust-lang-th/book-th* +* Danske at *https://github.com/DanKHansen/book-dk* + +## Appendix G - How Rust is Made and “Nightly Rust” + +This appendix is about how Rust is made and how that affects you as a Rust +developer. + +### Stability Without Stagnation + +As a language, Rust cares a *lot* about the stability of your code. We want +Rust to be a rock-solid foundation you can build on, and if things were +constantly changing, that would be impossible. At the same time, if we can’t +experiment with new features, we may not find out important flaws until after +their release, when we can no longer change things. + +Our solution to this problem is what we call “stability without stagnation”, +and our guiding principle is this: you should never have to fear upgrading to a +new version of stable Rust. Each upgrade should be painless, but should also +bring you new features, fewer bugs, and faster compile times. + +### Choo, Choo! Release Channels and Riding the Trains + +Rust development operates on a *train schedule*. That is, all development is +done on the `master` branch of the Rust repository. Releases follow a software +release train model, which has been used by Cisco IOS and other software +projects. There are three *release channels* for Rust: + +* Nightly +* Beta +* Stable + +Most Rust developers primarily use the stable channel, but those who want to +try out experimental new features may use nightly or beta. + +Here’s an example of how the development and release process works: let’s +assume that the Rust team is working on the release of Rust 1.5. That release +happened in December of 2015, but it will provide us with realistic version +numbers. A new feature is added to Rust: a new commit lands on the `master` +branch. Each night, a new nightly version of Rust is produced. Every day is a +release day, and these releases are created by our release infrastructure +automatically. So as time passes, our releases look like this, once a night: + +``` +nightly: * - - * - - * +``` + +Every six weeks, it’s time to prepare a new release! The `beta` branch of the +Rust repository branches off from the `master` branch used by nightly. Now, +there are two releases: + +``` +nightly: * - - * - - * + | +beta: * +``` + +Most Rust users do not use beta releases actively, but test against beta in +their CI system to help Rust discover possible regressions. In the meantime, +there’s still a nightly release every night: + +``` +nightly: * - - * - - * - - * - - * + | +beta: * +``` + +Let’s say a regression is found. Good thing we had some time to test the beta +release before the regression snuck into a stable release! The fix is applied +to `master`, so that nightly is fixed, and then the fix is backported to the +`beta` branch, and a new release of beta is produced: + +``` +nightly: * - - * - - * - - * - - * - - * + | +beta: * - - - - - - - - * +``` + +Six weeks after the first beta was created, it’s time for a stable release! The +`stable` branch is produced from the `beta` branch: + +``` +nightly: * - - * - - * - - * - - * - - * - * - * + | +beta: * - - - - - - - - * + | +stable: * +``` + +Hooray! Rust 1.5 is done! However, we’ve forgotten one thing: because the six +weeks have gone by, we also need a new beta of the *next* version of Rust, 1.6. +So after `stable` branches off of `beta`, the next version of `beta` branches +off of `nightly` again: + +``` +nightly: * - - * - - * - - * - - * - - * - * - * + | | +beta: * - - - - - - - - * * + | +stable: * +``` + +This is called the “train model” because every six weeks, a release “leaves the +station”, but still has to take a journey through the beta channel before it +arrives as a stable release. + +Rust releases every six weeks, like clockwork. If you know the date of one Rust +release, you can know the date of the next one: it’s six weeks later. A nice +aspect of having releases scheduled every six weeks is that the next train is +coming soon. If a feature happens to miss a particular release, there’s no need +to worry: another one is happening in a short time! This helps reduce pressure +to sneak possibly unpolished features in close to the release deadline. + +Thanks to this process, you can always check out the next build of Rust and +verify for yourself that it’s easy to upgrade to: if a beta release doesn’t +work as expected, you can report it to the team and get it fixed before the +next stable release happens! Breakage in a beta release is relatively rare, but +`rustc` is still a piece of software, and bugs do exist. + +### Maintenance time + +The Rust project supports the most recent stable version. When a new stable +version is released, the old version reaches its end of life (EOL). This means +each version is supported for six weeks. + +### Unstable Features + +There’s one more catch with this release model: unstable features. Rust uses a +technique called “feature flags” to determine what features are enabled in a +given release. If a new feature is under active development, it lands on +`master`, and therefore, in nightly, but behind a *feature flag*. If you, as a +user, wish to try out the work-in-progress feature, you can, but you must be +using a nightly release of Rust and annotate your source code with the +appropriate flag to opt in. + +If you’re using a beta or stable release of Rust, you can’t use any feature +flags. This is the key that allows us to get practical use with new features +before we declare them stable forever. Those who wish to opt into the bleeding +edge can do so, and those who want a rock-solid experience can stick with +stable and know that their code won’t break. Stability without stagnation. + +This book only contains information about stable features, as in-progress +features are still changing, and surely they’ll be different between when this +book was written and when they get enabled in stable builds. You can find +documentation for nightly-only features online. + +### Rustup and the Role of Rust Nightly + +Rustup makes it easy to change between different release channels of Rust, on a +global or per-project basis. By default, you’ll have stable Rust installed. To +install nightly, for example: + +``` +$ rustup toolchain install nightly +``` + +You can see all of the *toolchains* (releases of Rust and associated +components) you have installed with `rustup` as well. Here’s an example on one +of your authors’ Windows computer: + +``` +> rustup toolchain list +stable-x86_64-pc-windows-msvc (default) +beta-x86_64-pc-windows-msvc +nightly-x86_64-pc-windows-msvc +``` + +As you can see, the stable toolchain is the default. Most Rust users use stable +most of the time. You might want to use stable most of the time, but use +nightly on a specific project, because you care about a cutting-edge feature. +To do so, you can use `rustup override` in that project’s directory to set the +nightly toolchain as the one `rustup` should use when you’re in that directory: + +``` +$ cd ~/projects/needs-nightly +$ rustup override set nightly +``` +Now, every time you call `rustc` or `cargo` inside of +*~/projects/needs-nightly*, `rustup` will make sure that you are using nightly +Rust, rather than your default of stable Rust. This comes in handy when you +have a lot of Rust projects! + +### The RFC Process and Teams + +So how do you learn about these new features? Rust’s development model follows +a *Request For Comments (RFC) process*. If you’d like an improvement in Rust, +you can write up a proposal, called an RFC. + +Anyone can write RFCs to improve Rust, and the proposals are reviewed and +discussed by the Rust team, which is comprised of many topic subteams. There’s +a full list of the teams on Rust’s website at *https://www.rust-lang.org/governance*, which includes teams for +each area of the project: language design, compiler implementation, +infrastructure, documentation, and more. The appropriate team reads the +proposal and the comments, writes some comments of their own, and eventually, +there’s consensus to accept or reject the feature. + +If the feature is accepted, an issue is opened on the Rust repository, and +someone can implement it. The person who implements it very well may not be the +person who proposed the feature in the first place! When the implementation is +ready, it lands on the `master` branch behind a feature gate, as we discussed +in the “Unstable Features” section. + +After some time, once Rust developers who use nightly releases have been able +to try out the new feature, team members will discuss the feature, how it’s +worked out on nightly, and decide if it should make it into stable Rust or not. +If the decision is to move forward, the feature gate is removed, and the +feature is now considered stable! It rides the trains into a new stable release +of Rust. diff --git a/nostarch/appendix_a.md b/nostarch/appendix_a.md index ca3883be48..26791b66ec 100644 --- a/nostarch/appendix_a.md +++ b/nostarch/appendix_a.md @@ -130,13 +130,12 @@ This code will compile without any errors. Note the `r#` prefix on the function name in its definition as well as where the function is called in `main`. Raw identifiers allow you to use any word you choose as an identifier, even if -that word happens to be a reserved keyword. This gives us more freedom to -choose identifier names, as well as lets us integrate with programs written in -a language where these words aren’t keywords. In addition, raw identifiers -allow you to use libraries written in a different Rust edition than your crate -uses. For example, `try` isn’t a keyword in the 2015 edition but is in the 2018 -and 2021 editions. If you depend on a library that is written using the 2015 +that word happens to be a reserved keyword. This gives us more freedom to choose +identifier names, as well as lets us integrate with programs written in a +language where these words aren’t keywords. In addition, raw identifiers allow +you to use libraries written in a different Rust edition than your crate uses. +For example, `try` isn’t a keyword in the 2015 edition but is in the 2018, 2021, +and 2024 editions. If you depend on a library that is written using the 2015 edition and has a `try` function, you’ll need to use the raw identifier syntax, -`r#try` in this case, to call that function from your 2021 edition code. See -Appendix E for more information on editions. - +`r#try` in this case, to call that function from your code on later editions. +See Appendix E for more information on editions. diff --git a/nostarch/chapter00.md b/nostarch/chapter00.md new file mode 100644 index 0000000000..1e80ef0dcc --- /dev/null +++ b/nostarch/chapter00.md @@ -0,0 +1,199 @@ + + +[TOC] + +# Introduction + +> Note: This edition of the book is the same as The Rust Programming +> Language at *https://nostarch.com/rust-programming-language-2nd-edition* available in print and ebook format from No Starch +> Press at *https://nostarch.com/*. + +Welcome to *The Rust Programming Language*, an introductory book about Rust. +The Rust programming language helps you write faster, more reliable software. +High-level ergonomics and low-level control are often at odds in programming +language design; Rust challenges that conflict. Through balancing powerful +technical capacity and a great developer experience, Rust gives you the option +to control low-level details (such as memory usage) without all the hassle +traditionally associated with such control. + +## Who Rust Is For + +Rust is ideal for many people for a variety of reasons. Let’s look at a few of +the most important groups. + +### Teams of Developers + +Rust is proving to be a productive tool for collaborating among large teams of +developers with varying levels of systems programming knowledge. Low-level code +is prone to various subtle bugs, which in most other languages can be caught +only through extensive testing and careful code review by experienced +developers. In Rust, the compiler plays a gatekeeper role by refusing to +compile code with these elusive bugs, including concurrency bugs. By working +alongside the compiler, the team can spend their time focusing on the program’s +logic rather than chasing down bugs. + +Rust also brings contemporary developer tools to the systems programming world: + +* Cargo, the included dependency manager and build tool, makes adding, + compiling, and managing dependencies painless and consistent across the Rust + ecosystem. +* The Rustfmt formatting tool ensures a consistent coding style across + developers. +* The rust-analyzer powers Integrated Development Environment (IDE) + integration for code completion and inline error messages. + +By using these and other tools in the Rust ecosystem, developers can be +productive while writing systems-level code. + +### Students + +Rust is for students and those who are interested in learning about systems +concepts. Using Rust, many people have learned about topics like operating +systems development. The community is very welcoming and happy to answer +student questions. Through efforts such as this book, the Rust teams want to +make systems concepts more accessible to more people, especially those new to +programming. + +### Companies + +Hundreds of companies, large and small, use Rust in production for a variety of +tasks, including command line tools, web services, DevOps tooling, embedded +devices, audio and video analysis and transcoding, cryptocurrencies, +bioinformatics, search engines, Internet of Things applications, machine +learning, and even major parts of the Firefox web browser. + +### Open Source Developers + +Rust is for people who want to build the Rust programming language, community, +developer tools, and libraries. We’d love to have you contribute to the Rust +language. + +### People Who Value Speed and Stability + +Rust is for people who crave speed and stability in a language. By speed, we +mean both how quickly Rust code can run and the speed at which Rust lets you +write programs. The Rust compiler’s checks ensure stability through feature +additions and refactoring. This is in contrast to the brittle legacy code in +languages without these checks, which developers are often afraid to modify. By +striving for zero-cost abstractions—higher-level features that compile to +lower-level code as fast as code written manually—Rust endeavors to make safe +code be fast code as well. + +The Rust language hopes to support many other users as well; those mentioned +here are merely some of the biggest stakeholders. Overall, Rust’s greatest +ambition is to eliminate the trade-offs that programmers have accepted for +decades by providing safety *and* productivity, speed *and* ergonomics. Give +Rust a try and see if its choices work for you. + +## Who This Book Is For + +This book assumes that you’ve written code in another programming language but +doesn’t make any assumptions about which one. We’ve tried to make the material +broadly accessible to those from a wide variety of programming backgrounds. We +don’t spend a lot of time talking about what programming *is* or how to think +about it. If you’re entirely new to programming, you would be better served by +reading a book that specifically provides an introduction to programming. + +## How to Use This Book + +In general, this book assumes that you’re reading it in sequence from front to +back. Later chapters build on concepts in earlier chapters, and earlier +chapters might not delve into details on a particular topic but will revisit +the topic in a later chapter. + +You’ll find two kinds of chapters in this book: concept chapters and project +chapters. In concept chapters, you’ll learn about an aspect of Rust. In project +chapters, we’ll build small programs together, applying what you’ve learned so +far. Chapters 2, 12, and 21 are project chapters; the rest are concept chapters. + +Chapter 1 explains how to install Rust, how to write a “Hello, world!” program, +and how to use Cargo, Rust’s package manager and build tool. Chapter 2 is a +hands-on introduction to writing a program in Rust, having you build up a +number guessing game. Here we cover concepts at a high level, and later +chapters will provide additional detail. If you want to get your hands dirty +right away, Chapter 2 is the place for that. Chapter 3 covers Rust features +that are similar to those of other programming languages, and in Chapter 4 +you’ll learn about Rust’s ownership system. If you’re a particularly meticulous +learner who prefers to learn every detail before moving on to the next, you +might want to skip Chapter 2 and go straight to Chapter 3, returning to Chapter +2 when you’d like to work on a project applying the details you’ve learned. + +Chapter 5 discusses structs and methods, and Chapter 6 covers enums, `match` +expressions, and the `if let` control flow construct. You’ll use structs and +enums to make custom types in Rust. + +In Chapter 7, you’ll learn about Rust’s module system and about privacy rules +for organizing your code and its public Application Programming Interface +(API). Chapter 8 discusses some common collection data structures that the +standard library provides, such as vectors, strings, and hash maps. Chapter 9 +explores Rust’s error-handling philosophy and techniques. + +Chapter 10 digs into generics, traits, and lifetimes, which give you the power +to define code that applies to multiple types. Chapter 11 is all about testing, +which even with Rust’s safety guarantees is necessary to ensure your program’s +logic is correct. In Chapter 12, we’ll build our own implementation of a subset +of functionality from the `grep` command line tool that searches for text +within files. For this, we’ll use many of the concepts we discussed in the +previous chapters. + +Chapter 13 explores closures and iterators: features of Rust that come from +functional programming languages. In Chapter 14, we’ll examine Cargo in more +depth and talk about best practices for sharing your libraries with others. +Chapter 15 discusses smart pointers that the standard library provides and the +traits that enable their functionality. + +In Chapter 16, we’ll walk through different models of concurrent programming and +talk about how Rust helps you to program in multiple threads fearlessly. In +Chapter 17, we will build on that by exploring Rust’s async and await syntax and +the lightweight concurrency model they support. + +Chapter 18 looks at how Rust idioms compare to object-oriented programming +principles you might be familiar with. + +Chapter 19 is a reference on patterns and pattern matching, which are powerful +ways of expressing ideas throughout Rust programs. Chapter 20 contains a +smorgasbord of advanced topics of interest, including unsafe Rust, macros, and +more about lifetimes, traits, types, functions, and closures. + +In Chapter 21, we’ll complete a project in which we’ll implement a low-level +multithreaded web server! + +Finally, some appendixes contain useful information about the language in a more +reference-like format. **Appendix A** covers Rust’s keywords, **Appendix B** +covers Rust’s operators and symbols, **Appendix C** covers derivable traits +provided by the standard library, **Appendix D** covers some useful development +tools, and **Appendix E** explains Rust editions. In **Appendix F**, you can +find translations of the book, and in **Appendix G** we’ll cover how Rust is +made and what nightly Rust is. + +There is no wrong way to read this book: if you want to skip ahead, go for it! +You might have to jump back to earlier chapters if you experience any +confusion. But do whatever works for you. + + + +An important part of the process of learning Rust is learning how to read the +error messages the compiler displays: these will guide you toward working code. +As such, we’ll provide many examples that don’t compile along with the error +message the compiler will show you in each situation. Know that if you enter +and run a random example, it may not compile! Make sure you read the +surrounding text to see whether the example you’re trying to run is meant to +error. Ferris will also help you distinguish code that isn’t meant to work: + +|Ferris|Meaning| +|------|-------| +|Ferris with a question mark|This code does not compile!| +|Ferris throwing up their hands|This code panics!| +|Ferris with one claw up, shrugging|This code does not produce the desired behavior.| + +In most situations, we’ll lead you to the correct version of any code that +doesn’t compile. + +## Source Code + +The source files from which this book is generated can be found on +GitHub at *https://github.com/rust-lang/book/tree/main/src*. diff --git a/nostarch/chapter01.md b/nostarch/chapter01.md index 3379b14f22..534d42da2b 100644 --- a/nostarch/chapter01.md +++ b/nostarch/chapter01.md @@ -21,10 +21,8 @@ The first step is to install Rust. We’ll download Rust through `rustup`, a command line tool for managing Rust versions and associated tools. You’ll need an internet connection for the download. -> Note: If you prefer not to use `rustup` for some reason, please see the Other -Rust Installation Methods page at -*https://forge.rust-lang.org/infra/other-installation-methods.html* for more -options. +> Note: If you prefer not to use `rustup` for some reason, please see the +> Other Rust Installation Methods page at *https://forge.rust-lang.org/infra/other-installation-methods.html* for more options. The following steps install the latest stable version of the Rust compiler. Rust’s stability guarantees ensure that all the examples in the book that @@ -34,20 +32,20 @@ warnings. In other words, any newer, stable version of Rust you install using these steps should work as expected with the content of this book. > ### Command Line Notation -> +> > In this chapter and throughout the book, we’ll show some commands used in the -terminal. Lines that you should enter in a terminal all start with `$`. You -don’t need to type the `$` character; it’s the command line prompt shown to -indicate the start of each command. Lines that don’t start with `$` typically -show the output of the previous command. Additionally, PowerShell-specific -examples will use `>` rather than `$`. +> terminal. Lines that you should enter in a terminal all start with `$`. You +> don’t need to type the `$` character; it’s the command line prompt shown to +> indicate the start of each command. Lines that don’t start with `$` typically +> show the output of the previous command. Additionally, PowerShell-specific +> examples will use `>` rather than `$`. ### Installing rustup on Linux or macOS If you’re using Linux or macOS, open a terminal and enter the following command: ``` -$ curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh +$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh ``` The command downloads a script and starts the installation of the `rustup` @@ -76,19 +74,11 @@ the `build-essential` package. ### Installing rustup on Windows -On Windows, go to *https://www.rust-lang.org/tools/install* and follow the -instructions for installing Rust. At some point in the installation, you’ll -receive a message explaining that you’ll also need the MSVC build tools for -Visual Studio 2013 or later. - -To acquire the build tools, you’ll need to install Visual Studio 2022 from -*https://visualstudio.microsoft.com/downloads*. When asked which workloads to -install, include: - -* “Desktop Development with C++” -* The Windows 10 or 11 SDK -* The English language pack component, along with any other language pack of -your choosing +On Windows, go to https://www.rust-lang.org/tools/install at *https://www.rust-lang.org/tools/install* and follow +the instructions for installing Rust. At some point in the installation, you’ll +be prompted to install Visual Studio. This provides a linker and the native +libraries needed to compile programs. If you need more help with this step, see +https://rust-lang.github.io/rustup/installation/windows-msvc.html at *https://rust-lang.github.io/rustup/installation/windows-msvc.html* The rest of this book uses commands that work in both *cmd.exe* and PowerShell. If there are specific differences, we’ll explain which to use. @@ -133,8 +123,7 @@ $ echo $PATH If that’s all correct and Rust still isn’t working, there are a number of places you can get help. Find out how to get in touch with other Rustaceans (a -silly nickname we call ourselves) on the community page at -*https://www.rust-lang.org/community*. +silly nickname we call ourselves) on the community page at *https://www.rust-lang.org/community*. ### Updating and Uninstalling @@ -162,6 +151,14 @@ Any time a type or function is provided by the standard library and you’re not sure what it does or how to use it, use the application programming interface (API) documentation to find out! +### Text Editors and Integrated Development Environments + +This book makes no assumptions about what tools you use to author Rust code. +Just about any text editor will get the job done! However, many text editors and +integrated development environments (IDEs) have built-in support for Rust. You +can always find a fairly current list of many editors and IDEs on the tools +page at *https://www.rust-lang.org/tools* on the Rust website. + ## Hello, World! Now that you’ve installed Rust, it’s time to write your first Rust program. @@ -169,12 +166,12 @@ It’s traditional when learning a new language to write a little program that prints the text `Hello, world!` to the screen, so we’ll do the same here! > Note: This book assumes basic familiarity with the command line. Rust makes -no specific demands about your editing or tooling or where your code lives, so -if you prefer to use an integrated development environment (IDE) instead of the -command line, feel free to use your favorite IDE. Many IDEs now have some -degree of Rust support; check the IDE’s documentation for details. The Rust -team has been focusing on enabling great IDE support via `rust-analyzer`. See -Appendix D for more details. +> no specific demands about your editing or tooling or where your code lives, so +> if you prefer to use an integrated development environment (IDE) instead of +> the command line, feel free to use your favorite IDE. Many IDEs now have some +> degree of Rust support; check the IDE’s documentation for details. The Rust +> team has been focusing on enabling great IDE support via `rust-analyzer`. See +> Appendix D for more details. ### Creating a Project Directory @@ -213,7 +210,7 @@ convention is to use an underscore to separate them. For example, use Now open the *main.rs* file you just created and enter the code in Listing 1-1. -Filename: main.rs +main.rs ``` fn main() { @@ -242,8 +239,9 @@ Hello, world! ``` Regardless of your operating system, the string `Hello, world!` should print to -the terminal. If you don’t see this output, refer back to “Troubleshooting” on -page XX for ways to get help. +the terminal. If you don’t see this output, refer back to the +“Troubleshooting” part of the Installation +section for ways to get help. If `Hello, world!` did print, congratulations! You’ve officially written a Rust program. That makes you a Rust programmer—welcome! @@ -269,32 +267,31 @@ function bodies. It’s good style to place the opening curly bracket on the sam line as the function declaration, adding one space in between. > Note: If you want to stick to a standard style across Rust projects, you can -use an automatic formatter tool called `rustfmt` to format your code in a -particular style (more on `rustfmt` in Appendix D). The Rust team has included -this tool with the standard Rust distribution, as `rustc` is, so it should -already be installed on your computer! +> use an automatic formatter tool called `rustfmt` to format your code in a +> particular style (more on `rustfmt` in +> Appendix D). The Rust team has included this tool +> with the standard Rust distribution, as `rustc` is, so it should already be +> installed on your computer! The body of the `main` function holds the following code: ``` - println!("Hello, world!"); +println!("Hello, world!"); ``` This line does all the work in this little program: it prints text to the -screen. There are four important details to notice here. +screen. There are three important details to notice here. -First, Rust style is to indent with four spaces, not a tab. - -Second, `println!` calls a Rust macro. If it had called a function instead, it +First, `println!` calls a Rust macro. If it had called a function instead, it would be entered as `println` (without the `!`). We’ll discuss Rust macros in -more detail in Chapter 19. For now, you just need to know that using a `!` +more detail in Chapter 20. For now, you just need to know that using a `!` means that you’re calling a macro instead of a normal function and that macros don’t always follow the same rules as functions. -Third, you see the `"Hello, world!"` string. We pass this string as an argument +Second, you see the `"Hello, world!"` string. We pass this string as an argument to `println!`, and the string is printed to the screen. -Fourth, we end the line with a semicolon (`;`), which indicates that this +Third, we end the line with a semicolon (`;`), which indicates that this expression is over and the next one is ready to begin. Most lines of Rust code end with a semicolon. @@ -342,8 +339,7 @@ From here, you run the *main* or *main.exe* file, like this: $ ./main # or .\main.exe on Windows ``` -If your *main.rs* is your “Hello, world!” program, this line prints `Hello, -world!` to your terminal. +If your *main.rs* is your “Hello, world!” program, this line prints `Hello, world!` to your terminal. If you’re more familiar with a dynamic language, such as Ruby, Python, or JavaScript, you might not be used to compiling and running a program as @@ -375,16 +371,16 @@ using Cargo, adding dependencies will be much easier to do. Because the vast majority of Rust projects use Cargo, the rest of this book assumes that you’re using Cargo too. Cargo comes installed with Rust if you -used the official installers discussed in “Installation” on page XX. If you -installed Rust through some other means, check whether Cargo is installed by -entering the following in your terminal: +used the official installers discussed in the +“Installation” section. If you installed Rust +through some other means, check whether Cargo is installed by entering the +following in your terminal: ``` $ cargo --version ``` -If you see a version number, you have it! If you see an error, such as `command -not found`, look at the documentation for your method of installation to +If you see a version number, you have it! If you see an error, such as `command not found`, look at the documentation for your method of installation to determine how to install Cargo separately. ### Creating a Project with Cargo @@ -412,27 +408,27 @@ Git files won’t be generated if you run `cargo new` within an existing Git repository; you can override this behavior by using `cargo new --vcs=git`. > Note: Git is a common version control system. You can change `cargo new` to -use a different version control system or no version control system by using -the `--vcs` flag. Run `cargo new --help` to see the available options. +> use a different version control system or no version control system by using +> the `--vcs` flag. Run `cargo new --help` to see the available options. Open *Cargo.toml* in your text editor of choice. It should look similar to the code in Listing 1-2. -Filename: Cargo.toml +Cargo.toml ``` [package] name = "hello_cargo" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] ``` Listing 1-2: Contents of *Cargo.toml* generated by `cargo new` -This file is in the *TOML* (*Tom’s Obvious, Minimal Language*) format, which is -Cargo’s configuration format. +This file is in the *TOML* (*Tom’s Obvious, Minimal +Language*) format, which is Cargo’s configuration format. The first line, `[package]`, is a section heading that indicates that the following statements are configuring a package. As we add more information to @@ -471,7 +467,8 @@ everything is in its place. If you started a project that doesn’t use Cargo, as we did with the “Hello, world!” project, you can convert it to a project that does use Cargo. Move the project code into the *src* directory and create an appropriate *Cargo.toml* -file. +file. One easy way to get that *Cargo.toml* file is to run `cargo init`, which +will create it for you automatically. ### Building and Running a Cargo Project @@ -495,8 +492,7 @@ $ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows Hello, world! ``` -If all goes well, `Hello, world!` should print to the terminal. Running `cargo -build` for the first time also causes Cargo to create a new file at the top +If all goes well, `Hello, world!` should print to the terminal. Running `cargo build` for the first time also causes Cargo to create a new file at the top level: *Cargo.lock*. This file keeps track of the exact versions of dependencies in your project. This project doesn’t have dependencies, so the file is a bit sparse. You won’t ever need to change this file manually; Cargo @@ -513,9 +509,7 @@ $ cargo run Hello, world! ``` -Using `cargo run` is more convenient than having to remember to run `cargo -build` and then use the whole path to the binary, so most developers use `cargo -run`. +Using `cargo run` is more convenient than having to remember to run `cargo build` and then use the whole path to the binary, so most developers use `cargo run`. Notice that this time we didn’t see output indicating that Cargo was compiling `hello_cargo`. Cargo figured out that the files hadn’t changed, so it didn’t @@ -554,9 +548,9 @@ Let’s recap what we’ve learned so far about Cargo: * We can build a project using `cargo build`. * We can build and run a project in one step using `cargo run`. * We can build a project without producing a binary to check for errors using -`cargo check`. + `cargo check`. * Instead of saving the result of the build in the same directory as our code, -Cargo stores it in the *target/debug* directory. + Cargo stores it in the *target/debug* directory. An additional advantage of using Cargo is that the commands are the same no matter which operating system you’re working on. So, at this point, we’ll no @@ -564,8 +558,7 @@ longer provide specific instructions for Linux and macOS versus Windows. ### Building for Release -When your project is finally ready for release, you can use `cargo build ---release` to compile it with optimizations. This command will create an +When your project is finally ready for release, you can use `cargo build --release` to compile it with optimizations. This command will create an executable in *target/release* instead of *target/debug*. The optimizations make your Rust code run faster, but turning them on lengthens the time it takes for your program to compile. This is why there are two different profiles: one @@ -593,8 +586,7 @@ $ cd someproject $ cargo build ``` -For more information about Cargo, check out its documentation at -*https://doc.rust-lang.org/cargo*. +For more information about Cargo, check out its documentation at *https://doc.rust-lang.org/cargo/*. ## Summary @@ -611,4 +603,3 @@ This is a great time to build a more substantial program to get used to reading and writing Rust code. So, in Chapter 2, we’ll build a guessing game program. If you would rather start by learning how common programming concepts work in Rust, see Chapter 3 and then return to Chapter 2. - diff --git a/nostarch/chapter02.md b/nostarch/chapter02.md index b01770dc5c..5384a0be2f 100644 --- a/nostarch/chapter02.md +++ b/nostarch/chapter02.md @@ -37,16 +37,22 @@ directory. Look at the generated *Cargo.toml* file: + + Filename: Cargo.toml ``` [package] name = "guessing_game" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at -https://doc.rust-lang.org/cargo/reference/manifest.html +edition = "2024" [dependencies] ``` @@ -68,8 +74,8 @@ using the `cargo run` command: ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 1.50s - Running `target/debug/guessing_game` + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s + Running `file:///projects/guessing_game/target/debug/guessing_game` Hello, world! ``` @@ -86,7 +92,7 @@ that input, and check that the input is in the expected form. To start, we’ll allow the player to input a guess. Enter the code in Listing 2-1 into *src/main.rs*. -Filename: src/main.rs +src/main.rs ``` use std::io; @@ -102,7 +108,7 @@ fn main() { .read_line(&mut guess) .expect("Failed to read line"); - println!("You guessed: {guess}"); + println!("You guessed: {}", guess); } ``` @@ -119,8 +125,7 @@ use std::io; By default, Rust has a set of items defined in the standard library that it brings into the scope of every program. This set is called the *prelude*, and -you can see everything in it at -*https://doc.rust-lang.org/std/prelude/index.html*. +you can see everything in it in the standard library documentation at *../std/prelude/index.html*. If a type you want to use isn’t in the prelude, you have to bring that type into scope explicitly with a `use` statement. Using the `std::io` library @@ -141,9 +146,9 @@ As you also learned in Chapter 1, `println!` is a macro that prints a string to the screen: ``` -println!("Guess the number!"); + println!("Guess the number!"); -println!("Please input your guess."); + println!("Please input your guess."); ``` This code is printing a prompt stating what the game is and requesting input @@ -154,7 +159,7 @@ from the user. Next, we’ll create a *variable* to store the user input, like this: ``` -let mut guess = String::new(); + let mut guess = String::new(); ``` Now the program is getting interesting! There’s a lot going on in this little @@ -167,8 +172,9 @@ let apples = 5; This line creates a new variable named `apples` and binds it to the value 5. In Rust, variables are immutable by default, meaning once we give the variable a value, the value won’t change. We’ll be discussing this concept in detail in -“Variables and Mutability” on page XX. To make a variable mutable, we add `mut` -before the variable name: +the “Variables and Mutability” +section in Chapter 3. To make a variable mutable, we add `mut` before the +variable name: ``` let apples = 5; // immutable @@ -176,16 +182,16 @@ let mut bananas = 5; // mutable ``` > Note: The `//` syntax starts a comment that continues until the end of the -line. Rust ignores everything in comments. We’ll discuss comments in more -detail in Chapter 3. +> line. Rust ignores everything in comments. We’ll discuss comments in more +> detail in Chapter 3. Returning to the guessing game program, you now know that `let mut guess` will introduce a mutable variable named `guess`. The equal sign (`=`) tells Rust we want to bind something to the variable now. On the right of the equal sign is the value that `guess` is bound to, which is the result of calling -`String::new`, a function that returns a new instance of a `String`. `String` -is a string type provided by the standard library that is a growable, UTF-8 -encoded bit of text. +`String::new`, a function that returns a new instance of a `String`. +`String` is a string type provided by the standard +library that is a growable, UTF-8 encoded bit of text. The `::` syntax in the `::new` line indicates that `new` is an associated function of the `String` type. An *associated function* is a function that’s @@ -204,23 +210,23 @@ the `stdin` function from the `io` module, which will allow us to handle user input: ``` -io::stdin() - .read_line(&mut guess) + io::stdin() + .read_line(&mut guess) ``` If we hadn’t imported the `io` library with `use std::io;` at the beginning of the program, we could still use the function by writing this function call as -`std::io::stdin`. The `stdin` function returns an instance of `std::io::Stdin`, -which is a type that represents a handle to the standard input for your -terminal. - -Next, the line `.read_line(&mut guess)` calls the `read_line` method on the -standard input handle to get input from the user. We’re also passing `&mut -guess` as the argument to `read_line` to tell it what string to store the user -input in. The full job of `read_line` is to take whatever the user types into -standard input and append that into a string (without overwriting its -contents), so we therefore pass that string as an argument. The string argument -needs to be mutable so the method can change the string’s content. +`std::io::stdin`. The `stdin` function returns an instance of +`std::io::Stdin`, which is a type that represents a +handle to the standard input for your terminal. + +Next, the line `.read_line(&mut guess)` calls the `read_line` method on the standard input handle to get input from the user. +We’re also passing `&mut guess` as the argument to `read_line` to tell it what +string to store the user input in. The full job of `read_line` is to take +whatever the user types into standard input and append that into a string +(without overwriting its contents), so we therefore pass that string as an +argument. The string argument needs to be mutable so the method can change the +string’s content. The `&` indicates that this argument is a *reference*, which gives you a way to let multiple parts of your code access one piece of data without needing to @@ -232,6 +238,10 @@ immutable by default. Hence, you need to write `&mut guess` rather than `&guess` to make it mutable. (Chapter 4 will explain references more thoroughly.) + + + + ### Handling Potential Failure with Result We’re still working on this line of code. We’re now discussing a third line of @@ -239,7 +249,7 @@ text, but note that it’s still part of a single logical line of code. The next part is this method: ``` -.expect("Failed to read line"); + .expect("Failed to read line"); ``` We could have written this code as: @@ -254,27 +264,27 @@ lines when you call a method with the `.method_name()` syntax. Now let’s discuss what this line does. As mentioned earlier, `read_line` puts whatever the user enters into the string -we pass to it, but it also returns a `Result` value. `Result` is an -*enumeration*, often called an *enum*, which is a type that can be in one of -multiple possible states. We call each possible state a *variant*. +we pass to it, but it also returns a `Result` value. `Result` is an *enumeration*, often called an *enum*, +which is a type that can be in one of multiple possible states. We call each +possible state a *variant*. -Chapter 6 will cover enums in more detail. The purpose of these `Result` types -is to encode error-handling information. +Chapter 6 will cover enums in more detail. The purpose +of these `Result` types is to encode error-handling information. `Result`’s variants are `Ok` and `Err`. The `Ok` variant indicates the -operation was successful, and inside `Ok` is the successfully generated value. -The `Err` variant means the operation failed, and `Err` contains information +operation was successful, and it contains the successfully generated value. +The `Err` variant means the operation failed, and it contains information about how or why the operation failed. Values of the `Result` type, like values of any type, have methods defined on -them. An instance of `Result` has an `expect` method that you can call. If this -instance of `Result` is an `Err` value, `expect` will cause the program to -crash and display the message that you passed as an argument to `expect`. If -the `read_line` method returns an `Err`, it would likely be the result of an -error coming from the underlying operating system. If this instance of `Result` -is an `Ok` value, `expect` will take the return value that `Ok` is holding and -return just that value to you so you can use it. In this case, that value is -the number of bytes in the user’s input. +them. An instance of `Result` has an `expect` method +that you can call. If this instance of `Result` is an `Err` value, `expect` +will cause the program to crash and display the message that you passed as an +argument to `expect`. If the `read_line` method returns an `Err`, it would +likely be the result of an error coming from the underlying operating system. +If this instance of `Result` is an `Ok` value, `expect` will take the return +value that `Ok` is holding and return just that value to you so you can use it. +In this case, that value is the number of bytes in the user’s input. If you don’t call `expect`, the program will compile, but you’ll get a warning: @@ -285,13 +295,17 @@ warning: unused `Result` that must be used --> src/main.rs:10:5 | 10 | io::stdin().read_line(&mut guess); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `#[warn(unused_must_use)]` on by default = note: this `Result` may be an `Err` variant, which should be handled + = note: `#[warn(unused_must_use)]` on by default +help: use `let _ = ...` to ignore the resulting value + | +10 | let _ = io::stdin().read_line(&mut guess); + | +++++++ warning: `guessing_game` (bin "guessing_game") generated 1 warning - Finished dev [unoptimized + debuginfo] target(s) in 0.59s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s ``` Rust warns that you haven’t used the `Result` value returned from `read_line`, @@ -299,7 +313,8 @@ indicating that the program hasn’t handled a possible error. The right way to suppress the warning is to actually write error-handling code, but in our case we just want to crash this program when a problem occurs, so we -can use `expect`. You’ll learn about recovering from errors in Chapter 9. +can use `expect`. You’ll learn about recovering from errors in Chapter +9. ### Printing Values with println! Placeholders @@ -307,7 +322,7 @@ Aside from the closing curly bracket, there’s only one more line to discuss in the code so far: ``` -println!("You guessed: {guess}"); + println!("You guessed: {}", guess); ``` This line prints the string that now contains the user’s input. The `{}` set of @@ -326,16 +341,22 @@ let y = 10; println!("x = {x} and y + 2 = {}", y + 2); ``` -This code would print `x = 5 and y = 12`. +This code would print `x = 5 and y + 2 = 12`. ### Testing the First Part Let’s test the first part of the guessing game. Run it using `cargo run`: + + ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 6.44s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.44s Running `target/debug/guessing_game` Guess the number! Please input your guess. @@ -352,8 +373,8 @@ Next, we need to generate a secret number that the user will try to guess. The secret number should be different every time so the game is fun to play more than once. We’ll use a random number between 1 and 100 so the game isn’t too difficult. Rust doesn’t yet include random number functionality in its standard -library. However, the Rust team does provide a `rand` crate at -*https://crates.io/crates/rand* with said functionality. +library. However, the Rust team does provide a `rand` crate at *https://crates.io/crates/rand* with +said functionality. ### Using a Crate to Get More Functionality @@ -369,21 +390,28 @@ following line to the bottom, beneath the `[dependencies]` section header that Cargo created for you. Be sure to specify `rand` exactly as we have here, with this version number, or the code examples in this tutorial may not work: + + Filename: Cargo.toml ``` [dependencies] -rand = "0.8.5" +rand = "0.8.4" ``` In the *Cargo.toml* file, everything that follows a header is part of that section that continues until another section starts. In `[dependencies]` you tell Cargo which external crates your project depends on and which versions of those crates you require. In this case, we specify the `rand` crate with the -semantic version specifier `0.8.5`. Cargo understands Semantic Versioning -(sometimes called *SemVer*), which is a standard for writing version numbers. -The specifier `0.8.5` is actually shorthand for `^0.8.5`, which means any -version that is at least 0.8.5 but below 0.9.0. +semantic version specifier `0.8.5`. Cargo understands Semantic +Versioning (sometimes called *SemVer*), which is a +standard for writing version numbers. The specifier `0.8.5` is actually +shorthand for `^0.8.5`, which means any version that is at least 0.8.5 but +below 0.9.0. Cargo considers these versions to have public APIs compatible with version 0.8.5, and this specification ensures you’ll get the latest patch release that @@ -393,29 +421,37 @@ is not guaranteed to have the same API as what the following examples use. Now, without changing any of the code, let’s build the project, as shown in Listing 2-2. + + + ``` $ cargo build - Updating crates.io index - Downloaded rand v0.8.5 - Downloaded libc v0.2.127 - Downloaded getrandom v0.2.7 - Downloaded cfg-if v1.0.0 - Downloaded ppv-lite86 v0.2.16 - Downloaded rand_chacha v0.3.1 - Downloaded rand_core v0.6.3 - Compiling rand_core v0.6.3 - Compiling libc v0.2.127 - Compiling getrandom v0.2.7 - Compiling cfg-if v1.0.0 - Compiling ppv-lite86 v0.2.16 - Compiling rand_chacha v0.3.1 - Compiling rand v0.8.5 - Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 2.53s -``` - -Listing 2-2: The output from running `cargo build` after adding the `rand` -crate as a dependency + Updating crates.io index + Locking 15 packages to latest Rust 1.85.0 compatible versions + Adding rand v0.8.5 (available: v0.9.0) + Compiling proc-macro2 v1.0.93 + Compiling unicode-ident v1.0.17 + Compiling libc v0.2.170 + Compiling cfg-if v1.0.0 + Compiling byteorder v1.5.0 + Compiling getrandom v0.2.15 + Compiling rand_core v0.6.4 + Compiling quote v1.0.38 + Compiling syn v2.0.98 + Compiling zerocopy-derive v0.7.35 + Compiling zerocopy v0.7.35 + Compiling ppv-lite86 v0.2.20 + Compiling rand_chacha v0.3.1 + Compiling rand v0.8.5 + Compiling guessing_game v0.1.0 (file:///projects/guessing_game) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s +``` + +Listing 2-2: The output from running `cargo build` after adding the rand crate as a dependency You may see different version numbers (but they will all be compatible with the code, thanks to SemVer!) and different lines (depending on the operating @@ -423,8 +459,8 @@ system), and the lines may be in a different order. When we include an external dependency, Cargo fetches the latest versions of everything that dependency needs from the *registry*, which is a copy of data -from Crates.io at *https://crates.io*. Crates.io is where people in the Rust -ecosystem post their open source Rust projects for others to use. +from Crates.io at *https://crates.io/*. Crates.io is where people in the Rust ecosystem +post their open source Rust projects for others to use. After updating the registry, Cargo checks the `[dependencies]` section and downloads any crates listed that aren’t already downloaded. In this case, @@ -442,10 +478,15 @@ do, it simply exits. If you open the *src/main.rs* file, make a trivial change, and then save it and build again, you’ll only see two lines of output: + + ``` $ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s ``` These lines show that Cargo only updates the build with your tiny change to the @@ -478,15 +519,22 @@ checked into source control with the rest of the code in your project. When you *do* want to update a crate, Cargo provides the command `update`, which will ignore the *Cargo.lock* file and figure out all the latest versions that fit your specifications in *Cargo.toml*. Cargo will then write those -versions to the *Cargo.lock* file. Otherwise, by default, Cargo will only look -for versions greater than 0.8.5 and less than 0.9.0. If the `rand` crate has +versions to the *Cargo.lock* file. In this case, Cargo will only look for +versions greater than 0.8.5 and less than 0.9.0. If the `rand` crate has released the two new versions 0.8.6 and 0.9.0, you would see the following if you ran `cargo update`: + + ``` $ cargo update Updating crates.io index - Updating rand v0.8.5 -> v0.8.6 + Locking 1 package to latest Rust 1.85.0 compatible version + Updating rand v0.8.5 -> v0.8.6 (available: v0.9.0) ``` Cargo ignores the 0.9.0 release. At this point, you would also notice a change @@ -503,28 +551,29 @@ The next time you run `cargo build`, Cargo will update the registry of crates available and reevaluate your `rand` requirements according to the new version you have specified. -There’s a lot more to say about Cargo and its ecosystem, which we’ll discuss in -Chapter 14, but for now, that’s all you need to know. Cargo makes it very easy -to reuse libraries, so Rustaceans are able to write smaller projects that are -assembled from a number of packages. +There’s a lot more to say about Cargo and its +ecosystem, which we’ll discuss in Chapter 14, but +for now, that’s all you need to know. Cargo makes it very easy to reuse +libraries, so Rustaceans are able to write smaller projects that are assembled +from a number of packages. ### Generating a Random Number Let’s start using `rand` to generate a number to guess. The next step is to update *src/main.rs*, as shown in Listing 2-3. -Filename: src/main.rs +src/main.rs ``` use std::io; -1 use rand::Rng; +use rand::Rng; fn main() { println!("Guess the number!"); - 2 let secret_number = rand::thread_rng().gen_range(1..=100); + let secret_number = rand::thread_rng().gen_range(1..=100); - 3 println!("The secret number is: {secret_number}"); + println!("The secret number is: {secret_number}"); println!("Please input your guess."); @@ -540,11 +589,11 @@ fn main() { Listing 2-3: Adding code to generate a random number -First we add the line `use rand::Rng;` [1]. The `Rng` trait defines methods -that random number generators implement, and this trait must be in scope for us -to use those methods. Chapter 10 will cover traits in detail. +First we add the line `use rand::Rng;`. The `Rng` trait defines methods that +random number generators implement, and this trait must be in scope for us to +use those methods. Chapter 10 will cover traits in detail. -Next, we’re adding two lines in the middle. In the first line [2], we call the +Next, we’re adding two lines in the middle. In the first line, we call the `rand::thread_rng` function that gives us the particular random number generator we’re going to use: one that is local to the current thread of execution and is seeded by the operating system. Then we call the `gen_range` @@ -556,24 +605,31 @@ the form `start..=end` and is inclusive on the lower and upper bounds, so we need to specify `1..=100` to request a number between 1 and 100. > Note: You won’t just know which traits to use and which methods and functions -to call from a crate, so each crate has documentation with instructions for -using it. Another neat feature of Cargo is that running the `cargo doc --open` -command will build documentation provided by all your dependencies locally and -open it in your browser. If you’re interested in other functionality in the -`rand` crate, for example, run `cargo doc --open` and click `rand` in the -sidebar on the left. - -The second new line [3] prints the secret number. This is useful while we’re +> to call from a crate, so each crate has documentation with instructions for +> using it. Another neat feature of Cargo is that running the `cargo doc --open` command will build documentation provided by all your dependencies +> locally and open it in your browser. If you’re interested in other +> functionality in the `rand` crate, for example, run `cargo doc --open` and +> click `rand` in the sidebar on the left. + +The second new line prints the secret number. This is useful while we’re developing the program to be able to test it, but we’ll delete it from the final version. It’s not much of a game if the program prints the answer as soon as it starts! Try running the program a few times: + + ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 2.53s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/guessing_game` Guess the number! The secret number is: 7 @@ -582,7 +638,7 @@ Please input your guess. You guessed: 4 $ cargo run - Finished dev [unoptimized + debuginfo] target(s) in 0.02s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/guessing_game` Guess the number! The secret number is: 83 @@ -600,19 +656,19 @@ Now that we have user input and a random number, we can compare them. That step is shown in Listing 2-4. Note that this code won’t compile just yet, as we will explain. -Filename: src/main.rs +src/main.rs ``` use rand::Rng; -1 use std::cmp::Ordering; +use std::cmp::Ordering; use std::io; fn main() { - --snip-- + // --snip-- println!("You guessed: {guess}"); - 2 match guess.3 cmp(&secret_number) { + match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), @@ -622,19 +678,19 @@ fn main() { Listing 2-4: Handling the possible return values of comparing two numbers -First we add another `use` statement [1], bringing a type called +First we add another `use` statement, bringing a type called `std::cmp::Ordering` into scope from the standard library. The `Ordering` type is another enum and has the variants `Less`, `Greater`, and `Equal`. These are the three outcomes that are possible when you compare two values. Then we add five new lines at the bottom that use the `Ordering` type. The -`cmp` method [3] compares two values and can be called on anything that can be +`cmp` method compares two values and can be called on anything that can be compared. It takes a reference to whatever you want to compare with: here it’s comparing `guess` to `secret_number`. Then it returns a variant of the `Ordering` enum we brought into scope with the `use` statement. We use a -`match` expression [2] to decide what to do next based on which variant of -`Ordering` was returned from the call to `cmp` with the values in `guess` and -`secret_number`. +`match` expression to decide what to do next based on +which variant of `Ordering` was returned from the call to `cmp` with the values +in `guess` and `secret_number`. A `match` expression is made up of *arms*. An arm consists of a *pattern* to match against, and the code that should be run if the value given to `match` @@ -642,7 +698,7 @@ fits that arm’s pattern. Rust takes the value given to `match` and looks through each arm’s pattern in turn. Patterns and the `match` construct are powerful Rust features: they let you express a variety of situations your code might encounter and they make sure you handle them all. These features will be -covered in detail in Chapter 6 and Chapter 18, respectively. +covered in detail in Chapter 6 and Chapter 19, respectively. Let’s walk through an example with the `match` expression we use here. Say that the user has guessed 50 and the randomly generated secret number this time is @@ -661,17 +717,39 @@ arm in this scenario. However, the code in Listing 2-4 won’t compile yet. Let’s try it: + + ``` $ cargo build + Compiling libc v0.2.86 + Compiling getrandom v0.2.2 + Compiling cfg-if v1.0.0 + Compiling ppv-lite86 v0.2.10 + Compiling rand_core v0.6.2 + Compiling rand_chacha v0.3.0 + Compiling rand v0.8.5 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) error[E0308]: mismatched types - --> src/main.rs:22:21 - | -22 | match guess.cmp(&secret_number) { - | ^^^^^^^^^^^^^^ expected struct `String`, found integer - | - = note: expected reference `&String` - found reference `&{integer}` + --> src/main.rs:22:21 + | +22 | match guess.cmp(&secret_number) { + | --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}` + | | + | arguments to this method are incorrect + | + = note: expected reference `&String` + found reference `&{integer}` +note: method defined here + --> file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/cmp.rs:964:8 + | +964 | fn cmp(&self, other: &Self) -> Ordering; + | ^^^ + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error ``` The core of the error states that there are *mismatched types*. Rust has a @@ -686,32 +764,35 @@ elsewhere that would cause Rust to infer a different numerical type. The reason for the error is that Rust cannot compare a string and a number type. Ultimately, we want to convert the `String` the program reads as input into a -real number type so we can compare it numerically to the secret number. We do -so by adding this line to the `main` function body: +number type so we can compare it numerically to the secret number. We do so by +adding this line to the `main` function body: Filename: src/main.rs ``` ---snip-- + // --snip-- + + let mut guess = String::new(); + + io::stdin() + .read_line(&mut guess) + .expect("Failed to read line"); -let mut guess = String::new(); + let guess: u32 = guess.trim().parse().expect("Please type a number!"); -io::stdin() - .read_line(&mut guess) - .expect("Failed to read line"); + println!("You guessed: {guess}"); -let guess: u32 = guess - .trim() - .parse() - .expect("Please type a number!"); + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => println!("You win!"), + } +``` -println!("You guessed: {guess}"); +The line is: -match guess.cmp(&secret_number) { - Ordering::Less => println!("Too small!"), - Ordering::Greater => println!("Too big!"), - Ordering::Equal => println!("You win!"), -} +``` +let guess: u32 = guess.trim().parse().expect("Please type a number!"); ``` We create a variable named `guess`. But wait, doesn’t the program already have @@ -719,26 +800,28 @@ a variable named `guess`? It does, but helpfully Rust allows us to shadow the previous value of `guess` with a new one. *Shadowing* lets us reuse the `guess` variable name rather than forcing us to create two unique variables, such as `guess_str` and `guess`, for example. We’ll cover this in more detail in -Chapter 3, but for now, know that this feature is often used when you want to -convert a value from one type to another type. +Chapter 3, but for now, know that this feature is +often used when you want to convert a value from one type to another type. We bind this new variable to the expression `guess.trim().parse()`. The `guess` in the expression refers to the original `guess` variable that contained the input as a string. The `trim` method on a `String` instance will eliminate any -whitespace at the beginning and end, which we must do to be able to compare the -string to the `u32`, which can only contain numerical data. The user must press -enter to satisfy `read_line` and input their guess, which adds a newline -character to the string. For example, if the user types `5` and presses enter, -`guess` looks like this: `5\n`. The `\n` represents “newline.” (On Windows, -pressing enter results in a carriage return and a newline, `\r\n`.) The `trim` -method eliminates `\n` or `\r\n`, resulting in just `5`. - -The `parse` method on strings converts a string to another type. Here, we use -it to convert from a string to a number. We need to tell Rust the exact number -type we want by using `let guess: u32`. The colon (`:`) after `guess` tells -Rust we’ll annotate the variable’s type. Rust has a few built-in number types; -the `u32` seen here is an unsigned, 32-bit integer. It’s a good default choice -for a small positive number. You’ll learn about other number types in Chapter 3. +whitespace at the beginning and end, which we must do before we can convert the +string to a `u32`, which can only contain numerical data. The user must press +enter to satisfy `read_line` and input their guess, which adds a +newline character to the string. For example, if the user types 5 and +presses enter, `guess` looks like this: `5\n`. The `\n` represents +“newline.” (On Windows, pressing enter results in a carriage return +and a newline, `\r\n`.) The `trim` method eliminates `\n` or `\r\n`, resulting +in just `5`. + +The `parse` method on strings converts a string to +another type. Here, we use it to convert from a string to a number. We need to +tell Rust the exact number type we want by using `let guess: u32`. The colon +(`:`) after `guess` tells Rust we’ll annotate the variable’s type. Rust has a +few built-in number types; the `u32` seen here is an unsigned, 32-bit integer. +It’s a good default choice for a small positive number. You’ll learn about +other number types in Chapter 3. Additionally, the `u32` annotation in this example program and the comparison with `secret_number` means Rust will infer that `secret_number` should be a @@ -747,22 +830,30 @@ type! The `parse` method will only work on characters that can logically be converted into numbers and so can easily cause errors. If, for example, the string -contained `A`👍`%`, there would be no way to convert that to a number. Because -it might fail, the `parse` method returns a `Result` type, much as the -`read_line` method does (discussed earlier in “Handling Potential Failure with -Result” on page XX). We’ll treat this `Result` the same way by using the -`expect` method again. If `parse` returns an `Err` `Result` variant because it -couldn’t create a number from the string, the `expect` call will crash the game -and print the message we give it. If `parse` can successfully convert the -string to a number, it will return the `Ok` variant of `Result`, and `expect` -will return the number that we want from the `Ok` value. +contained `A👍%`, there would be no way to convert that to a number. Because it +might fail, the `parse` method returns a `Result` type, much as the `read_line` +method does (discussed earlier in “Handling Potential Failure with +`Result`” at *#handling-potential-failure-with-result*). We’ll treat +this `Result` the same way by using the `expect` method again. If `parse` +returns an `Err` `Result` variant because it couldn’t create a number from the +string, the `expect` call will crash the game and print the message we give it. +If `parse` can successfully convert the string to a number, it will return the +`Ok` variant of `Result`, and `expect` will return the number that we want from +the `Ok` value. Let’s run the program now: + + ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 0.43s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/guessing_game` Guess the number! The secret number is: 58 @@ -788,19 +879,20 @@ more chances at guessing the number: Filename: src/main.rs ``` ---snip-- + // --snip-- -println!("The secret number is: {secret_number}"); + println!("The secret number is: {secret_number}"); -loop { - println!("Please input your guess."); + loop { + println!("Please input your guess."); - --snip-- + // --snip-- - match guess.cmp(&secret_number) { - Ordering::Less => println!("Too small!"), - Ordering::Greater => println!("Too big!"), - Ordering::Equal => println!("You win!"), + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => println!("You win!"), + } } } ``` @@ -811,15 +903,26 @@ and run the program again. The program will now ask for another guess forever, which actually introduces a new problem. It doesn’t seem like the user can quit! The user could always interrupt the program by using the keyboard shortcut -ctrl-C. But there’s another way to escape this insatiable monster, as mentioned -in the `parse` discussion in “Comparing the Guess to the Secret Number” on page -XX: if the user enters a non-number answer, the program will crash. We can take +ctrl-c. But there’s another way to escape this insatiable +monster, as mentioned in the `parse` discussion in “Comparing the Guess to the +Secret Number”: if +the user enters a non-number answer, the program will crash. We can take advantage of that to allow the user to quit, as shown here: + + ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 1.50s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s Running `target/debug/guessing_game` Guess the number! The secret number is: 59 @@ -837,8 +940,9 @@ You guessed: 59 You win! Please input your guess. quit -thread 'main' panicked at 'Please type a number!: ParseIntError -{ kind: InvalidDigit }', src/main.rs:28:47 + +thread 'main' panicked at src/main.rs:28:47: +Please type a number!: ParseIntError { kind: InvalidDigit } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` @@ -853,14 +957,16 @@ Let’s program the game to quit when the user wins by adding a `break` statemen Filename: src/main.rs ``` ---snip-- + // --snip-- -match guess.cmp(&secret_number) { - Ordering::Less => println!("Too small!"), - Ordering::Greater => println!("Too big!"), - Ordering::Equal => { - println!("You win!"); - break; + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => { + println!("You win!"); + break; + } + } } } ``` @@ -876,27 +982,26 @@ the user inputs a non-number, let’s make the game ignore a non-number so the user can continue guessing. We can do that by altering the line where `guess` is converted from a `String` to a `u32`, as shown in Listing 2-5. -Filename: src/main.rs +src/main.rs ``` ---snip-- + // --snip-- -io::stdin() - .read_line(&mut guess) - .expect("Failed to read line"); + io::stdin() + .read_line(&mut guess) + .expect("Failed to read line"); -let guess: u32 = match guess.trim().parse() { - Ok(num) => num, - Err(_) => continue, -}; + let guess: u32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; -println!("You guessed: {guess}"); + println!("You guessed: {guess}"); ---snip-- + // --snip-- ``` -Listing 2-5: Ignoring a non-number guess and asking for another guess instead -of crashing the program +Listing 2-5: Ignoring a non-number guess and asking for another guess instead of crashing the program We switch from an `expect` call to a `match` expression to move from crashing on an error to handling the error. Remember that `parse` returns a `Result` @@ -914,7 +1019,7 @@ If `parse` is *not* able to turn the string into a number, it will return an `Err` value that contains more information about the error. The `Err` value does not match the `Ok(num)` pattern in the first `match` arm, but it does match the `Err(_)` pattern in the second arm. The underscore, `_`, is a -catchall value; in this example, we’re saying we want to match all `Err` +catch-all value; in this example, we’re saying we want to match all `Err` values, no matter what information they have inside them. So the program will execute the second arm’s code, `continue`, which tells the program to go to the next iteration of the `loop` and ask for another guess. So, effectively, the @@ -922,10 +1027,19 @@ program ignores all errors that `parse` might encounter! Now everything in the program should work as expected. Let’s try it: + + ``` $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) - Finished dev [unoptimized + debuginfo] target(s) in 4.45s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s Running `target/debug/guessing_game` Guess the number! The secret number is: 61 @@ -950,7 +1064,7 @@ that the program is still printing the secret number. That worked well for testing, but it ruins the game. Let’s delete the `println!` that outputs the secret number. Listing 2-6 shows the final code. -Filename: src/main.rs +src/main.rs ``` use rand::Rng; @@ -1003,4 +1117,3 @@ covers concepts that most programming languages have, such as variables, data types, and functions, and shows how to use them in Rust. Chapter 4 explores ownership, a feature that makes Rust different from other languages. Chapter 5 discusses structs and method syntax, and Chapter 6 explains how enums work. - diff --git a/nostarch/chapter03.md b/nostarch/chapter03.md index 249032fd79..d389b80496 100644 --- a/nostarch/chapter03.md +++ b/nostarch/chapter03.md @@ -18,19 +18,20 @@ Specifically, you’ll learn about variables, basic types, functions, comments, and control flow. These foundations will be in every Rust program, and learning them early will give you a strong core to start from. -> ### Keywords -> +> #### Keywords +> > The Rust language has a set of *keywords* that are reserved for use by the -language only, much as in other languages. Keep in mind that you cannot use -these words as names of variables or functions. Most of the keywords have -special meanings, and you’ll be using them to do various tasks in your Rust -programs; a few have no current functionality associated with them but have -been reserved for functionality that might be added to Rust in the future. You -can find a list of the keywords in Appendix A. +> language only, much as in other languages. Keep in mind that you cannot use +> these words as names of variables or functions. Most of the keywords have +> special meanings, and you’ll be using them to do various tasks in your Rust +> programs; a few have no current functionality associated with them but have +> been reserved for functionality that might be added to Rust in the future. You +> can find a list of the keywords in Appendix A. ## Variables and Mutability -As mentioned in “Storing Values with Variables” on page XX, by default, +As mentioned in the “Storing Values with +Variables” section, by default, variables are immutable. This is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers. However, you still have the option to make your variables mutable. @@ -65,13 +66,18 @@ error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 | 2 | let x = 5; - | - - | | - | first assignment to `x` - | help: consider making this binding mutable: `mut x` + | - first assignment to `x` 3 | println!("The value of x is: {x}"); 4 | x = 6; | ^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +2 | let mut x = 5; + | +++ + +For more information about this error, try `rustc --explain E0384`. +error: could not compile `variables` (bin "variables") due to 1 previous error ``` This example shows how the compiler helps you find errors in your programs. @@ -79,8 +85,7 @@ Compiler errors can be frustrating, but really they only mean your program isn’t safely doing what you want it to do yet; they do *not* mean that you’re not a good programmer! Experienced Rustaceans still get compiler errors. -You received the error message `cannot assign twice to immutable variable `x`` -because you tried to assign a second value to the immutable `x` variable. +You received the error message `` cannot assign twice to immutable variable `x` `` because you tried to assign a second value to the immutable `x` variable. It’s important that we get compile-time errors when we attempt to change a value that’s designated as immutable because this very situation can lead to @@ -95,9 +100,10 @@ easier to reason through. But mutability can be very useful, and can make code more convenient to write. Although variables are immutable by default, you can make them mutable by -adding `mut` in front of the variable name as you did in Chapter 2. Adding -`mut` also conveys intent to future readers of the code by indicating that -other parts of the code will be changing this variable’s value. +adding `mut` in front of the variable name as you did in Chapter +2. Adding `mut` also conveys +intent to future readers of the code by indicating that other parts of the code +will be changing this variable’s value. For example, let’s change *src/main.rs* to the following: @@ -117,7 +123,7 @@ When we run the program now, we get this: ``` $ cargo run Compiling variables v0.1.0 (file:///projects/variables) - Finished dev [unoptimized + debuginfo] target(s) in 0.30s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/variables` The value of x is: 5 The value of x is: 6 @@ -136,9 +142,9 @@ and variables. First, you aren’t allowed to use `mut` with constants. Constants aren’t just immutable by default—they’re always immutable. You declare constants using the `const` keyword instead of the `let` keyword, and the type of the value *must* -be annotated. We’ll cover types and type annotations in “Data Types” on page -XX, so don’t worry about the details right now. Just know that you must always -annotate the type. +be annotated. We’ll cover types and type annotations in the next section, +“Data Types”, so don’t worry about the details +right now. Just know that you must always annotate the type. Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about. @@ -159,9 +165,9 @@ program). Rust’s naming convention for constants is to use all uppercase with underscores between words. The compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify, rather than setting this constant -to the value `10,800`. See the Rust Reference’s section on constant evaluation -at *https://doc.rust-lang.org/reference/const_eval.html* for more information -on what operations can be used when declaring constants. +to the value 10,800. See the Rust Reference’s section on constant +evaluation at *../reference/const_eval.html* for more information on what operations can be used +when declaring constants. Constants are valid for the entire time a program runs, within the scope in which they were declared. This property makes constants useful for values in @@ -176,8 +182,9 @@ hardcoded value needed to be updated in the future. ### Shadowing -As you saw in the guessing game tutorial in Chapter 2, you can declare a new -variable with the same name as a previous variable. Rustaceans say that the +As you saw in the guessing game tutorial in Chapter +2, you can declare a +new variable with the same name as a previous variable. Rustaceans say that the first variable is *shadowed* by the second, which means that the second variable is what the compiler will see when you use the name of the variable. In effect, the second variable overshadows the first, taking any uses of the @@ -213,7 +220,7 @@ When we run this program, it will output the following: ``` $ cargo run Compiling variables v0.1.0 (file:///projects/variables) - Finished dev [unoptimized + debuginfo] target(s) in 0.31s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/variables` The value of x in the inner scope is: 12 The value of x is: 6 @@ -232,8 +239,8 @@ program asks a user to show how many spaces they want between some text by inputting space characters, and then we want to store that input as a number: ``` -let spaces = " "; -let spaces = spaces.len(); + let spaces = " "; + let spaces = spaces.len(); ``` The first `spaces` variable is a string type and the second `spaces` variable @@ -243,8 +250,8 @@ the simpler `spaces` name. However, if we try to use `mut` for this, as shown here, we’ll get a compile-time error: ``` -let mut spaces = " "; -spaces = spaces.len(); + let mut spaces = " "; + spaces = spaces.len(); ``` The error says we’re not allowed to mutate a variable’s type: @@ -259,6 +266,9 @@ error[E0308]: mismatched types | ----- expected due to this value 3 | spaces = spaces.len(); | ^^^^^^^^^^^^ expected `&str`, found `usize` + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `variables` (bin "variables") due to 1 previous error ``` Now that we’ve explored how variables work, let’s look at more data types they @@ -274,8 +284,9 @@ Keep in mind that Rust is a *statically typed* language, which means that it must know the types of all variables at compile time. The compiler can usually infer what type we want to use based on the value and how we use it. In cases when many types are possible, such as when we converted a `String` to a numeric -type using `parse` in “Comparing the Guess to the Secret Number” on page XX, we -must add a type annotation, like this: +type using `parse` in the “Comparing the Guess to the Secret +Number” section in +Chapter 2, we must add a type annotation, like this: ``` let guess: u32 = "42".parse().expect("Not a number!"); @@ -288,11 +299,20 @@ information from us to know which type we want to use: ``` $ cargo build Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations) -error[E0282]: type annotations needed +error[E0284]: type annotations needed --> src/main.rs:2:9 | 2 | let guess = "42".parse().expect("Not a number!"); - | ^^^^^ consider giving `guess` a type + | ^^^^^ ----- type must be known at this point + | + = note: cannot satisfy `<_ as FromStr>::Err == _` +help: consider giving `guess` an explicit type + | +2 | let guess: /* Type */ = "42".parse().expect("Not a number!"); + | ++++++++++++ + +For more information about this error, try `rustc --explain E0284`. +error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error ``` You’ll see different type annotations for other data types. @@ -314,14 +334,14 @@ the type of an integer value. Table 3-1: Integer Types in Rust -| Length | Signed | Unsigned | -|---|---|---| -| 8-bit | `i8` | `u8` | -| 16-bit | `i16` | `u16` | -| 32-bit | `i32` | `u32` | -| 64-bit | `i64` | `u64` | -| 128-bit | `i128` | `u128` | -| arch | `isize` | `usize` | +|Length|Signed|Unsigned| +|------|------|--------| +|8-bit|`i8`|`u8`| +|16-bit|`i16`|`u16`| +|32-bit|`i32`|`u32`| +|64-bit|`i64`|`u64`| +|128-bit|`i128`|`u128`| +|arch|`isize`|`usize`| Each variant can be either signed or unsigned and has an explicit size. *Signed* and *unsigned* refer to whether it’s possible for the number to be @@ -332,11 +352,11 @@ the sign matters, a number is shown with a plus sign or a minus sign; however, when it’s safe to assume the number is positive, it’s shown with no sign. Signed numbers are stored using two’s complement representation. -Each signed variant can store numbers from -(2n - 1) to 2n - -1 - 1 inclusive, where *n* is the number of bits that variant uses. So an -`i8` can store numbers from -(27) to 27 - 1, which equals --128 to 127. Unsigned variants can store numbers from 0 to 2n - 1, -so a `u8` can store numbers from 0 to 28 - 1, which equals 0 to 255. +Each signed variant can store numbers from −(2n − 1) to 2n − +1 − 1 inclusive, where *n* is the number of bits that variant uses. So an +`i8` can store numbers from −(27) to 27 − 1, which equals +−128 to 127. Unsigned variants can store numbers from 0 to 2n − 1, +so a `u8` can store numbers from 0 to 28 − 1, which equals 0 to 255. Additionally, the `isize` and `usize` types depend on the architecture of the computer your program is running on, which is denoted in the table as “arch”: @@ -351,47 +371,49 @@ have the same value as if you had specified `1000`. Table 3-2: Integer Literals in Rust -| Number literals | Example | -|---|---| -| Decimal | `98_222` | -| Hex | `0xff` | -| Octal | `0o77` | -| Binary | `0b1111_0000` | -| Byte (`u8` only) | `b'A'` | +|Number literals|Example| +|---------------|-------| +|Decimal|`98_222`| +|Hex|`0xff`| +|Octal|`0o77`| +|Binary|`0b1111_0000`| +|Byte (`u8` only)|`b'A'`| So how do you know which type of integer to use? If you’re unsure, Rust’s defaults are generally good places to start: integer types default to `i32`. The primary situation in which you’d use `isize` or `usize` is when indexing some sort of collection. -> ### Integer Overflow -> +> ##### Integer Overflow +> > Let’s say you have a variable of type `u8` that can hold values between 0 and -255. If you try to change the variable to a value outside that range, such as -256, *integer overflow* will occur, which can result in one of two behaviors. -When you’re compiling in debug mode, Rust includes checks for integer overflow -that cause your program to *panic* at runtime if this behavior occurs. Rust -uses the term *panicking* when a program exits with an error; we’ll discuss -panics in more depth in “Unrecoverable Errors with panic!” on page XX. -> +> 255. If you try to change the variable to a value outside that range, such as +> 256, *integer overflow* will occur, which can result in one of two behaviors. +> When you’re compiling in debug mode, Rust includes checks for integer overflow +> that cause your program to *panic* at runtime if this behavior occurs. Rust +> uses the term *panicking* when a program exits with an error; we’ll discuss +> panics in more depth in the “Unrecoverable Errors with +> `panic!`” section in Chapter +> 9. +> > When you’re compiling in release mode with the `--release` flag, Rust does -*not* include checks for integer overflow that cause panics. Instead, if -overflow occurs, Rust performs *two’s complement wrapping*. In short, values -greater than the maximum value the type can hold “wrap around” to the minimum -of the values the type can hold. In the case of a `u8`, the value 256 becomes -0, the value 257 becomes 1, and so on. The program won’t panic, but the -variable will have a value that probably isn’t what you were expecting it to -have. Relying on integer overflow’s wrapping behavior is considered an error. -> +> *not* include checks for integer overflow that cause panics. Instead, if +> overflow occurs, Rust performs *two’s complement wrapping*. In short, values +> greater than the maximum value the type can hold “wrap around” to the minimum +> of the values the type can hold. In the case of a `u8`, the value 256 becomes +> 0, the value 257 becomes 1, and so on. The program won’t panic, but the +> variable will have a value that probably isn’t what you were expecting it to +> have. Relying on integer overflow’s wrapping behavior is considered an error. +> > To explicitly handle the possibility of overflow, you can use these families -of methods provided by the standard library for primitive numeric types: -> +> of methods provided by the standard library for primitive numeric types: +> > * Wrap in all modes with the `wrapping_*` methods, such as `wrapping_add`. > * Return the `None` value if there is overflow with the `checked_*` methods. -> * Return the value and a boolean indicating whether there was overflow with -the `overflowing_*` methods. +> * Return the value and a Boolean indicating whether there was overflow with +> the `overflowing_*` methods. > * Saturate at the value’s minimum or maximum values with the `saturating_*` -methods. +> methods. #### Floating-Point Types @@ -413,8 +435,7 @@ fn main() { } ``` -Floating-point numbers are represented according to the IEEE-754 standard. The -`f32` type is a single-precision float, and `f64` has double precision. +Floating-point numbers are represented according to the IEEE-754 standard. #### Numeric Operations @@ -446,8 +467,9 @@ fn main() { ``` Each expression in these statements uses a mathematical operator and evaluates -to a single value, which is then bound to a variable. Appendix B contains a -list of all operators that Rust provides. +to a single value, which is then bound to a variable. Appendix +B contains a list of all operators that Rust +provides. #### The Boolean Type @@ -466,8 +488,8 @@ fn main() { ``` The main way to use Boolean values is through conditionals, such as an `if` -expression. We’ll cover how `if` expressions work in Rust in “Control Flow” on -page XX. +expression. We’ll cover how `if` expressions work in Rust in the “Control +Flow” section. #### The Character Type @@ -493,7 +515,7 @@ Values range from `U+0000` to `U+D7FF` and `U+E000` to `U+10FFFF` inclusive. However, a “character” isn’t really a concept in Unicode, so your human intuition for what a “character” is may not match up with what a `char` is in Rust. We’ll discuss this topic in detail in “Storing UTF-8 Encoded Text with -Strings” on page XX. +Strings” in Chapter 8. ### Compound Types @@ -584,13 +606,14 @@ fn main() { } ``` -Arrays are useful when you want your data allocated on the stack rather than -the heap (we will discuss the stack and the heap more in Chapter 4) or when you -want to ensure you always have a fixed number of elements. An array isn’t as +Arrays are useful when you want your data allocated on the stack, the same as +the other types we have seen so far, rather than the heap (we will discuss the +stack and the heap more in Chapter 4) or when +you want to ensure you always have a fixed number of elements. An array isn’t as flexible as the vector type, though. A *vector* is a similar collection type -provided by the standard library that *is* allowed to grow or shrink in size. -If you’re unsure whether to use an array or a vector, chances are you should -use a vector. Chapter 8 discusses vectors in more detail. +provided by the standard library that *is* allowed to grow or shrink in size. If +you’re unsure whether to use an array or a vector, chances are you should use a +vector. Chapter 8 discusses vectors in more detail. However, arrays are more useful when you know the number of elements will not need to change. For example, if you were using the names of the month in a @@ -624,7 +647,7 @@ The array named `a` will contain `5` elements that will all be set to the value `3` initially. This is the same as writing `let a = [3, 3, 3, 3, 3];` but in a more concise way. -#### Accessing Array Elements +##### Accessing Array Elements An array is a single chunk of memory of a known, fixed size that can be allocated on the stack. You can access elements of an array using indexing, @@ -645,7 +668,7 @@ In this example, the variable named `first` will get the value `1` because that is the value at index `[0]` in the array. The variable named `second` will get the value `2` from index `[1]` in the array. -#### Invalid Array Element Access +##### Invalid Array Element Access Let’s see what happens if you try to access an element of an array that is past the end of the array. Say you run this code, similar to the guessing game in @@ -674,9 +697,7 @@ fn main() { let element = a[index]; - println!( - "The value of the element at index {index} is: {element}" - ); + println!("The value of the element at index {index} is: {element}"); } ``` @@ -685,9 +706,15 @@ enter `0`, `1`, `2`, `3`, or `4`, the program will print out the corresponding value at that index in the array. If you instead enter a number past the end of the array, such as `10`, you’ll see output like this: + + ``` -thread 'main' panicked at 'index out of bounds: the len is 5 but the index is -10', src/main.rs:19:19 +thread 'main' panicked at src/main.rs:19:19: +index out of bounds: the len is 5 but the index is 10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` @@ -750,7 +777,7 @@ should see the following output: ``` $ cargo run Compiling functions v0.1.0 (file:///projects/functions) - Finished dev [unoptimized + debuginfo] target(s) in 0.28s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s Running `target/debug/functions` Hello, world! Another function. @@ -789,7 +816,7 @@ Try running this program; you should get the following output: ``` $ cargo run Compiling functions v0.1.0 (file:///projects/functions) - Finished dev [unoptimized + debuginfo] target(s) in 1.21s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s Running `target/debug/functions` The value of x is: 5 ``` @@ -826,13 +853,12 @@ named `unit_label` and is type `char`. The function then prints text containing both the `value` and the `unit_label`. Let’s try running this code. Replace the program currently in your *functions* -project’s *src/main.rs* file with the preceding example and run it using `cargo -run`: +project’s *src/main.rs* file with the preceding example and run it using `cargo run`: ``` $ cargo run Compiling functions v0.1.0 (file:///projects/functions) - Finished dev [unoptimized + debuginfo] target(s) in 0.31s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/functions` The measurement is: 5h ``` @@ -850,15 +876,15 @@ understand. Other languages don’t have the same distinctions, so let’s look what statements and expressions are and how their differences affect the bodies of functions. -* **Statements **: are instructions that perform some action and do not return -a value. -* **Expressions **: evaluate to a resultant value. Let’s look at some examples. +* **Statements** are instructions that perform some action and do not return + a value. +* **Expressions** evaluate to a resultant value. Let’s look at some examples. We’ve actually already used statements and expressions. Creating a variable and assigning a value to it with the `let` keyword is a statement. In Listing 3-1, `let y = 6;` is a statement. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -869,7 +895,8 @@ fn main() { Listing 3-1: A `main` function declaration containing one statement Function definitions are also statements; the entire preceding example is a -statement in itself. +statement in itself. (As we will see below, *calling* a function is not a +statement.) Statements do not return values. Therefore, you can’t assign a `let` statement to another variable, as the following code tries to do; you’ll get an error: @@ -887,22 +914,29 @@ When you run this program, the error you’ll get looks like this: ``` $ cargo run Compiling functions v0.1.0 (file:///projects/functions) -error: expected expression, found statement (`let`) +error: expected expression, found `let` statement --> src/main.rs:2:14 | 2 | let x = (let y = 6); - | ^^^^^^^^^ + | ^^^ | - = note: variable declaration using `let` is a statement + = note: only supported directly in conditions of `if` and `while` expressions -error[E0658]: `let` expressions in this position are unstable - --> src/main.rs:2:14 +warning: unnecessary parentheses around assigned value + --> src/main.rs:2:13 | 2 | let x = (let y = 6); - | ^^^^^^^^^ + | ^ ^ + | + = note: `#[warn(unused_parens)]` on by default +help: remove these parentheses | - = note: see issue #53667 for -more information +2 - let x = (let y = 6); +2 + let x = let y = 6; + | + +warning: `functions` (bin "functions") generated 1 warning +error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted ``` The `let y = 6` statement does not return a value, so there isn’t anything for @@ -923,18 +957,27 @@ Filename: src/main.rs ``` fn main() { - 1 let y = {2 + let y = { let x = 3; - 3 x + 1 + x + 1 }; println!("The value of y is: {y}"); } ``` -The expression [2] is a block that, in this case, evaluates to `4`. That value -gets bound to `y` as part of the `let` statement [1]. Note the line without a -semicolon at the end [3], which is unlike most of the lines you’ve seen so far. +This expression: + +``` +{ + let x = 3; + x + 1 +} +``` + +is a block that, in this case, evaluates to `4`. That value gets bound to `y` +as part of the `let` statement. Note that the `x + 1` line doesn’t have a +semicolon at the end, which is unlike most of the lines you’ve seen so far. Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value. Keep this in mind as you explore function return values and expressions @@ -972,7 +1015,7 @@ running this code; the output should look like this: ``` $ cargo run Compiling functions v0.1.0 (file:///projects/functions) - Finished dev [unoptimized + debuginfo] target(s) in 0.30s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/functions` The value of x is: 5 ``` @@ -1038,7 +1081,10 @@ error[E0308]: mismatched types | | | implicitly returns `()` as its body has no tail or `return` expression 8 | x + 1; - | - help: remove this semicolon + | - help: remove this semicolon to return this value + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `functions` (bin "functions") due to 1 previous error ``` The main error message, `mismatched types`, reveals the core issue with this @@ -1067,9 +1113,9 @@ comment continues until the end of the line. For comments that extend beyond a single line, you’ll need to include `//` on each line, like this: ``` -// So we’re doing something complicated here, long enough that we need +// So we're doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will -// explain what’s going on. +// explain what's going on. ``` Comments can also be placed at the end of lines containing code: @@ -1078,7 +1124,7 @@ Filename: src/main.rs ``` fn main() { - let lucky_number = 7; // I’m feeling lucky today + let lucky_number = 7; // I'm feeling lucky today } ``` @@ -1089,13 +1135,14 @@ Filename: src/main.rs ``` fn main() { - // I’m feeling lucky today + // I'm feeling lucky today let lucky_number = 7; } ``` Rust also has another kind of comment, documentation comments, which we’ll -discuss in “Publishing a Crate to Crates.io” on page XX. +discuss in the “Publishing a Crate to Crates.io” +section of Chapter 14. ## Control Flow @@ -1132,8 +1179,8 @@ this case, the condition checks whether or not the variable `number` has a value less than 5. We place the block of code to execute if the condition is `true` immediately after the condition inside curly brackets. Blocks of code associated with the conditions in `if` expressions are sometimes called *arms*, -just like the arms in `match` expressions that we discussed in “Comparing the -Guess to the Secret Number” on page XX. +just like the arms in `match` expressions that we discussed in the “Comparing +the Guess to the Secret Number” section of Chapter 2. Optionally, we can also include an `else` expression, which we chose to do here, to give the program an alternative block of code to execute should the @@ -1146,7 +1193,7 @@ Try running this code; you should see the following output: ``` $ cargo run Compiling branches v0.1.0 (file:///projects/branches) - Finished dev [unoptimized + debuginfo] target(s) in 0.31s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was true ``` @@ -1163,7 +1210,7 @@ Run the program again, and look at the output: ``` $ cargo run Compiling branches v0.1.0 (file:///projects/branches) - Finished dev [unoptimized + debuginfo] target(s) in 0.31s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was false ``` @@ -1195,6 +1242,9 @@ error[E0308]: mismatched types | 4 | if number { | ^^^^^^ expected `bool`, found integer + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `branches` (bin "branches") due to 1 previous error ``` The error indicates that Rust expected a `bool` but got an integer. Unlike @@ -1247,7 +1297,7 @@ see the following output: ``` $ cargo run Compiling branches v0.1.0 (file:///projects/branches) - Finished dev [unoptimized + debuginfo] target(s) in 0.31s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` number is divisible by 3 ``` @@ -1268,7 +1318,7 @@ Rust branching construct called `match` for these cases. Because `if` is an expression, we can use it on the right side of a `let` statement to assign the outcome to a variable, as in Listing 3-2. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1287,7 +1337,7 @@ expression. Run this code to see what happens: ``` $ cargo run Compiling branches v0.1.0 (file:///projects/branches) - Finished dev [unoptimized + debuginfo] target(s) in 0.30s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/branches` The value of number is: 5 ``` @@ -1323,10 +1373,12 @@ error[E0308]: `if` and `else` have incompatible types --> src/main.rs:4:44 | 4 | let number = if condition { 5 } else { "six" }; - | - ^^^^^ expected integer, found -`&str` + | - ^^^^^ expected integer, found `&str` | | | expected because of this + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `branches` (bin "branches") due to 1 previous error ``` The expression in the `if` block evaluates to an integer, and the expression in @@ -1366,14 +1418,20 @@ fn main() { ``` When we run this program, we’ll see `again!` printed over and over continuously -until we stop the program manually. Most terminals support the keyboard -shortcut ctrl-C to interrupt a program that is stuck in a continual loop. Give -it a try: +until we stop the program manually. Most terminals support the keyboard shortcut +ctrl-c to interrupt a program that is stuck in a continual +loop. Give it a try: + + ``` $ cargo run Compiling loops v0.1.0 (file:///projects/loops) - Finished dev [unoptimized + debuginfo] target(s) in 0.29s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s Running `target/debug/loops` again! again! @@ -1382,15 +1440,15 @@ again! ^Cagain! ``` -The symbol `^C` represents where you pressed ctrl-C. You may or may not see the -word `again!` printed after the `^C`, depending on where the code was in the -loop when it received the interrupt signal. +The symbol `^C` represents where you pressed ctrl-c. You +may or may not see the word `again!` printed after the `^C`, depending on where +the code was in the loop when it received the interrupt signal. Fortunately, Rust also provides a way to break out of a loop using code. You can place the `break` keyword within the loop to tell the program when to stop -executing the loop. Recall that we did this in the guessing game in “Quitting -After a Correct Guess” on page XX to exit the program when the user won the -game by guessing the correct number. +executing the loop. Recall that we did this in the guessing game in the +“Quitting After a Correct Guess” section of Chapter 2 to exit the program when the user won the game by +guessing the correct number. We also used `continue` in the guessing game, which in a loop tells the program to skip over any remaining code in this iteration of the loop and go to the @@ -1429,6 +1487,9 @@ and then check whether the `counter` is equal to `10`. When it is, we use the semicolon to end the statement that assigns the value to `result`. Finally, we print the value in `result`, which in this case is `20`. +You can also `return` from inside a loop. While `break` only exits the current +loop, `return` always exits the current function. + #### Loop Labels to Disambiguate Between Multiple Loops If you have loops within loops, `break` and `continue` apply to the innermost @@ -1463,12 +1524,12 @@ fn main() { The outer loop has the label `'counting_up`, and it will count up from 0 to 2. The inner loop without a label counts down from 10 to 9. The first `break` that -doesn’t specify a label will exit the inner loop only. The `break -'counting_up;` statement will exit the outer loop. This code prints: +doesn’t specify a label will exit the inner loop only. The `break 'counting_up;` statement will exit the outer loop. This code prints: ``` +$ cargo run Compiling loops v0.1.0 (file:///projects/loops) - Finished dev [unoptimized + debuginfo] target(s) in 0.58s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s Running `target/debug/loops` count = 0 remaining = 10 @@ -1492,7 +1553,7 @@ that Rust has a built-in language construct for it, called a `while` loop. In Listing 3-3, we use `while` to loop the program three times, counting down each time, and then, after the loop, print a message and exit. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1508,8 +1569,7 @@ fn main() { } ``` -Listing 3-3: Using a `while` loop to run code while a condition evaluates to -`true` +Listing 3-3: Using a `while` loop to run code while a condition holds true This construct eliminates a lot of nesting that would be necessary if you used `loop`, `if`, `else`, and `break`, and it’s clearer. While a condition @@ -1517,11 +1577,11 @@ evaluates to `true`, the code runs; otherwise, it exits the loop. #### Looping Through a Collection with for -You can choose to use the `while` construct to loop over the elements of a +You can also use the `while` construct to loop over the elements of a collection, such as an array. For example, the loop in Listing 3-4 prints each element in the array `a`. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1546,7 +1606,7 @@ element in the array: ``` $ cargo run Compiling loops v0.1.0 (file:///projects/loops) - Finished dev [unoptimized + debuginfo] target(s) in 0.32s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s Running `target/debug/loops` the value is: 10 the value is: 20 @@ -1569,7 +1629,7 @@ index is within the bounds of the array on every iteration through the loop. As a more concise alternative, you can use a `for` loop and execute some code for each item in a collection. A `for` loop looks like the code in Listing 3-5. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1626,8 +1686,7 @@ do the following: * Convert temperatures between Fahrenheit and Celsius. * Generate the *n*th Fibonacci number. * Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” -taking advantage of the repetition in the song. + taking advantage of the repetition in the song. When you’re ready to move on, we’ll talk about a concept in Rust that *doesn’t* commonly exist in other programming languages: ownership. - diff --git a/nostarch/chapter04.md b/nostarch/chapter04.md index 11f7f4944b..bf56558793 100644 --- a/nostarch/chapter04.md +++ b/nostarch/chapter04.md @@ -36,65 +36,68 @@ working through some examples that focus on a very common data structure: strings. > ### The Stack and the Heap -> +> > Many programming languages don’t require you to think about the stack and the -heap very often. But in a systems programming language like Rust, whether a -value is on the stack or the heap affects how the language behaves and why you -have to make certain decisions. Parts of ownership will be described in -relation to the stack and the heap later in this chapter, so here is a brief -explanation in preparation. -> +> heap very often. But in a systems programming language like Rust, whether a +> value is on the stack or the heap affects how the language behaves and why +> you have to make certain decisions. Parts of ownership will be described in +> relation to the stack and the heap later in this chapter, so here is a brief +> explanation in preparation. +> > Both the stack and the heap are parts of memory available to your code to use -at runtime, but they are structured in different ways. The stack stores values -in the order it gets them and removes the values in the opposite order. This is -referred to as *last in, first out*. Think of a stack of plates: when you add -more plates, you put them on top of the pile, and when you need a plate, you -take one off the top. Adding or removing plates from the middle or bottom -wouldn’t work as well! Adding data is called *pushing onto the stack*, and -removing data is called *popping off the stack*. All data stored on the stack -must have a known, fixed size. Data with an unknown size at compile time or a -size that might change must be stored on the heap instead. -> +> at runtime, but they are structured in different ways. The stack stores +> values in the order it gets them and removes the values in the opposite +> order. This is referred to as *last in, first out*. Think of a stack of +> plates: when you add more plates, you put them on top of the pile, and when +> you need a plate, you take one off the top. Adding or removing plates from +> the middle or bottom wouldn’t work as well! Adding data is called *pushing +> onto the stack*, and removing data is called *popping off the stack*. All +> data stored on the stack must have a known, fixed size. Data with an unknown +> size at compile time or a size that might change must be stored on the heap +> instead. +> > The heap is less organized: when you put data on the heap, you request a -certain amount of space. The memory allocator finds an empty spot in the heap -that is big enough, marks it as being in use, and returns a *pointer*, which is -the address of that location. This process is called *allocating on the heap* -and is sometimes abbreviated as just *allocating* (pushing values onto the -stack is not considered allocating). Because the pointer to the heap is a -known, fixed size, you can store the pointer on the stack, but when you want -the actual data, you must follow the pointer. Think of being seated at a -restaurant. When you enter, you state the number of people in your group, and -the host finds an empty table that fits everyone and leads you there. If -someone in your group comes late, they can ask where you’ve been seated to find -you. -> +> certain amount of space. The memory allocator finds an empty spot in the heap +> that is big enough, marks it as being in use, and returns a *pointer*, which +> is the address of that location. This process is called *allocating on the +> heap* and is sometimes abbreviated as just *allocating* (pushing values onto +> the stack is not considered allocating). Because the pointer to the heap is a +> known, fixed size, you can store the pointer on the stack, but when you want +> the actual data, you must follow the pointer. Think of being seated at a +> restaurant. When you enter, you state the number of people in your group, and +> the host finds an empty table that fits everyone and leads you there. If +> someone in your group comes late, they can ask where you’ve been seated to +> find you. +> > Pushing to the stack is faster than allocating on the heap because the -allocator never has to search for a place to store new data; that location is -always at the top of the stack. Comparatively, allocating space on the heap -requires more work because the allocator must first find a big enough space to -hold the data and then perform bookkeeping to prepare for the next allocation. -> +> allocator never has to search for a place to store new data; that location is +> always at the top of the stack. Comparatively, allocating space on the heap +> requires more work because the allocator must first find a big enough space +> to hold the data and then perform bookkeeping to prepare for the next +> allocation. +> > Accessing data in the heap is slower than accessing data on the stack because -you have to follow a pointer to get there. Contemporary processors are faster -if they jump around less in memory. Continuing the analogy, consider a server -at a restaurant taking orders from many tables. It’s most efficient to get all -the orders at one table before moving on to the next table. Taking an order -from table A, then an order from table B, then one from A again, and then one -from B again would be a much slower process. By the same token, a processor can -do its job better if it works on data that’s close to other data (as it is on -the stack) rather than farther away (as it can be on the heap). -> +> you have to follow a pointer to get there. Contemporary processors are faster +> if they jump around less in memory. Continuing the analogy, consider a server +> at a restaurant taking orders from many tables. It’s most efficient to get +> all the orders at one table before moving on to the next table. Taking an +> order from table A, then an order from table B, then one from A again, and +> then one from B again would be a much slower process. By the same token, a +> processor can do its job better if it works on data that’s close to other +> data (as it is on the stack) rather than farther away (as it can be on the +> heap). +> > When your code calls a function, the values passed into the function -(including, potentially, pointers to data on the heap) and the function’s local -variables get pushed onto the stack. When the function is over, those values -get popped off the stack. -> +> (including, potentially, pointers to data on the heap) and the function’s +> local variables get pushed onto the stack. When the function is over, those +> values get popped off the stack. +> > Keeping track of what parts of code are using what data on the heap, -minimizing the amount of duplicate data on the heap, and cleaning up unused -data on the heap so you don’t run out of space are all problems that ownership -addresses. Once you understand ownership, you won’t need to think about the -stack and the heap very often, but knowing that the main purpose of ownership -is to manage heap data can help explain why it works the way it does. +> minimizing the amount of duplicate data on the heap, and cleaning up unused +> data on the heap so you don’t run out of space are all problems that ownership +> addresses. Once you understand ownership, you won’t need to think about the +> stack and the heap very often, but knowing that the main purpose of ownership +> is to manage heap data can help explain why it works the way it does. ### Ownership Rules @@ -126,12 +129,13 @@ hardcoded into the text of our program. The variable is valid from the point at which it’s declared until the end of the current *scope*. Listing 4-1 shows a program with comments annotating where the variable `s` would be valid. + ``` -{ // s is not valid here, since it's not yet declared - let s = "hello"; // s is valid from this point forward + { // s is not valid here, it’s not yet declared + let s = "hello"; // s is valid from this point forward - // do stuff with s -} // this scope is now over, and s is no longer valid + // do stuff with s + } // this scope is now over, and s is no longer valid ``` Listing 4-1: A variable and the scope in which it is valid @@ -148,13 +152,13 @@ understanding by introducing the `String` type. ### The String Type To illustrate the rules of ownership, we need a data type that is more complex -than those we covered in “Data Types” on page XX. The types covered previously -are of a known size, can be stored on the stack and popped off the stack when -their scope is over, and can be quickly and trivially copied to make a new, -independent instance if another part of code needs to use the same value in a -different scope. But we want to look at data that is stored on the heap and -explore how Rust knows when to clean up that data, and the `String` type is a -great example. +than those we covered in the “Data Types” section +of Chapter 3. The types covered previously are of a known size, can be stored +on the stack and popped off the stack when their scope is over, and can be +quickly and trivially copied to make a new, independent instance if another +part of code needs to use the same value in a different scope. But we want to +look at data that is stored on the heap and explore how Rust knows when to +clean up that data, and the `String` type is a great example. We’ll concentrate on the parts of `String` that relate to ownership. These aspects also apply to other complex data types, whether they are provided by @@ -177,18 +181,19 @@ let s = String::from("hello"); The double colon `::` operator allows us to namespace this particular `from` function under the `String` type rather than using some sort of name like -`string_from`. We’ll discuss this syntax more in “Method Syntax” on page XX, -and when we talk about namespacing with modules in “Paths for Referring to an -Item in the Module Tree” on page XX. +`string_from`. We’ll discuss this syntax more in the “Method +Syntax” section of Chapter 5, and when we talk +about namespacing with modules in “Paths for Referring to an Item in the +Module Tree” in Chapter 7. This kind of string *can* be mutated: ``` -let mut s = String::from("hello"); + let mut s = String::from("hello"); -s.push_str(", world!"); // push_str() appends a literal to a String + s.push_str(", world!"); // push_str() appends a literal to a String -println!("{s}"); // This will print `hello, world!` + println!("{s}"); // This will print `hello, world!` ``` So, what’s the difference here? Why can `String` be mutated but literals @@ -209,7 +214,7 @@ to hold the contents. This means: * The memory must be requested from the memory allocator at runtime. * We need a way of returning this memory to the allocator when we’re done with -our `String`. + our `String`. That first part is done by us: when we call `String::from`, its implementation requests the memory it needs. This is pretty much universal in programming @@ -230,38 +235,44 @@ variable that owns it goes out of scope. Here’s a version of our scope example from Listing 4-1 using a `String` instead of a string literal: ``` -{ - let s = String::from("hello"); // s is valid from this point forward + { + let s = String::from("hello"); // s is valid from this point forward - // do stuff with s -} // this scope is now over, and s is no - // longer valid + // do stuff with s + } // this scope is now over, and s is no + // longer valid ``` There is a natural point at which we can return the memory our `String` needs to the allocator: when `s` goes out of scope. When a variable goes out of -scope, Rust calls a special function for us. This function is called `drop`, -and it’s where the author of `String` can put the code to return the memory. -Rust calls `drop` automatically at the closing curly bracket. +scope, Rust calls a special function for us. This function is called +`drop`, and it’s where the author of `String` can put +the code to return the memory. Rust calls `drop` automatically at the closing +curly bracket. > Note: In C++, this pattern of deallocating resources at the end of an item’s -lifetime is sometimes called *Resource Acquisition Is Initialization* *(RAII)*. -The `drop` function in Rust will be familiar to you if you’ve used RAII -patterns. +> lifetime is sometimes called *Resource Acquisition Is Initialization (RAII)*. +> The `drop` function in Rust will be familiar to you if you’ve used RAII +> patterns. This pattern has a profound impact on the way Rust code is written. It may seem simple right now, but the behavior of code can be unexpected in more complicated situations when we want to have multiple variables use the data we’ve allocated on the heap. Let’s explore some of those situations now. + + + + #### Variables and Data Interacting with Move Multiple variables can interact with the same data in different ways in Rust. Let’s look at an example using an integer in Listing 4-2. + ``` -let x = 5; -let y = x; + let x = 5; + let y = x; ``` Listing 4-2: Assigning the integer value of variable `x` to `y` @@ -275,8 +286,8 @@ onto the stack. Now let’s look at the `String` version: ``` -let s1 = String::from("hello"); -let s2 = s1; + let s1 = String::from("hello"); + let s2 = s1; ``` This looks very similar, so we might assume that the way it works would be the @@ -289,8 +300,14 @@ the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents. -Figure 4-1: Representation in memory of a `String` holding the value `"hello"` -bound to `s1` +Two tables: the first table contains the representation of s1 on the
+stack, consisting of its length (5), capacity (5), and a pointer to the first
+value in the second table. The second table contains the representation of the
+string data on the heap, byte by byte. + +Figure 4-1: Representation in memory of a `String` +holding the value `"hello"` bound to `s1` The length is how much memory, in bytes, the contents of the `String` are currently using. The capacity is the total amount of memory, in bytes, that the @@ -303,16 +320,24 @@ pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to. In other words, the data representation in memory looks like Figure 4-2. -Figure 4-2: Representation in memory of the variable `s2` that has a copy of -the pointer, length, and capacity of `s1` +Three tables: tables s1 and s2 representing those strings on the
+stack, respectively, and both pointing to the same string data on the heap. + +Figure 4-2: Representation in memory of the variable `s2` +that has a copy of the pointer, length, and capacity of `s1` The representation does *not* look like Figure 4-3, which is what memory would look like if Rust instead copied the heap data as well. If Rust did this, the operation `s2 = s1` could be very expensive in terms of runtime performance if the data on the heap were large. -Figure 4-3: Another possibility for what `s2 = s1` might do if Rust copied the -heap data as well +Four tables: two tables representing the stack data for s1 and s2,
+and each points to its own copy of string data on the heap. + +Figure 4-3: Another possibility for what `s2 = s1` might +do if Rust copied the heap data as well Earlier, we said that when a variable goes out of scope, Rust automatically calls the `drop` function and cleans up the heap memory for that variable. But @@ -328,27 +353,37 @@ out of scope. Check out what happens when you try to use `s1` after `s2` is created; it won’t work: ``` -let s1 = String::from("hello"); -let s2 = s1; + let s1 = String::from("hello"); + let s2 = s1; -println!("{s1}, world!"); + println!("{s1}, world!"); ``` You’ll get an error like this because Rust prevents you from using the invalidated reference: ``` +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) error[E0382]: borrow of moved value: `s1` - --> src/main.rs:5:28 + --> src/main.rs:5:15 | 2 | let s1 = String::from("hello"); - | -- move occurs because `s1` has type `String`, which - does not implement the `Copy` trait + | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{s1}, world!"); - | ^^ value borrowed here after move + | ^^^^ value borrowed here after move + | + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider cloning the value if the performance cost is acceptable + | +3 | let s2 = s1.clone(); + | ++++++++ + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `ownership` (bin "ownership") due to 1 previous error ``` If you’ve heard the terms *shallow copy* and *deep copy* while working with @@ -358,7 +393,14 @@ because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a *move*. In this example, we would say that `s1` was *moved* into `s2`. So, what actually happens is shown in Figure 4-4. -Figure 4-4: Representation in memory after `s1` has been invalidated +Three tables: tables s1 and s2 representing those strings on the
+stack, respectively, and both pointing to the same string data on the heap.
+Table s1 is grayed out be-cause s1 is no longer valid; only s2 can be used to
+access the heap data. + +Figure 4-4: Representation in memory after `s1` has been +invalidated That solves our problem! With only `s2` valid, when it goes out of scope it alone will free the memory, and we’re done. @@ -367,6 +409,44 @@ In addition, there’s a design choice that’s implied by this: Rust will never automatically create “deep” copies of your data. Therefore, any *automatic* copying can be assumed to be inexpensive in terms of runtime performance. +#### Scope and Assignment + +The inverse of this is true for the relationship between scoping, ownership, and +memory being freed via the `drop` function as well. When you assign a completely +new value to an existing variable, Rust will call `drop` and free the original +value’s memory immediately. Consider this code, for example: + +``` + let mut s = String::from("hello"); + s = String::from("ahoy"); + + println!("{s}, world!"); +``` + +We initially declare a variable `s` and bind it to a `String` with the value +`"hello"`. Then we immediately create a new `String` with the value `"ahoy"` and +assign it to `s`. At this point, nothing is referring to the original value on +the heap at all. + +One table s representing the string value on the stack, pointing to
+the second piece of string data (ahoy) on the heap, with the original string
+data (hello) grayed out because it cannot be accessed anymore. + +Figure 4-5: Representation in memory after the initial +value has been replaced in its entirety. + +The original string thus immediately goes out of scope. Rust will run the `drop` +function on it and its memory will be freed right away. When we print the value +at the end, it will be `"ahoy, world!"`. + + + + + #### Variables and Data Interacting with Clone If we *do* want to deeply copy the heap data of the `String`, not just the @@ -377,10 +457,10 @@ programming languages, you’ve probably seen them before. Here’s an example of the `clone` method in action: ``` -let s1 = String::from("hello"); -let s2 = s1.clone(); + let s1 = String::from("hello"); + let s2 = s1.clone(); -println!("s1 = {s1}, s2 = {s2}"); + println!("s1 = {s1}, s2 = {s2}"); ``` This works just fine and explicitly produces the behavior shown in Figure 4-3, @@ -396,10 +476,10 @@ There’s another wrinkle we haven’t talked about yet. This code using integers—part of which was shown in Listing 4-2—works and is valid: ``` -let x = 5; -let y = x; + let x = 5; + let y = x; -println!("x = {x}, y = {y}"); + println!("x = {x}, y = {y}"); ``` But this code seems to contradict what we just learned: we don’t have a call to @@ -414,15 +494,16 @@ different from the usual shallow copying, and we can leave it out. Rust has a special annotation called the `Copy` trait that we can place on types that are stored on the stack, as integers are (we’ll talk more about -traits in Chapter 10). If a type implements the `Copy` trait, variables that -use it do not move, but rather are trivially copied, making them still valid -after assignment to another variable. +traits in Chapter 10). If a type implements the `Copy` +trait, variables that use it do not move, but rather are trivially copied, +making them still valid after assignment to another variable. Rust won’t let us annotate a type with `Copy` if the type, or any of its parts, has implemented the `Drop` trait. If the type needs something special to happen when the value goes out of scope and we add the `Copy` annotation to that type, we’ll get a compile-time error. To learn about how to add the `Copy` annotation -to your type to implement the trait, see “Derivable Traits” on page XX. +to your type to implement the trait, see “Derivable +Traits” in Appendix C. So, what types implement the `Copy` trait? You can check the documentation for the given type to be sure, but as a general rule, any group of simple scalar @@ -435,7 +516,7 @@ implement `Copy`: * All the floating-point types, such as `f64`. * The character type, `char`. * Tuples, if they only contain types that also implement `Copy`. For example, -`(i32, i32)` implements `Copy`, but `(i32, String)` does not. + `(i32, i32)` implements `Copy`, but `(i32, String)` does not. ### Ownership and Functions @@ -444,8 +525,9 @@ assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does. Listing 4-3 has an example with some annotations showing where variables go into and out of scope. +src/main.rs + ``` -// src/main.rs fn main() { let s = String::from("hello"); // s comes into scope @@ -454,21 +536,21 @@ fn main() { let x = 5; // x comes into scope - makes_copy(x); // x would move into the function, - // but i32 is Copy, so it's okay to still - // use x afterward + makes_copy(x); // because i32 implements the Copy trait, + // x does NOT move into the function, + println!("{}", x); // so it's okay to use x afterward -} // Here, x goes out of scope, then s. However, because s's value was moved, - // nothing special happens +} // Here, x goes out of scope, then s. But because s's value was moved, nothing + // special happens. fn takes_ownership(some_string: String) { // some_string comes into scope println!("{some_string}"); } // Here, some_string goes out of scope and `drop` is called. The backing - // memory is freed + // memory is freed. fn makes_copy(some_integer: i32) { // some_integer comes into scope println!("{some_integer}"); -} // Here, some_integer goes out of scope. Nothing special happens +} // Here, some_integer goes out of scope. Nothing special happens. ``` Listing 4-3: Functions with ownership and scope annotated @@ -484,34 +566,36 @@ Returning values can also transfer ownership. Listing 4-4 shows an example of a function that returns some value, with similar annotations as those in Listing 4-3. +src/main.rs + ``` -// src/main.rs fn main() { - let s1 = gives_ownership(); // gives_ownership moves its return - // value into s1 + let s1 = gives_ownership(); // gives_ownership moves its return + // value into s1 - let s2 = String::from("hello"); // s2 comes into scope + let s2 = String::from("hello"); // s2 comes into scope - let s3 = takes_and_gives_back(s2); // s2 is moved into - // takes_and_gives_back, which also - // moves its return value into s3 + let s3 = takes_and_gives_back(s2); // s2 is moved into + // takes_and_gives_back, which also + // moves its return value into s3 } // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing - // happens. s1 goes out of scope and is dropped + // happens. s1 goes out of scope and is dropped. -fn gives_ownership() -> String { // gives_ownership will move its - // return value into the function - // that calls it +fn gives_ownership() -> String { // gives_ownership will move its + // return value into the function + // that calls it let some_string = String::from("yours"); // some_string comes into scope - some_string // some_string is returned and - // moves out to the calling - // function + some_string // some_string is returned and + // moves out to the calling + // function } -// This function takes a String and returns a String -fn takes_and_gives_back(a_string: String) -> String { // a_string comes into - // scope +// This function takes a String and returns a String. +fn takes_and_gives_back(a_string: String) -> String { + // a_string comes into + // scope a_string // a_string is returned and moves out to the calling function } @@ -532,7 +616,7 @@ from the body of the function that we might want to return as well. Rust does let us return multiple values using a tuple, as shown in Listing 4-5. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -570,7 +654,7 @@ particular type for the life of that reference. Here is how you would define and use a `calculate_length` function that has a reference to an object as a parameter instead of taking ownership of the value: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -586,30 +670,36 @@ fn calculate_length(s: &String) -> usize { } ``` + + First, notice that all the tuple code in the variable declaration and the function return value is gone. Second, note that we pass `&s1` into `calculate_length` and, in its definition, we take `&String` rather than `String`. These ampersands represent *references*, and they allow you to refer -to some value without taking ownership of it. Figure 4-5 depicts this concept. +to some value without taking ownership of it. Figure 4-6 depicts this concept. + +Three tables: the table for s contains only a pointer to the table
+for s1. The table for s1 contains the stack data for s1 and points to the
+string data on the heap. -Figure 4-5: A diagram of `&String s` pointing at `String s1` +Figure 4-6: A diagram of `&String s` pointing at `String s1` > Note: The opposite of referencing by using `&` is *dereferencing*, which is -accomplished with the dereference operator, `*`. We’ll see some uses of the -dereference operator in Chapter 8 and discuss details of dereferencing in -Chapter 15. +> accomplished with the dereference operator, `*`. We’ll see some uses of the +> dereference operator in Chapter 8 and discuss details of dereferencing in +> Chapter 15. Let’s take a closer look at the function call here: ``` -let s1 = String::from("hello"); + let s1 = String::from("hello"); -let len = calculate_length(&s1); + let len = calculate_length(&s1); ``` The `&s1` syntax lets us create a reference that *refers* to the value of `s1` -but does not own it. Because it does not own it, the value it points to will -not be dropped when the reference stops being used. +but does not own it. Because the reference does not own it, the value it points +to will not be dropped when the reference stops being used. Likewise, the signature of the function uses `&` to indicate that the type of the parameter `s` is a reference. Let’s add some explanatory annotations: @@ -617,8 +707,8 @@ the parameter `s` is a reference. Let’s add some explanatory annotations: ``` fn calculate_length(s: &String) -> usize { // s is a reference to a String s.len() -} // Here, s goes out of scope. But because it does not have ownership of what - // it refers to, the String is not dropped +} // Here, s goes out of scope. But because s does not have ownership of what + // it refers to, the value is not dropped. ``` The scope in which the variable `s` is valid is the same as any function @@ -635,7 +725,7 @@ to give it back. You don’t own it. So, what happens if we try to modify something we’re borrowing? Try the code in Listing 4-6. Spoiler alert: it doesn’t work! -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -654,16 +744,21 @@ Listing 4-6: Attempting to modify a borrowed value Here’s the error: ``` -error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` -reference +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) +error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference --> src/main.rs:8:5 | -7 | fn change(some_string: &String) { - | ------- help: consider changing this to be a mutable -reference: `&mut String` 8 | some_string.push_str(", world"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so -the data it refers to cannot be borrowed as mutable + | ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable + | +help: consider changing this to be a mutable reference + | +7 | fn change(some_string: &mut String) { + | +++ + +For more information about this error, try `rustc --explain E0596`. +error: could not compile `ownership` (bin "ownership") due to 1 previous error ``` Just as variables are immutable by default, so are references. We’re not @@ -674,7 +769,7 @@ allowed to modify something we have a reference to. We can fix the code from Listing 4-6 to allow us to modify a borrowed value with just a few small tweaks that use, instead, a *mutable reference*: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -688,8 +783,9 @@ fn change(some_string: &mut String) { } ``` -First we change `s` to be `mut`. Then we create a mutable reference with `&mut -s` where we call the `change` function, and update the function signature to + + +First we change `s` to be `mut`. Then we create a mutable reference with `&mut s` where we call the `change` function, and update the function signature to accept a mutable reference with `some_string: &mut String`. This makes it very clear that the `change` function will mutate the value it borrows. @@ -697,20 +793,24 @@ Mutable references have one big restriction: if you have a mutable reference to a value, you can have no other references to that value. This code that attempts to create two mutable references to `s` will fail: -Filename: src/main.rs +src/main.rs ``` -let mut s = String::from("hello"); + let mut s = String::from("hello"); -let r1 = &mut s; -let r2 = &mut s; + let r1 = &mut s; + let r2 = &mut s; -println!("{r1}, {r2}"); + println!("{}, {}", r1, r2); ``` + + Here’s the error: ``` +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 | @@ -719,8 +819,11 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time 5 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 6 | -7 | println!("{r1}, {r2}"); - | -- first borrow later used here +7 | println!("{}, {}", r1, r2); + | -- first borrow later used here + +For more information about this error, try `rustc --explain E0499`. +error: could not compile `ownership` (bin "ownership") due to 1 previous error ``` This error says that this code is invalid because we cannot borrow `s` as @@ -748,33 +851,34 @@ As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not *simultaneous* ones: ``` -let mut s = String::from("hello"); + let mut s = String::from("hello"); -{ - let r1 = &mut s; -} // r1 goes out of scope here, so we can make a new reference with no problems + { + let r1 = &mut s; + } // r1 goes out of scope here, so we can make a new reference with no problems. -let r2 = &mut s; + let r2 = &mut s; ``` Rust enforces a similar rule for combining mutable and immutable references. This code results in an error: ``` -let mut s = String::from("hello"); + let mut s = String::from("hello"); -let r1 = &s; // no problem -let r2 = &s; // no problem -let r3 = &mut s; // BIG PROBLEM + let r1 = &s; // no problem + let r2 = &s; // no problem + let r3 = &mut s; // BIG PROBLEM -println!("{r1}, {r2}, and {r3}"); + println!("{}, {}, and {}", r1, r2, r3); ``` Here’s the error: ``` -error[E0502]: cannot borrow `s` as mutable because it is also borrowed as -immutable +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) +error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:6:14 | 4 | let r1 = &s; // no problem @@ -783,8 +887,11 @@ immutable 6 | let r3 = &mut s; // BIG PROBLEM | ^^^^^^ mutable borrow occurs here 7 | -8 | println!("{r1}, {r2}, and {r3}"); - | -- immutable borrow later used here +8 | println!("{}, {}, and {}", r1, r2, r3); + | -- immutable borrow later used here + +For more information about this error, try `rustc --explain E0502`. +error: could not compile `ownership` (bin "ownership") due to 1 previous error ``` Whew! We *also* cannot have a mutable reference while we have an immutable one @@ -797,19 +904,19 @@ reading of the data. Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used. For instance, this code will -compile because the last usage of the immutable references, the `println!`, -occurs before the mutable reference is introduced: +compile because the last usage of the immutable references is in the `println!`, +before the mutable reference is introduced: ``` -let mut s = String::from("hello"); + let mut s = String::from("hello"); -let r1 = &s; // no problem -let r2 = &s; // no problem -println!("{r1} and {r2}"); -// variables r1 and r2 will not be used after this point + let r1 = &s; // no problem + let r2 = &s; // no problem + println!("{r1} and {r2}"); + // Variables r1 and r2 will not be used after this point. -let r3 = &mut s; // no problem -println!("{r3}"); + let r3 = &mut s; // no problem + println!("{r3}"); ``` The scopes of the immutable references `r1` and `r2` end after the `println!` @@ -836,7 +943,7 @@ reference to the data does. Let’s try to create a dangling reference to see how Rust prevents them with a compile-time error: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -850,21 +957,39 @@ fn dangle() -> &String { } ``` + + Here’s the error: ``` +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) error[E0106]: missing lifetime specifier --> src/main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected named lifetime parameter | - = help: this function's return type contains a borrowed value, -but there is no value for it to be borrowed from -help: consider using the `'static` lifetime + = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from +help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` | 5 | fn dangle() -> &'static String { - | ~~~~~~~~ + | +++++++ +help: instead, you are more likely to want to return an owned value + | +5 - fn dangle() -> &String { +5 + fn dangle() -> String { + | + +error[E0515]: cannot return reference to local variable `s` + --> src/main.rs:8:5 + | +8 | &s + | ^^ returns a reference to data owned by the current function + +Some errors have detailed explanations: E0106, E0515. +For more information about an error, try `rustc --explain E0106`. +error: could not compile `ownership` (bin "ownership") due to 2 previous errors ``` This error message refers to a feature we haven’t covered yet: lifetimes. We’ll @@ -872,24 +997,27 @@ discuss lifetimes in detail in Chapter 10. But, if you disregard the parts about lifetimes, the message does contain the key to why this code is a problem: ``` -this function's return type contains a borrowed value, but there -is no value for it to be borrowed from +this function's return type contains a borrowed value, but there is no value +for it to be borrowed from ``` Let’s take a closer look at exactly what’s happening at each stage of our `dangle` code: +src/main.rs + ``` -// src/main.rs fn dangle() -> &String { // dangle returns a reference to a String let s = String::from("hello"); // s is a new String &s // we return a reference to the String, s -} // Here, s goes out of scope and is dropped, so its memory goes away +} // Here, s goes out of scope, and is dropped, so its memory goes away. // Danger! ``` + + Because `s` is created inside `dangle`, when the code of `dangle` is finished, `s` will be deallocated. But we tried to return a reference to it. That means this reference would be pointing to an invalid `String`. That’s no good! Rust @@ -913,16 +1041,16 @@ deallocated. Let’s recap what we’ve discussed about references: * At any given time, you can have *either* one mutable reference *or* any -number of immutable references. + number of immutable references. * References must always be valid. Next, we’ll look at a different kind of reference: slices. ## The Slice Type -*Slices* let you reference a contiguous sequence of elements in a collection -rather than the whole collection. A slice is a kind of reference, so it does -not have ownership. +*Slices* let you reference a contiguous sequence of elements in a +collection at *ch08-00-common-collections.md* rather than the whole collection. A +slice is a kind of reference, so it does not have ownership. Here’s a small programming problem: write a function that takes a string of words separated by spaces and returns the first word it finds in that string. @@ -936,51 +1064,71 @@ slices, to understand the problem that slices will solve: fn first_word(s: &String) -> ? ``` -The `first_word` function has a `&String` as a parameter. We don’t want -ownership, so this is fine. But what should we return? We don’t really have a -way to talk about *part* of a string. However, we could return the index of the -end of the word, indicated by a space. Let’s try that, as shown in Listing 4-7. +The `first_word` function has a `&String` as a parameter. We don’t need +ownership, so this is fine. (In idiomatic Rust, functions do not take ownership +of their arguments unless they need to, and the reasons for that will become +clear as we keep going!) But what should we return? We don’t really have a way +to talk about part of a string. However, we could return the index of the end of +the word, indicated by a space. Let’s try that, as shown in Listing 4-7. -Filename: src/main.rs +src/main.rs ``` fn first_word(s: &String) -> usize { - 1 let bytes = s.as_bytes(); + let bytes = s.as_bytes(); - for (2 i, &item) in 3 bytes.iter().enumerate() { - 4 if item == b' ' { + for (i, &item) in bytes.iter().enumerate() { + if item == b' ' { return i; } } - 5 s.len() + s.len() } ``` -Listing 4-7: The `first_word` function that returns a byte index value into the -`String` parameter +Listing 4-7: The `first_word` function that returns a byte index value into the `String` parameter Because we need to go through the `String` element by element and check whether a value is a space, we’ll convert our `String` to an array of bytes using the -`as_bytes` method [1]. +`as_bytes` method. + +``` + let bytes = s.as_bytes(); +``` + +Next, we create an iterator over the array of bytes using the `iter` method: + +``` + for (i, &item) in bytes.iter().enumerate() { +``` -Next, we create an iterator over the array of bytes using the `iter` method -[3]. We’ll discuss iterators in more detail in Chapter 13. For now, know that -`iter` is a method that returns each element in a collection and that -`enumerate` wraps the result of `iter` and returns each element as part of a -tuple instead. The first element of the tuple returned from `enumerate` is the -index, and the second element is a reference to the element. This is a bit more -convenient than calculating the index ourselves. +We’ll discuss iterators in more detail in Chapter 13. +For now, know that `iter` is a method that returns each element in a collection +and that `enumerate` wraps the result of `iter` and returns each element as +part of a tuple instead. The first element of the tuple returned from +`enumerate` is the index, and the second element is a reference to the element. +This is a bit more convenient than calculating the index ourselves. Because the `enumerate` method returns a tuple, we can use patterns to -destructure that tuple. We’ll be discussing patterns more in Chapter 6. In the -`for` loop, we specify a pattern that has `i` for the index in the tuple and -`&item` for the single byte in the tuple [2]. Because we get a reference to the -element from `.iter().enumerate()`, we use `&` in the pattern. +destructure that tuple. We’ll be discussing patterns more in Chapter +6. In the `for` loop, we specify a pattern that has `i` +for the index in the tuple and `&item` for the single byte in the tuple. +Because we get a reference to the element from `.iter().enumerate()`, we use +`&` in the pattern. Inside the `for` loop, we search for the byte that represents the space by -using the byte literal syntax [4]. If we find a space, we return the position. -Otherwise, we return the length of the string by using `s.len()` [5]. +using the byte literal syntax. If we find a space, we return the position. +Otherwise, we return the length of the string by using `s.len()`. + +``` + if item == b' ' { + return i; + } + } + + s.len() +``` We now have a way to find out the index of the end of the first word in the string, but there’s a problem. We’re returning a `usize` on its own, but it’s @@ -989,8 +1137,9 @@ because it’s a separate value from the `String`, there’s no guarantee that i will still be valid in the future. Consider the program in Listing 4-8 that uses the `first_word` function from Listing 4-7. +src/main.rs + ``` -// src/main.rs fn main() { let mut s = String::from("hello world"); @@ -998,13 +1147,13 @@ fn main() { s.clear(); // this empties the String, making it equal to "" - // word still has the value 5 here, but there's no more string that - // we could meaningfully use the value 5 with. word is now totally invalid! + // `word` still has the value `5` here, but `s` no longer has any content + // that we could meaningfully use with the value `5`, so `word` is now + // totally invalid! } ``` -Listing 4-8: Storing the result from calling the `first_word` function and then -changing the `String` contents +Listing 4-8: Storing the result from calling the `first_word` function and then changing the `String` contents This program compiles without any errors and would also do so if we used `word` after calling `s.clear()`. Because `word` isn’t connected to the state of `s` @@ -1032,25 +1181,31 @@ Luckily, Rust has a solution to this problem: string slices. A *string slice* is a reference to part of a `String`, and it looks like this: ``` -let s = String::from("hello world"); + let s = String::from("hello world"); -let hello = &s[0..5]; -let world = &s[6..11]; + let hello = &s[0..5]; + let world = &s[6..11]; ``` Rather than a reference to the entire `String`, `hello` is a reference to a portion of the `String`, specified in the extra `[0..5]` bit. We create slices using a range within brackets by specifying `[starting_index..ending_index]`, -where `starting_index` is the first position in the slice and `ending_index` is -one more than the last position in the slice. Internally, the slice data +where *`starting_index`* is the first position in the slice and *`ending_index`* +is one more than the last position in the slice. Internally, the slice data structure stores the starting position and the length of the slice, which -corresponds to `ending_index` minus `starting_index`. So, in the case of `let -world = &s[6..11];`, `world` would be a slice that contains a pointer to the +corresponds to *`ending_index`* minus *`starting_index`*. So, in the case of `let world = &s[6..11];`, `world` would be a slice that contains a pointer to the byte at index 6 of `s` with a length value of `5`. -Figure 4-6 shows this in a diagram. +Figure 4-7 shows this in a diagram. -Figure 4-6: String slice referring to part of a `String` +Three tables: a table representing the stack data of s, which points
+to the byte at index 0 in a table of the string data "hello world" on
+the heap. The third table rep-resents the stack data of the slice world, which
+has a length value of 5 and points to byte 6 of the heap data table. + +Figure 4-7: String slice referring to part of a +`String` With Rust’s `..` range syntax, if you want to start at index 0, you can drop the value before the two periods. In other words, these are equal: @@ -1087,16 +1242,16 @@ let slice = &s[..]; ``` > Note: String slice range indices must occur at valid UTF-8 character -boundaries. If you attempt to create a string slice in the middle of a -multibyte character, your program will exit with an error. For the purposes of -introducing string slices, we are assuming ASCII only in this section; a more -thorough discussion of UTF-8 handling is in “Storing UTF-8 Encoded Text with -Strings” on page XX. +> boundaries. If you attempt to create a string slice in the middle of a +> multibyte character, your program will exit with an error. For the purposes +> of introducing string slices, we are assuming ASCII only in this section; a +> more thorough discussion of UTF-8 handling is in the “Storing UTF-8 Encoded +> Text with Strings” section of Chapter 8. With all this information in mind, let’s rewrite `first_word` to return a slice. The type that signifies “string slice” is written as `&str`: -Filename: src/main.rs +src/main.rs ``` fn first_word(s: &String) -> &str { @@ -1112,6 +1267,8 @@ fn first_word(s: &String) -> &str { } ``` + + We get the index for the end of the word the same way we did in Listing 4-7, by looking for the first occurrence of a space. When we find a space, we return a string slice using the start of the string and the index of the space as the @@ -1137,7 +1294,7 @@ string. Slices make this bug impossible and let us know we have a problem with our code much sooner. Using the slice version of `first_word` will throw a compile-time error: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1151,11 +1308,14 @@ fn main() { } ``` + + Here’s the compiler error: ``` -error[E0502]: cannot borrow `s` as mutable because it is also borrowed as -immutable +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) +error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:18:5 | 16 | let word = first_word(&s); @@ -1165,7 +1325,10 @@ immutable | ^^^^^^^^^ mutable borrow occurs here 19 | 20 | println!("the first word is: {word}"); - | ---- immutable borrow later used here + | ------ immutable borrow later used here + +For more information about this error, try `rustc --explain E0502`. +error: could not compile `ownership` (bin "ownership") due to 1 previous error ``` Recall from the borrowing rules that if we have an immutable reference to @@ -1177,6 +1340,10 @@ reference in `clear` and the immutable reference in `word` from existing at the same time, and compilation fails. Not only has Rust made our API easier to use, but it has also eliminated an entire class of errors at compile time! + + + + #### String Literals as Slices Recall that we talked about string literals being stored inside the binary. Now @@ -1203,39 +1370,39 @@ A more experienced Rustacean would write the signature shown in Listing 4-9 instead because it allows us to use the same function on both `&String` values and `&str` values. + ``` fn first_word(s: &str) -> &str { ``` -Listing 4-9: Improving the `first_word` function by using a string slice for -the type of the `s` parameter +Listing 4-9: Improving the `first_word` function by using a string slice for the type of the `s` parameter If we have a string slice, we can pass that directly. If we have a `String`, we can pass a slice of the `String` or a reference to the `String`. This -flexibility takes advantage of *deref coercions*, a feature we will cover in -“Implicit Deref Coercions with Functions and Methods” on page XX. +flexibility takes advantage of *deref coercions*, a feature we will cover in the +“Implicit Deref Coercions with Functions and +Methods” at *ch15-02-deref.html#implicit-deref-coercions-with-functions-and-methods* section of Chapter 15. Defining a function to take a string slice instead of a reference to a `String` makes our API more general and useful without losing any functionality: -Filename: src/main.rs +src/main.rs ``` fn main() { let my_string = String::from("hello world"); - // `first_word` works on slices of `String`s, whether partial - // or whole + // `first_word` works on slices of `String`s, whether partial or whole. let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); - // `first_word` also works on references to `String`s, which - // are equivalent to whole slices of `String`s + // `first_word` also works on references to `String`s, which are equivalent + // to whole slices of `String`s. let word = first_word(&my_string); let my_string_literal = "hello world"; - // `first_word` works on slices of string literals, - // whether partial or whole + // `first_word` works on slices of string literals, whether partial or + // whole. let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); @@ -1245,6 +1412,8 @@ fn main() { } ``` + + ### Other Slices String slices, as you might imagine, are specific to strings. But there’s a @@ -1281,4 +1450,3 @@ means you don’t have to write and debug extra code to get this control. Ownership affects how lots of other parts of Rust work, so we’ll talk about these concepts further throughout the rest of the book. Let’s move on to Chapter 5 and look at grouping pieces of data together in a `struct`. - diff --git a/nostarch/chapter05.md b/nostarch/chapter05.md index 9881a3b748..4940e8c47f 100644 --- a/nostarch/chapter05.md +++ b/nostarch/chapter05.md @@ -23,12 +23,11 @@ program’s domain to take full advantage of Rust’s compile-time type checking ## Defining and Instantiating Structs -Structs are similar to tuples, discussed in “The Tuple Type” on page XX, in -that both hold multiple related values. Like tuples, the pieces of a struct can -be different types. Unlike with tuples, in a struct you’ll name each piece of -data so it’s clear what the values mean. Adding these names means that structs -are more flexible than tuples: you don’t have to rely on the order of the data -to specify or access the values of an instance. +Structs are similar to tuples, discussed in “The Tuple Type” section, in that both hold multiple related values. Like tuples, the +pieces of a struct can be different types. Unlike with tuples, in a struct +you’ll name each piece of data so it’s clear what the values mean. Adding these +names means that structs are more flexible than tuples: you don’t have to rely +on the order of the data to specify or access the values of an instance. To define a struct, we enter the keyword `struct` and name the entire struct. A struct’s name should describe the significance of the pieces of data being @@ -36,7 +35,7 @@ grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call *fields*. For example, Listing 5-1 shows a struct that stores information about a user account. -Filename: src/main.rs +src/main.rs ``` struct User { @@ -51,15 +50,15 @@ Listing 5-1: A `User` struct definition To use a struct after we’ve defined it, we create an *instance* of that struct by specifying concrete values for each of the fields. We create an instance by -stating the name of the struct and then add curly brackets containing key: -value pairs, where the keys are the names of the fields and the values are the +stating the name of the struct and then add curly brackets containing *key: +value* pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill in that template with particular data to create values of the type. For example, we can declare a particular user as shown in Listing 5-2. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -80,7 +79,7 @@ mutable, we can change a value by using the dot notation and assigning into a particular field. Listing 5-3 shows how to change the value in the `email` field of a mutable `User` instance. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -106,6 +105,8 @@ Listing 5-4 shows a `build_user` function that returns a `User` instance with the given email and username. The `active` field gets the value of `true`, and the `sign_in_count` gets a value of `1`. +src/main.rs + ``` fn build_user(email: String, username: String) -> User { User { @@ -117,14 +118,17 @@ fn build_user(email: String, username: String) -> User { } ``` -Listing 5-4: A `build_user` function that takes an email and username and -returns a `User` instance +Listing 5-4: A `build_user` function that takes an email and username and returns a `User` instance It makes sense to name the function parameters with the same name as the struct fields, but having to repeat the `email` and `username` field names and variables is a bit tedious. If the struct had more fields, repeating each name would get even more annoying. Luckily, there’s a convenient shorthand! + + + + ### Using the Field Init Shorthand Because the parameter names and the struct field names are exactly the same in @@ -132,6 +136,8 @@ Listing 5-4, we can use the *field init shorthand* syntax to rewrite `build_user` so it behaves exactly the same but doesn’t have the repetition of `username` and `email`, as shown in Listing 5-5. +src/main.rs + ``` fn build_user(email: String, username: String) -> User { User { @@ -143,8 +149,7 @@ fn build_user(email: String, username: String) -> User { } ``` -Listing 5-5: A `build_user` function that uses field init shorthand because the -`username` and `email` parameters have the same name as struct fields +Listing 5-5: A `build_user` function that uses field init shorthand because the `username` and `email` parameters have the same name as struct fields Here, we’re creating a new instance of the `User` struct, which has a field named `email`. We want to set the `email` field’s value to the value in the @@ -162,11 +167,11 @@ First, in Listing 5-6 we show how to create a new `User` instance in `user2` regularly, without the update syntax. We set a new value for `email` but otherwise use the same values from `user1` that we created in Listing 5-2. -Filename: src/main.rs +src/main.rs ``` fn main() { - --snip-- + // --snip-- let user2 = User { active: user1.active, @@ -177,18 +182,17 @@ fn main() { } ``` -Listing 5-6: Creating a new `User` instance using one of the values from `user1` +Listing 5-6: Creating a new `User` instance using all but one of the values from `user1` Using struct update syntax, we can achieve the same effect with less code, as shown in Listing 5-7. The syntax `..` specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance. -Filename: src/main.rs +src/main.rs ``` fn main() { - --snip-- - + // --snip-- let user2 = User { email: String::from("another@example.com"), @@ -197,8 +201,7 @@ fn main() { } ``` -Listing 5-7: Using struct update syntax to set a new `email` value for a `User` -instance but to use the rest of the values from `user1` +Listing 5-7: Using struct update syntax to set a new `email` value for a `User` instance but to use the rest of the values from `user1` The code in Listing 5-7 also creates an instance in `user2` that has a different value for `email` but has the same values for the `username`, @@ -209,14 +212,16 @@ many fields as we want in any order, regardless of the order of the fields in the struct’s definition. Note that the struct update syntax uses `=` like an assignment; this is because -it moves the data, just as we saw in “Variables and Data Interacting with Move” -on page XX. In this example, we can no longer use `user1` after creating +it moves the data, just as we saw in the “Variables and Data Interacting with +Move” section. In this example, we can no longer use `user1` after creating `user2` because the `String` in the `username` field of `user1` was moved into `user2`. If we had given `user2` new `String` values for both `email` and `username`, and thus only used the `active` and `sign_in_count` values from -`user1`, then `user1` would still be valid after creating `user2`. Both -`active` and `sign_in_count` are types that implement the `Copy` trait, so the -behavior we discussed in “Stack-Only Data: Copy” on page XX would apply. +`user1`, then `user1` would still be `user1`, then `user1` would still be valid +after creating `user2`. Both `active` and `sign_in_count` are types that +implement the `Copy` trait, so the behavior we discussed in “Stack-Only Data: +Copy” on page XX would apply. We can still use `user1.email` in this example, +since its value was *not* moved out. ### Using Tuple Structs Without Named Fields to Create Different Types @@ -231,7 +236,7 @@ To define a tuple struct, start with the `struct` keyword and the struct name followed by the types in the tuple. For example, here we define and use two tuple structs named `Color` and `Point`: -Filename: src/main.rs +src/main.rs ``` struct Color(i32, i32, i32); @@ -243,6 +248,8 @@ fn main() { } ``` + + Note that the `black` and `origin` values are different types because they’re instances of different tuple structs. Each struct you define is its own type, even though the fields within the struct might have the same types. For @@ -250,19 +257,21 @@ example, a function that takes a parameter of type `Color` cannot take a `Point` as an argument, even though both types are made up of three `i32` values. Otherwise, tuple struct instances are similar to tuples in that you can destructure them into their individual pieces, and you can use a `.` followed -by the index to access an individual value. +by the index to access an individual value. Unlike tuples, tuple structs +require you to name the type of the struct when you destructure them. For +example, we would write `let Point(x, y, z) = point`. ### Unit-Like Structs Without Any Fields You can also define structs that don’t have any fields! These are called *unit-like structs* because they behave similarly to `()`, the unit type that -we mentioned in “The Tuple Type” on page XX. Unit-like structs can be useful -when you need to implement a trait on some type but don’t have any data that -you want to store in the type itself. We’ll discuss traits in Chapter 10. -Here’s an example of declaring and instantiating a unit struct named -`AlwaysEqual`: +we mentioned in “The Tuple Type” section. Unit-like +structs can be useful when you need to implement a trait on some type but don’t +have any data that you want to store in the type itself. We’ll discuss traits +in Chapter 10. Here’s an example of declaring and instantiating a unit struct +named `AlwaysEqual`: -Filename: src/main.rs +src/main.rs ``` struct AlwaysEqual; @@ -272,6 +281,8 @@ fn main() { } ``` + + To define `AlwaysEqual`, we use the `struct` keyword, the name we want, and then a semicolon. No need for curly brackets or parentheses! Then we can get an instance of `AlwaysEqual` in the `subject` variable in a similar way: using the @@ -283,27 +294,30 @@ implement that behavior! You’ll see in Chapter 10 how to define traits and implement them on any type, including unit-like structs. > ### Ownership of Struct Data -> +> > In the `User` struct definition in Listing 5-1, we used the owned `String` -type rather than the `&str` string slice type. This is a deliberate choice -because we want each instance of this struct to own all of its data and for -that data to be valid for as long as the entire struct is valid. -> +> type rather than the `&str` string slice type. This is a deliberate choice +> because we want each instance of this struct to own all of its data and for +> that data to be valid for as long as the entire struct is valid. +> > It’s also possible for structs to store references to data owned by something -else, but to do so requires the use of *lifetimes*, a Rust feature that we’ll -discuss in Chapter 10. Lifetimes ensure that the data referenced by a struct is -valid for as long as the struct is. Let’s say you try to store a reference in a -struct without specifying lifetimes, like the following in *src/main.rs*; this -won’t work: -> -> ``` +> else, but to do so requires the use of *lifetimes*, a Rust feature that we’ll +> discuss in Chapter 10. Lifetimes ensure that the data referenced by a struct +> is valid for as long as the struct is. Let’s say you try to store a reference +> in a struct without specifying lifetimes, like the following; this won’t work: +> +> +> +> +> +> ````rust,ignore,does_not_compile > struct User { > active: bool, > username: &str, > email: &str, > sign_in_count: u64, > } -> +> > fn main() { > let user1 = User { > active: true, @@ -312,12 +326,14 @@ won’t work: > sign_in_count: 1, > }; > } -> ``` -> +> ```` +> +> +> > The compiler will complain that it needs lifetime specifiers: -> -> ``` -> $ `cargo run` +> +> ````console +> $ cargo run > Compiling structs v0.1.0 (file:///projects/structs) > error[E0106]: missing lifetime specifier > --> src/main.rs:3:15 @@ -331,7 +347,7 @@ won’t work: > 2 | active: bool, > 3 ~ username: &'a str, > | -> +> > error[E0106]: missing lifetime specifier > --> src/main.rs:4:12 > | @@ -348,8 +364,15 @@ won’t work: > ``` > > In Chapter 10, we’ll discuss how to fix these errors so you can store -references in structs, but for now, we’ll fix errors like these using owned -types like `String` instead of references like `&str`. +> references in structs, but for now, we’ll fix errors like these using owned +> types like `String` instead of references like `&str`. + + ## An Example Program Using Structs @@ -362,7 +385,7 @@ the width and height of a rectangle specified in pixels and calculate the area of the rectangle. Listing 5-8 shows a short program with one way of doing exactly that in our project’s *src/main.rs*. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -380,12 +403,15 @@ fn area(width: u32, height: u32) -> u32 { } ``` -Listing 5-8: Calculating the area of a rectangle specified by separate width -and height variables +Listing 5-8: Calculating the area of a rectangle specified by separate width and height variables Now, run this program using `cargo run`: ``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s + Running `target/debug/rectangles` The area of the rectangle is 1500 square pixels. ``` @@ -403,13 +429,14 @@ The `area` function is supposed to calculate the area of one rectangle, but the function we wrote has two parameters, and it’s not clear anywhere in our program that the parameters are related. It would be more readable and more manageable to group width and height together. We’ve already discussed one way -we might do that in “The Tuple Type” on page XX: by using tuples. +we might do that in “The Tuple Type” section +of Chapter 3: by using tuples. ### Refactoring with Tuples Listing 5-9 shows another version of our program that uses tuples. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -417,21 +444,21 @@ fn main() { println!( "The area of the rectangle is {} square pixels.", - 1 area(rect1) + area(rect1) ); } fn area(dimensions: (u32, u32)) -> u32 { - 2 dimensions.0 * dimensions.1 + dimensions.0 * dimensions.1 } ``` Listing 5-9: Specifying the width and height of the rectangle with a tuple In one way, this program is better. Tuples let us add a bit of structure, and -we’re now passing just one argument [1]. But in another way, this version is -less clear: tuples don’t name their elements, so we have to index into the -parts of the tuple [2], making our calculation less obvious. +we’re now passing just one argument. But in another way, this version is less +clear: tuples don’t name their elements, so we have to index into the parts of +the tuple, making our calculation less obvious. Mixing up the width and height wouldn’t matter for the area calculation, but if we want to draw the rectangle on the screen, it would matter! We would have to @@ -446,16 +473,16 @@ We use structs to add meaning by labeling the data. We can transform the tuple we’re using into a struct with a name for the whole as well as names for the parts, as shown in Listing 5-10. -Filename: src/main.rs +src/main.rs ``` -1 struct Rectangle { - 2 width: u32, +struct Rectangle { + width: u32, height: u32, } fn main() { - 3 let rect1 = Rectangle { + let rect1 = Rectangle { width: 30, height: 50, }; @@ -466,27 +493,27 @@ fn main() { ); } -4 fn area(rectangle: &Rectangle) -> u32 { - 5 rectangle.width * rectangle.height +fn area(rectangle: &Rectangle) -> u32 { + rectangle.width * rectangle.height } ``` Listing 5-10: Defining a `Rectangle` struct -Here, we’ve defined a struct and named it `Rectangle` [1]. Inside the curly +Here we’ve defined a struct and named it `Rectangle`. Inside the curly brackets, we defined the fields as `width` and `height`, both of which have -type `u32` [2]. Then, in `main`, we created a particular instance of -`Rectangle` that has a width of `30` and a height of `50` [3]. +type `u32`. Then, in `main`, we created a particular instance of `Rectangle` +that has a width of `30` and a height of `50`. Our `area` function is now defined with one parameter, which we’ve named -`rectangle`, whose type is an immutable borrow of a struct `Rectangle` instance -[4]. As mentioned in Chapter 4, we want to borrow the struct rather than take -ownership of it. This way, `main` retains its ownership and can continue using -`rect1`, which is the reason we use the `&` in the function signature and where -we call the function. +`rectangle`, whose type is an immutable borrow of a struct `Rectangle` +instance. As mentioned in Chapter 4, we want to borrow the struct rather than +take ownership of it. This way, `main` retains its ownership and can continue +using `rect1`, which is the reason we use the `&` in the function signature and +where we call the function. The `area` function accesses the `width` and `height` fields of the `Rectangle` -instance [5] (note that accessing fields of a borrowed struct instance does not +instance (note that accessing fields of a borrowed struct instance does not move the field values, which is why you often see borrows of structs). Our function signature for `area` now says exactly what we mean: calculate the area of `Rectangle`, using its `width` and `height` fields. This conveys that the @@ -498,10 +525,10 @@ win for clarity. It’d be useful to be able to print an instance of `Rectangle` while we’re debugging our program and see the values for all its fields. Listing 5-11 tries -using the `println!` macro as we have used in previous chapters. This won’t -work, however. +using the `println!` macro as we have used in +previous chapters. This won’t work, however. -Filename: src/main.rs +src/main.rs ``` struct Rectangle { @@ -541,13 +568,11 @@ implementation of `Display` to use with `println!` and the `{}` placeholder. If we continue reading the errors, we’ll find this helpful note: ``` -= help: the trait `std::fmt::Display` is not implemented for `Rectangle` -= note: in format strings you may be able to use `{:?}` (or {:#?} for -pretty-print) instead + = help: the trait `std::fmt::Display` is not implemented for `Rectangle` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead ``` -Let’s try it! The `println!` macro call will now look like `println!("rect1 is -{:?}", rect1);`. Putting the specifier `:?` inside the curly brackets tells +Let’s try it! The `println!` macro call will now look like `println!("rect1 is {rect1:?}");`. Putting the specifier `:?` inside the curly brackets tells `println!` we want to use an output format called `Debug`. The `Debug` trait enables us to print our struct in a way that is useful for developers so we can see its value while we’re debugging our code. @@ -561,8 +586,8 @@ error[E0277]: `Rectangle` doesn't implement `Debug` But again, the compiler gives us a helpful note: ``` -= help: the trait `Debug` is not implemented for `Rectangle` -= note: add `#[derive(Debug)]` or manually implement `Debug` + = help: the trait `Debug` is not implemented for `Rectangle` + = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle` ``` Rust *does* include functionality to print out debugging information, but we @@ -570,7 +595,7 @@ have to explicitly opt in to make that functionality available for our struct. To do that, we add the outer attribute `#[derive(Debug)]` just before the struct definition, as shown in Listing 5-12. -Filename: src/main.rs +src/main.rs ``` #[derive(Debug)] @@ -585,17 +610,20 @@ fn main() { height: 50, }; - println!("rect1 is {:?}", rect1); + println!("rect1 is {rect1:?}"); } ``` -Listing 5-12: Adding the attribute to derive the `Debug` trait and printing the -`Rectangle` instance using debug formatting +Listing 5-12: Adding the attribute to derive the `Debug` trait and printing the `Rectangle` instance using debug formatting Now when we run the program, we won’t get any errors, and we’ll see the following output: ``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s + Running `target/debug/rectangles` rect1 is Rectangle { width: 30, height: 50 } ``` @@ -606,6 +634,10 @@ those cases, we can use `{:#?}` instead of `{:?}` in the `println!` string. In this example, using the `{:#?}` style will output the following: ``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s + Running `target/debug/rectangles` rect1 is Rectangle { width: 30, height: 50, @@ -613,22 +645,20 @@ rect1 is Rectangle { ``` Another way to print out a value using the `Debug` format is to use the `dbg!` -macro, which takes ownership of an expression (as opposed to `println!`, which -takes a reference), prints the file and line number of where that `dbg!` macro -call occurs in your code along with the resultant value of that expression, and -returns ownership of the value. +macro, which takes ownership of an expression (as opposed +to `println!`, which takes a reference), prints the file and line number of +where that `dbg!` macro call occurs in your code along with the resultant value +of that expression, and returns ownership of the value. > Note: Calling the `dbg!` macro prints to the standard error console stream -(`stderr`), as opposed to `println!`, which prints to the standard output -console stream (`stdout`). We’ll talk more about `stderr` and `stdout` in -“Writing Error Messages to Standard Error Instead of Standard Output” on page -XX. +> (`stderr`), as opposed to `println!`, which prints to the standard output +> console stream (`stdout`). We’ll talk more about `stderr` and `stdout` in the +> “Writing Error Messages to Standard Error Instead of Standard Output” +> section in Chapter 12. Here’s an example where we’re interested in the value that gets assigned to the `width` field, as well as the value of the whole struct in `rect1`: -Filename: src/main.rs - ``` #[derive(Debug)] struct Rectangle { @@ -639,43 +669,46 @@ struct Rectangle { fn main() { let scale = 2; let rect1 = Rectangle { - 1 width: dbg!(30 * scale), + width: dbg!(30 * scale), height: 50, }; - 2 dbg!(&rect1); + dbg!(&rect1); } ``` -We can put `dbg!` around the expression `30 * scale` [1] and, because `dbg!` +We can put `dbg!` around the expression `30 * scale` and, because `dbg!` returns ownership of the expression’s value, the `width` field will get the same value as if we didn’t have the `dbg!` call there. We don’t want `dbg!` to -take ownership of `rect1`, so we use a reference to `rect1` in the next call -[2]. Here’s what the output of this example looks like: +take ownership of `rect1`, so we use a reference to `rect1` in the next call. +Here’s what the output of this example looks like: ``` -[src/main.rs:10] 30 * scale = 60 -[src/main.rs:14] &rect1 = Rectangle { +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s + Running `target/debug/rectangles` +[src/main.rs:10:16] 30 * scale = 60 +[src/main.rs:14:5] &rect1 = Rectangle { width: 60, height: 50, } ``` -We can see the first bit of output came from [1] where we’re debugging the -expression `30 * scale`, and its resultant value is `60` (the `Debug` -formatting implemented for integers is to print only their value). The `dbg!` -call at [2] outputs the value of `&rect1`, which is the `Rectangle` struct. -This output uses the pretty `Debug` formatting of the `Rectangle` type. The -`dbg!` macro can be really helpful when you’re trying to figure out what your -code is doing! +We can see the first bit of output came from *src/main.rs* line 10 where we’re +debugging the expression `30 * scale`, and its resultant value is `60` (the +`Debug` formatting implemented for integers is to print only their value). The +`dbg!` call on line 14 of *src/main.rs* outputs the value of `&rect1`, which is +the `Rectangle` struct. This output uses the pretty `Debug` formatting of the +`Rectangle` type. The `dbg!` macro can be really helpful when you’re trying to +figure out what your code is doing! In addition to the `Debug` trait, Rust has provided a number of traits for us to use with the `derive` attribute that can add useful behavior to our custom -types. Those traits and their behaviors are listed in Appendix C. We’ll cover -how to implement these traits with custom behavior as well as how to create -your own traits in Chapter 10. There are also many attributes other than -`derive`; for more information, see the “Attributes” section of the Rust -Reference at *https://doc.rust-lang.org/reference/attributes.html*. +types. Those traits and their behaviors are listed in Appendix C. We’ll cover how to implement these traits with custom behavior as +well as how to create your own traits in Chapter 10. There are also many +attributes other than `derive`; for more information, see the “Attributes” +section of the Rust Reference at *../reference/attributes.html*. Our `area` function is very specific: it only computes the area of rectangles. It would be helpful to tie this behavior more closely to our `Rectangle` struct @@ -689,9 +722,10 @@ defined on our `Rectangle` type. name, they can have parameters and a return value, and they contain some code that’s run when the method is called from somewhere else. Unlike functions, methods are defined within the context of a struct (or an enum or a trait -object, which we cover in Chapter 6 and Chapter 17, respectively), and their -first parameter is always `self`, which represents the instance of the struct -the method is being called on. +object, which we cover in Chapter 6 and Chapter +18, respectively), and their first parameter is +always `self`, which represents the instance of the struct the method is being +called on. ### Defining Methods @@ -699,7 +733,7 @@ Let’s change the `area` function that has a `Rectangle` instance as a paramete and instead make an `area` method defined on the `Rectangle` struct, as shown in Listing 5-13. -Filename: src/main.rs +src/main.rs ``` #[derive(Debug)] @@ -708,8 +742,8 @@ struct Rectangle { height: u32, } -1 impl Rectangle { - 2 fn area(&self) -> u32 { +impl Rectangle { + fn area(&self) -> u32 { self.width * self.height } } @@ -722,7 +756,7 @@ fn main() { println!( "The area of the rectangle is {} square pixels.", - 3 rect1.area() + rect1.area() ); } ``` @@ -730,14 +764,14 @@ fn main() { Listing 5-13: Defining an `area` method on the `Rectangle` struct To define the function within the context of `Rectangle`, we start an `impl` -(implementation) block for `Rectangle` [1]. Everything within this `impl` block +(implementation) block for `Rectangle`. Everything within this `impl` block will be associated with the `Rectangle` type. Then we move the `area` function -within the `impl` curly brackets [2] and change the first (and in this case, -only) parameter to be `self` in the signature and everywhere within the body. -In `main`, where we called the `area` function and passed `rect1` as an -argument, we can instead use *method syntax* to call the `area` method on our -`Rectangle` instance [3]. The method syntax goes after an instance: we add a -dot followed by the method name, parentheses, and any arguments. +within the `impl` curly brackets and change the first (and in this case, only) +parameter to be `self` in the signature and everywhere within the body. In +`main`, where we called the `area` function and passed `rect1` as an argument, +we can instead use *method syntax* to call the `area` method on our `Rectangle` +instance. The method syntax goes after an instance: we add a dot followed by +the method name, parentheses, and any arguments. In the signature for `area`, we use `&self` instead of `rectangle: &Rectangle`. The `&self` is actually short for `self: &Self`. Within an `impl` block, the @@ -770,7 +804,7 @@ Note that we can choose to give a method the same name as one of the struct’s fields. For example, we can define a method on `Rectangle` that is also named `width`: -Filename: src/main.rs +src/main.rs ``` impl Rectangle { @@ -786,14 +820,13 @@ fn main() { }; if rect1.width() { - println!( - "The rectangle has a nonzero width; it is {}", - rect1.width - ); + println!("The rectangle has a nonzero width; it is {}", rect1.width); } } ``` + + Here, we’re choosing to make the `width` method return `true` if the value in the instance’s `width` field is greater than `0` and `false` if the value is `0`: we can use a field within a method of the same name for any purpose. In @@ -801,42 +834,60 @@ the instance’s `width` field is greater than `0` and `false` if the value is method `width`. When we don’t use parentheses, Rust knows we mean the field `width`. -Often, but not always, when we give methods with the same name as a field we -want it to only return the value in the field and do nothing else. Methods like -this are called *getters*, and Rust does not implement them automatically for -struct fields as some other languages do. Getters are useful because you can -make the field private but the method public, and thus enable read-only access -to that field as part of the type’s public API. We will discuss what public and -private are and how to designate a field or method as public or private in -Chapter 7. +Often, but not always, when we give a method the same name as a field we want +it to only return the value in the field and do nothing else. Methods like this +are called *getters*, and Rust does not implement them automatically for struct +fields as some other languages do. Getters are useful because you can make the +field private but the method public, and thus enable read-only access to that +field as part of the type’s public API. We will discuss what public and private +are and how to designate a field or method as public or private in Chapter +7. > ### Where’s the -> Operator? -> +> > In C and C++, two different operators are used for calling methods: you use -`.` if you’re calling a method on the object directly and `->` if you’re -calling the method on a pointer to the object and need to dereference the -pointer first. In other words, if `object` is a pointer, -`object->`something`()` is similar to `(*object).`something`()`. -> +> `.` if you’re calling a method on the object directly and `->` if you’re +> calling the method on a pointer to the object and need to dereference the +> pointer first. In other words, if `object` is a pointer, +> `object->something()` is similar to `(*object).something()`. +> > Rust doesn’t have an equivalent to the `->` operator; instead, Rust has a -feature called *automatic referencing and dereferencing*. Calling methods is -one of the few places in Rust that has this behavior. -> -> Here’s how it works: when you call a method with `object.`something`()`, Rust -automatically adds in `&`, `&mut`, or `*` so `object` matches the signature of -the method. In other words, the following are the same: +> feature called *automatic referencing and dereferencing*. Calling methods is +> one of the few places in Rust with this behavior. > -> ``` +> Here’s how it works: when you call a method with `object.something()`, Rust +> automatically adds in `&`, `&mut`, or `*` so `object` matches the signature of +> the method. In other words, the following are the same: +> +> +> +> ````rust +> # #[derive(Debug,Copy,Clone)] +> # struct Point { +> # x: f64, +> # y: f64, +> # } +> # +> # impl Point { +> # fn distance(&self, other: &Point) -> f64 { +> # let x_squared = f64::powi(other.x - self.x, 2); +> # let y_squared = f64::powi(other.y - self.y, 2); +> # +> # f64::sqrt(x_squared + y_squared) +> # } +> # } +> # let p1 = Point { x: 0.0, y: 0.0 }; +> # let p2 = Point { x: 5.0, y: 6.5 }; > p1.distance(&p2); > (&p1).distance(&p2); -> ``` -> +> ```` +> > The first one looks much cleaner. This automatic referencing behavior works -because methods have a clear receiver—the type of `self`. Given the receiver -and name of a method, Rust can figure out definitively whether the method is -reading (`&self`), mutating (`&mut self`), or consuming (`self`). The fact that -Rust makes borrowing implicit for method receivers is a big part of making -ownership ergonomic in practice. +> because methods have a clear receiver—the type of `self`. Given the receiver +> and name of a method, Rust can figure out definitively whether the method is +> reading (`&self`), mutating (`&mut self`), or consuming (`self`). The fact +> that Rust makes borrowing implicit for method receivers is a big part of +> making ownership ergonomic in practice. ### Methods with More Parameters @@ -847,7 +898,7 @@ within `self` (the first `Rectangle`); otherwise, it should return `false`. That is, once we’ve defined the `can_hold` method, we want to be able to write the program shown in Listing 5-14. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -894,7 +945,7 @@ Boolean, and the implementation will check whether the width and height of respectively. Let’s add the new `can_hold` method to the `impl` block from Listing 5-13, shown in Listing 5-15. -Filename: src/main.rs +src/main.rs ``` impl Rectangle { @@ -908,8 +959,7 @@ impl Rectangle { } ``` -Listing 5-15: Implementing the `can_hold` method on `Rectangle` that takes -another `Rectangle` instance as a parameter +Listing 5-15: Implementing the `can_hold` method on `Rectangle` that takes another `Rectangle` instance as a parameter When we run this code with the `main` function in Listing 5-14, we’ll get our desired output. Methods can take multiple parameters that we add to the @@ -937,8 +987,8 @@ Filename: src/main.rs ``` impl Rectangle { - fn square(size: u32) -> 1 Self { - 2 Self { + fn square(size: u32) -> Self { + Self { width: size, height: size, } @@ -946,14 +996,15 @@ impl Rectangle { } ``` -The `Self` keywords in the return type [1] and in the body of the function [2] -are aliases for the type that appears after the `impl` keyword, which in this -case is `Rectangle`. +The `Self` keywords in the return type and in the body of the function are +aliases for the type that appears after the `impl` keyword, which in this case +is `Rectangle`. To call this associated function, we use the `::` syntax with the struct name; `let sq = Rectangle::square(3);` is an example. This function is namespaced by the struct: the `::` syntax is used for both associated functions and -namespaces created by modules. We’ll discuss modules in Chapter 7. +namespaces created by modules. We’ll discuss modules in Chapter +7. ### Multiple impl Blocks @@ -961,6 +1012,7 @@ Each struct is allowed to have multiple `impl` blocks. For example, Listing 5-15 is equivalent to the code shown in Listing 5-16, which has each method in its own `impl` block. + ``` impl Rectangle { fn area(&self) -> u32 { @@ -992,4 +1044,3 @@ structs have. But structs aren’t the only way you can create custom types: let’s turn to Rust’s enum feature to add another tool to your toolbox. - diff --git a/nostarch/chapter06.md b/nostarch/chapter06.md index ea4328bea8..66dccb4b91 100644 --- a/nostarch/chapter06.md +++ b/nostarch/chapter06.md @@ -58,8 +58,8 @@ enum IpAddrKind { We can create instances of each of the two variants of `IpAddrKind` like this: ``` -let four = IpAddrKind::V4; -let six = IpAddrKind::V6; + let four = IpAddrKind::V4; + let six = IpAddrKind::V6; ``` Note that the variants of the enum are namespaced under its identifier, and we @@ -74,8 +74,8 @@ fn route(ip_kind: IpAddrKind) {} And we can call this function with either variant: ``` -route(IpAddrKind::V4); -route(IpAddrKind::V6); + route(IpAddrKind::V4); + route(IpAddrKind::V6); ``` Using enums has even more advantages. Thinking more about our IP address type, @@ -84,39 +84,39 @@ only know what *kind* it is. Given that you just learned about structs in Chapter 5, you might be tempted to tackle this problem with structs as shown in Listing 6-1. + ``` -1 enum IpAddrKind { - V4, - V6, -} + enum IpAddrKind { + V4, + V6, + } -2 struct IpAddr { - 3 kind: IpAddrKind, - 4 address: String, -} + struct IpAddr { + kind: IpAddrKind, + address: String, + } -5 let home = IpAddr { - kind: IpAddrKind::V4, - address: String::from("127.0.0.1"), -}; + let home = IpAddr { + kind: IpAddrKind::V4, + address: String::from("127.0.0.1"), + }; -6 let loopback = IpAddr { - kind: IpAddrKind::V6, - address: String::from("::1"), -}; + let loopback = IpAddr { + kind: IpAddrKind::V6, + address: String::from("::1"), + }; ``` -Listing 6-1: Storing the data and `IpAddrKind` variant of an IP address using a -`struct` +Listing 6-1: Storing the data and `IpAddrKind` variant of an IP address using a `struct` -Here, we’ve defined a struct `IpAddr` [2] that has two fields: a `kind` field -[3] that is of type `IpAddrKind` (the enum we defined previously [1]) and an -`address` field [4] of type `String`. We have two instances of this struct. The -first is `home` [5], and it has the value `IpAddrKind::V4` as its `kind` with -associated address data of `127.0.0.1`. The second instance is `loopback` [6]. -It has the other variant of `IpAddrKind` as its `kind` value, `V6`, and has -address `::1` associated with it. We’ve used a struct to bundle the `kind` and -`address` values together, so now the variant is associated with the value. +Here, we’ve defined a struct `IpAddr` that has two fields: a `kind` field that +is of type `IpAddrKind` (the enum we defined previously) and an `address` field +of type `String`. We have two instances of this struct. The first is `home`, +and it has the value `IpAddrKind::V4` as its `kind` with associated address +data of `127.0.0.1`. The second instance is `loopback`. It has the other +variant of `IpAddrKind` as its `kind` value, `V6`, and has address `::1` +associated with it. We’ve used a struct to bundle the `kind` and `address` +values together, so now the variant is associated with the value. However, representing the same concept using just an enum is more concise: rather than an enum inside a struct, we can put data directly into each enum @@ -124,14 +124,14 @@ variant. This new definition of the `IpAddr` enum says that both `V4` and `V6` variants will have associated `String` values: ``` -enum IpAddr { - V4(String), - V6(String), -} + enum IpAddr { + V4(String), + V6(String), + } -let home = IpAddr::V4(String::from("127.0.0.1")); + let home = IpAddr::V4(String::from("127.0.0.1")); -let loopback = IpAddr::V6(String::from("::1")); + let loopback = IpAddr::V6(String::from("::1")); ``` We attach data to each variant of the enum directly, so there is no need for an @@ -150,31 +150,32 @@ still express `V6` addresses as one `String` value, we wouldn’t be able to wit a struct. Enums handle this case with ease: ``` -enum IpAddr { - V4(u8, u8, u8, u8), - V6(String), -} + enum IpAddr { + V4(u8, u8, u8, u8), + V6(String), + } -let home = IpAddr::V4(127, 0, 0, 1); + let home = IpAddr::V4(127, 0, 0, 1); -let loopback = IpAddr::V6(String::from("::1")); + let loopback = IpAddr::V6(String::from("::1")); ``` We’ve shown several different ways to define data structures to store version four and version six IP addresses. However, as it turns out, wanting to store IP addresses and encode which kind they are is so common that the standard -library has a definition we can use! Let’s look at how the standard library -defines `IpAddr`: it has the exact enum and variants that we’ve defined and -used, but it embeds the address data inside the variants in the form of two -different structs, which are defined differently for each variant: +library has a definition we can use! Let’s look at how +the standard library defines `IpAddr`: it has the exact enum and variants that +we’ve defined and used, but it embeds the address data inside the variants in +the form of two different structs, which are defined differently for each +variant: ``` struct Ipv4Addr { - --snip-- + // --snip-- } struct Ipv6Addr { - --snip-- + // --snip-- } enum IpAddr { @@ -196,6 +197,7 @@ more about bringing types into scope in Chapter 7. Let’s look at another example of an enum in Listing 6-2: this one has a wide variety of types embedded in its variants. + ``` enum Message { Quit, @@ -205,8 +207,7 @@ enum Message { } ``` -Listing 6-2: A `Message` enum whose variants each store different amounts and -types of values +Listing 6-2: A `Message` enum whose variants each store different amounts and types of values This enum has four variants with different types: @@ -240,20 +241,20 @@ define methods on structs using `impl`, we’re also able to define methods on enums. Here’s a method named `call` that we could define on our `Message` enum: ``` -impl Message { - fn call(&self) { - 1 // method body would be defined here + impl Message { + fn call(&self) { + // method body would be defined here + } } -} -2 let m = Message::Write(String::from("hello")); -m.call(); + let m = Message::Write(String::from("hello")); + m.call(); ``` The body of the method would use `self` to get the value that we called the -method on. In this example, we’ve created a variable `m` [2] that has the value +method on. In this example, we’ve created a variable `m` that has the value `Message::Write(String::from("hello"))`, and that is what `self` will be in the -body of the `call` method [1] when `m.call()` runs. +body of the `call` method when `m.call()` runs. Let’s look at another enum in the standard library that is very common and useful: `Option`. @@ -264,12 +265,12 @@ This section explores a case study of `Option`, which is another enum defined by the standard library. The `Option` type encodes the very common scenario in which a value could be something or it could be nothing. -For example, if you request the first item in a list containing multiple items, -you would get a value. If you request the first item in an empty list, you -would get nothing. Expressing this concept in terms of the type system means -the compiler can check whether you’ve handled all the cases you should be -handling; this functionality can prevent bugs that are extremely common in -other programming languages. +For example, if you request the first item in a non-empty list, you would get +a value. If you request the first item in an empty list, you would get nothing. +Expressing this concept in terms of the type system means the compiler can +check whether you’ve handled all the cases you should be handling; this +functionality can prevent bugs that are extremely common in other programming +languages. Programming language design is often thought of in terms of which features you include, but the features you exclude are important too. Rust doesn’t have the @@ -281,16 +282,17 @@ In his 2009 presentation “Null References: The Billion Dollar Mistake,” Tony Hoare, the inventor of null, has this to say: > I call it my billion-dollar mistake. At that time, I was designing the first -comprehensive type system for references in an object-oriented language. My -goal was to ensure that all use of references should be absolutely safe, with -checking performed automatically by the compiler. But I couldn’t resist the -temptation to put in a null reference, simply because it was so easy to -implement. This has led to innumerable errors, vulnerabilities, and system -crashes, which have probably caused a billion dollars of pain and damage in the -last forty years.The problem with null values is that if you try to use a null -value as a not-null value, you’ll get an error of some kind. Because this null -or not-null property is pervasive, it’s extremely easy to make this kind of -error. +> comprehensive type system for references in an object-oriented language. My +> goal was to ensure that all use of references should be absolutely safe, with +> checking performed automatically by the compiler. But I couldn’t resist the +> temptation to put in a null reference, simply because it was so easy to +> implement. This has led to innumerable errors, vulnerabilities, and system +> crashes, which have probably caused a billion dollars of pain and damage in +> the last forty years. + +The problem with null values is that if you try to use a null value as a +not-null value, you’ll get an error of some kind. Because this null or not-null +property is pervasive, it’s extremely easy to make this kind of error. However, the concept that null is trying to express is still a useful one: a null is a value that is currently invalid or absent for some reason. @@ -298,7 +300,8 @@ null is a value that is currently invalid or absent for some reason. The problem isn’t really with the concept but with the particular implementation. As such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is -`Option`, and it is defined by the standard library as follows: +`Option`, and it is defined by the standard library +as follows: ``` enum Option { @@ -319,13 +322,13 @@ For now, all you need to know is that `` means that the `Some` variant of the `Option` enum can hold one piece of data of any type, and that each concrete type that gets used in place of `T` makes the overall `Option` type a different type. Here are some examples of using `Option` values to hold -number types and string types: +number types and char types: ``` -let some_number = Some(5); -let some_char = Some('e'); + let some_number = Some(5); + let some_char = Some('e'); -let absent_number: Option = None; + let absent_number: Option = None; ``` The type of `some_number` is `Option`. The type of `some_char` is @@ -347,15 +350,17 @@ definitely a valid value. For example, this code won’t compile, because it’s trying to add an `i8` to an `Option`: ``` -let x: i8 = 5; -let y: Option = Some(5); + let x: i8 = 5; + let y: Option = Some(5); -let sum = x + y; + let sum = x + y; ``` If we run this code, we get an error message like this one: ``` +$ cargo run + Compiling enums v0.1.0 (file:///projects/enums) error[E0277]: cannot add `Option` to `i8` --> src/main.rs:5:17 | @@ -363,6 +368,14 @@ error[E0277]: cannot add `Option` to `i8` | ^ no implementation for `i8 + Option` | = help: the trait `Add>` is not implemented for `i8` + = help: the following other types implement trait `Add`: + `&'a i8` implements `Add` + `&i8` implements `Add<&i8>` + `i8` implements `Add<&i8>` + `i8` implements `Add` + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `enums` (bin "enums") due to 1 previous error ``` Intense! In effect, this error message means that Rust doesn’t understand how @@ -390,8 +403,9 @@ the safety of Rust code. So how do you get the `T` value out of a `Some` variant when you have a value of type `Option` so that you can use that value? The `Option` enum has a large number of methods that are useful in a variety of situations; you can -check them out in its documentation. Becoming familiar with the methods on -`Option` will be extremely useful in your journey with Rust. +check them out in its documentation. Becoming familiar +with the methods on `Option` will be extremely useful in your journey with +Rust. In general, in order to use an `Option` value, you want to have code that will handle each variant. You want some code that will run only when you have a @@ -402,15 +416,20 @@ does just this when used with enums: it will run different code depending on which variant of the enum it has, and that code can use the data inside the matching value. + + + + ## The match Control Flow Construct Rust has an extremely powerful control flow construct called `match` that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. Patterns can be made up of literal values, -variable names, wildcards, and many other things; Chapter 18 covers all the -different kinds of patterns and what they do. The power of `match` comes from -the expressiveness of the patterns and the fact that the compiler confirms that -all possible cases are handled. +variable names, wildcards, and many other things; Chapter +19 covers all the different kinds of patterns +and what they do. The power of `match` comes from the expressiveness of the +patterns and the fact that the compiler confirms that all possible cases are +handled. Think of a `match` expression as being like a coin-sorting machine: coins slide down a track with variously sized holes along it, and each coin falls through @@ -423,8 +442,9 @@ function that takes an unknown US coin and, in a similar way as the counting machine, determines which coin it is and returns its value in cents, as shown in Listing 6-3. + ``` -1 enum Coin { +enum Coin { Penny, Nickel, Dime, @@ -432,8 +452,8 @@ in Listing 6-3. } fn value_in_cents(coin: Coin) -> u8 { - 2 match coin { - 3 Coin::Penny => 1, + match coin { + Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, @@ -441,20 +461,19 @@ fn value_in_cents(coin: Coin) -> u8 { } ``` -Listing 6-3: An enum and a `match` expression that has the variants of the enum -as its patterns +Listing 6-3: An enum and a `match` expression that has the variants of the enum as its patterns Let’s break down the `match` in the `value_in_cents` function. First we list the `match` keyword followed by an expression, which in this case is the value -`coin` [2]. This seems very similar to an expression used with `if`, but -there’s a big difference: with `if`, the expression needs to return a Boolean -value, but here it can return any type. The type of `coin` in this example is -the `Coin` enum that we defined at [1]. +`coin`. This seems very similar to a conditional expression used with `if`, but +there’s a big difference: with `if`, the condition needs to evaluate to a +Boolean value, but here it can be any type. The type of `coin` in this example +is the `Coin` enum that we defined on the first line. Next are the `match` arms. An arm has two parts: a pattern and some code. The first arm here has a pattern that is the value `Coin::Penny` and then the `=>` -operator that separates the pattern and the code to run [3]. The code in this -case is just the value `1`. Each arm is separated from the next with a comma. +operator that separates the pattern and the code to run. The code in this case +is just the value `1`. Each arm is separated from the next with a comma. When the `match` expression executes, it compares the resultant value against the pattern of each arm, in order. If a pattern matches the value, the code @@ -500,12 +519,13 @@ designs, so only quarters have this extra value. We can add this information to our `enum` by changing the `Quarter` variant to include a `UsState` value stored inside it, which we’ve done in Listing 6-4. + ``` #[derive(Debug)] // so we can inspect the state in a minute enum UsState { Alabama, Alaska, - --snip-- + // --snip-- } enum Coin { @@ -516,8 +536,7 @@ enum Coin { } ``` -Listing 6-4: A `Coin` enum in which the `Quarter` variant also holds a -`UsState` value +Listing 6-4: A `Coin` enum in which the `Quarter` variant also holds a `UsState` value Let’s imagine that a friend is trying to collect all 50 state quarters. While we sort our loose change by coin type, we’ll also call out the name of the @@ -536,7 +555,7 @@ fn value_in_cents(coin: Coin) -> u8 { Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { - println!("State quarter from {:?}!", state); + println!("State quarter from {state:?}!"); 25 } } @@ -566,43 +585,48 @@ operations. This function is very easy to write, thanks to `match`, and will look like Listing 6-5. + ``` -fn plus_one(x: Option) -> Option { - match x { - 1 None => None, - 2 Some(i) => Some(i + 1), + fn plus_one(x: Option) -> Option { + match x { + None => None, + Some(i) => Some(i + 1), + } } -} -let five = Some(5); -let six = plus_one(five); 3 -let none = plus_one(None); 4 + let five = Some(5); + let six = plus_one(five); + let none = plus_one(None); ``` Listing 6-5: A function that uses a `match` expression on an `Option` Let’s examine the first execution of `plus_one` in more detail. When we call -`plus_one(five)` [3], the variable `x` in the body of `plus_one` will have the +`plus_one(five)`, the variable `x` in the body of `plus_one` will have the value `Some(5)`. We then compare that against each match arm: ``` -None => None, + None => None, ``` -The `Some(5)` value doesn’t match the pattern `None` [1], so we continue to the +The `Some(5)` value doesn’t match the pattern `None`, so we continue to the next arm: ``` -Some(i) => Some(i + 1), + Some(i) => Some(i + 1), ``` -Does `Some(5)` match `Some(i)` [2]? Why yes, it does! We have the same variant. -The `i` binds to the value contained in `Some`, so `i` takes the value `5`. The -code in the match arm is then executed, so we add 1 to the value of `i` and -create a new `Some` value with our total `6` inside. +Does `Some(5)` match `Some(i)`? It does! We have the same variant. The `i` +binds to the value contained in `Some`, so `i` takes the value `5`. The code in +the match arm is then executed, so we add 1 to the value of `i` and create a +new `Some` value with our total `6` inside. Now let’s consider the second call of `plus_one` in Listing 6-5, where `x` is -`None` [4]. We enter the `match` and compare to the first arm [1]. +`None`. We enter the `match` and compare to the first arm: + +``` + None => None, +``` It matches! There’s no value to add to, so the program stops and returns the `None` value on the right side of `=>`. Because the first arm matched, no other @@ -621,11 +645,11 @@ cover all possibilities. Consider this version of our `plus_one` function, which has a bug and won’t compile: ``` -fn plus_one(x: Option) -> Option { - match x { - Some(i) => Some(i + 1), + fn plus_one(x: Option) -> Option { + match x { + Some(i) => Some(i + 1), + } } -} ``` We didn’t handle the `None` case, so this code will cause a bug. Luckily, it’s @@ -633,21 +657,31 @@ a bug Rust knows how to catch. If we try to compile this code, we’ll get this error: ``` +$ cargo run + Compiling enums v0.1.0 (file:///projects/enums) error[E0004]: non-exhaustive patterns: `None` not covered - --> src/main.rs:3:15 - | -3 | match x { - | ^ pattern `None` not covered - | - note: `Option` defined here - = note: the matched value is of type `Option` -help: ensure that all possible cases are being handled by adding -a match arm with a wildcard pattern or an explicit pattern as -shown + --> src/main.rs:3:15 + | +3 | match x { + | ^ pattern `None` not covered + | +note: `Option` defined here + --> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/core/src/option.rs:571:1 + | +571 | pub enum Option { + | ^^^^^^^^^^^^^^^^^^ +... +575 | None, + | ---- not covered + = note: the matched value is of type `Option` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | 4 ~ Some(i) => Some(i + 1), 5 ~ None => todo!(), | + +For more information about this error, try `rustc --explain E0004`. +error: could not compile `enums` (bin "enums") due to 1 previous error ``` Rust knows that we didn’t cover every possible case, and even knows which @@ -657,7 +691,7 @@ possibility in order for the code to be valid. Especially in the case of `None` case, it protects us from assuming that we have a value when we might have null, thus making the billion-dollar mistake discussed earlier impossible. -### Catch-all Patterns and the _ Placeholder +### Catch-All Patterns and the \_ Placeholder Using enums, we can also take special actions for a few particular values, but for all other values take one default action. Imagine we’re implementing a game @@ -670,22 +704,22 @@ functions without bodies because actually implementing them is out of scope for this example: ``` -let dice_roll = 9; -match dice_roll { - 3 => add_fancy_hat(), - 7 => remove_fancy_hat(), - 1 other => move_player(other), -} + let dice_roll = 9; + match dice_roll { + 3 => add_fancy_hat(), + 7 => remove_fancy_hat(), + other => move_player(other), + } -fn add_fancy_hat() {} -fn remove_fancy_hat() {} -fn move_player(num_spaces: u8) {} + fn add_fancy_hat() {} + fn remove_fancy_hat() {} + fn move_player(num_spaces: u8) {} ``` For the first two arms, the patterns are the literal values `3` and `7`. For the last arm that covers every other possible value, the pattern is the -variable we’ve chosen to name `other` [1]. The code that runs for the `other` -arm uses the variable by passing it to the `move_player` function. +variable we’ve chosen to name `other`. The code that runs for the `other` arm +uses the variable by passing it to the `move_player` function. This code compiles, even though we haven’t listed all the possible values a `u8` can have, because the last pattern will match all values not specifically @@ -704,16 +738,16 @@ a 7, you must roll again. We no longer need to use the catch-all value, so we can change our code to use `_` instead of the variable named `other`: ``` -let dice_roll = 9; -match dice_roll { - 3 => add_fancy_hat(), - 7 => remove_fancy_hat(), - _ => reroll(), -} + let dice_roll = 9; + match dice_roll { + 3 => add_fancy_hat(), + 7 => remove_fancy_hat(), + _ => reroll(), + } -fn add_fancy_hat() {} -fn remove_fancy_hat() {} -fn reroll() {} + fn add_fancy_hat() {} + fn remove_fancy_hat() {} + fn reroll() {} ``` This example also meets the exhaustiveness requirement because we’re explicitly @@ -722,29 +756,30 @@ ignoring all other values in the last arm; we haven’t forgotten anything. Finally, we’ll change the rules of the game one more time so that nothing else happens on your turn if you roll anything other than a 3 or a 7. We can express that by using the unit value (the empty tuple type we mentioned in “The Tuple -Type” on page XX) as the code that goes with the `_` arm: +Type” section) as the code that goes with the `_` arm: ``` -let dice_roll = 9; -match dice_roll { - 3 => add_fancy_hat(), - 7 => remove_fancy_hat(), - _ => (), -} + let dice_roll = 9; + match dice_roll { + 3 => add_fancy_hat(), + 7 => remove_fancy_hat(), + _ => (), + } -fn add_fancy_hat() {} -fn remove_fancy_hat() {} + fn add_fancy_hat() {} + fn remove_fancy_hat() {} ``` Here, we’re telling Rust explicitly that we aren’t going to use any other value that doesn’t match a pattern in an earlier arm, and we don’t want to run any code in this case. -There’s more about patterns and matching that we’ll cover in Chapter 18. For -now, we’re going to move on to the `if let` syntax, which can be useful in -situations where the `match` expression is a bit wordy. +There’s more about patterns and matching that we’ll cover in Chapter +19. For now, we’re going to move on to the +`if let` syntax, which can be useful in situations where the `match` expression +is a bit wordy. -## Concise Control Flow with if let +## Concise Control Flow with if let and let else The `if let` syntax lets you combine `if` and `let` into a less verbose way to handle values that match one pattern while ignoring the rest. Consider the @@ -752,31 +787,30 @@ program in Listing 6-6 that matches on an `Option` value in the `config_max` variable but only wants to execute code if the value is the `Some` variant. + ``` -let config_max = Some(3u8); -match config_max { - Some(max) => println!("The maximum is configured to be {max}"), - _ => (), -} + let config_max = Some(3u8); + match config_max { + Some(max) => println!("The maximum is configured to be {max}"), + _ => (), + } ``` -Listing 6-6: A `match` that only cares about executing code when the value is -`Some` +Listing 6-6: A `match` that only cares about executing code when the value is `Some` If the value is `Some`, we print out the value in the `Some` variant by binding the value to the variable `max` in the pattern. We don’t want to do anything -with the `None` value. To satisfy the `match` expression, we have to add `_ => -()` after processing just one variant, which is annoying boilerplate code to +with the `None` value. To satisfy the `match` expression, we have to add `_ => ()` after processing just one variant, which is annoying boilerplate code to add. Instead, we could write this in a shorter way using `if let`. The following code behaves the same as the `match` in Listing 6-6: ``` -let config_max = Some(3u8); -if let Some(max) = config_max { - println!("The maximum is configured to be {max}"); -} + let config_max = Some(3u8); + if let Some(max) = config_max { + println!("The maximum is configured to be {max}"); + } ``` The syntax `if let` takes a pattern and an expression separated by an equal @@ -784,8 +818,8 @@ sign. It works the same way as a `match`, where the expression is given to the `match` and the pattern is its first arm. In this case, the pattern is `Some(max)`, and the `max` binds to the value inside the `Some`. We can then use `max` in the body of the `if let` block in the same way we used `max` in -the corresponding `match` arm. The code in the `if let` block isn’t run if the -value doesn’t match the pattern. +the corresponding `match` arm. The code in the `if let` block only runs if the +value matches the pattern. Using `if let` means less typing, less indentation, and less boilerplate code. However, you lose the exhaustive checking that `match` enforces. Choosing @@ -805,26 +839,127 @@ announcing the state of the quarters, we could do that with a `match` expression, like this: ``` -let mut count = 0; -match coin { - Coin::Quarter(state) => println!("State quarter from {:?}!", state), - _ => count += 1, -} + let mut count = 0; + match coin { + Coin::Quarter(state) => println!("State quarter from {state:?}!"), + _ => count += 1, + } ``` Or we could use an `if let` and `else` expression, like this: ``` -let mut count = 0; -if let Coin::Quarter(state) = coin { - println!("State quarter from {:?}!", state); -} else { - count += 1; + let mut count = 0; + if let Coin::Quarter(state) = coin { + println!("State quarter from {state:?}!"); + } else { + count += 1; + } +``` + +## Staying on the “happy path” with let else + +One common pattern is to perform some computation when a value is present and +return a default value otherwise. Continuing on with our example of coins with a +`UsState` value, if we wanted to say something funny depending on how old the +state on the quarter was, we might introduce a method on `UsState` to check the +age of a state, like so: + +``` +impl UsState { + fn existed_in(&self, year: u16) -> bool { + match self { + UsState::Alabama => year >= 1819, + UsState::Alaska => year >= 1959, + // -- snip -- + } + } +} +``` + +Then we might use `if let` to match on the type of coin, introducing a `state` +variable within the body of the condition, as in Listing 6-7. + +src/main.rs + +``` +fn describe_state_quarter(coin: Coin) -> Option { + if let Coin::Quarter(state) = coin { + if state.existed_in(1900) { + Some(format!("{state:?} is pretty old, for America!")) + } else { + Some(format!("{state:?} is relatively new.")) + } + } else { + None + } } ``` +Listing 6-7: Using + +That gets the job done, but it has pushed the work into the body of the `if let` +statement, and if the work to be done is more complicated, it might be hard to +follow exactly how the top-level branches relate. We could also take advantage +of the fact that expressions produce a value either to produce the `state` from +the `if let` or to return early, as in Listing 6-8. (You could do similar with a +`match`, of course!) + +src/main.rs + +``` +fn describe_state_quarter(coin: Coin) -> Option { + let state = if let Coin::Quarter(state) = coin { + state + } else { + return None; + }; + + if state.existed_in(1900) { + Some(format!("{state:?} is pretty old, for America!")) + } else { + Some(format!("{state:?} is relatively new.")) + } +} +``` + +Listing 6-8: Using `if let` to produce a value or return early. + +This is a bit annoying to follow in its own way, though! One branch of the `if let` produces a value, and the other one returns from the function entirely. + +To make this common pattern nicer to express, Rust has `let`-`else`. The +`let`-`else` syntax takes a pattern on the left side and an expression on the +right, very similar to `if let`, but it does not have an `if` branch, only an +`else` branch. If the pattern matches, it will bind the value from the pattern +in the outer scope. If the pattern does *not* match, the program will flow into +the `else` arm, which must return from the function. + +In Listing 6-9, you can see how Listing 6-8 looks when using `let`-`else` in +place of `if let`. Notice that it stays “on the happy path” in the main body of +the function this way, without having significantly different control flow for +two branches the way the `if let` did. + +src/main.rs + +``` +fn describe_state_quarter(coin: Coin) -> Option { + let Coin::Quarter(state) = coin else { + return None; + }; + + if state.existed_in(1900) { + Some(format!("{state:?} is pretty old, for America!")) + } else { + Some(format!("{state:?} is relatively new.")) + } +} +``` + +Listing 6-9: Using `let`-`else` to clarify the flow through the function. + If you have a situation in which your program has logic that is too verbose to -express using a `match`, remember that `if let` is in your Rust toolbox as well. +express using a `match`, remember that `if let` and `let else` are in your Rust +toolbox as well. ## Summary @@ -842,4 +977,3 @@ function expects. In order to provide a well-organized API to your users that is straightforward to use and only exposes exactly what your users will need, let’s now turn to Rust’s modules. - diff --git a/nostarch/chapter07.md b/nostarch/chapter07.md index 0a7a407935..92b48e3d02 100644 --- a/nostarch/chapter07.md +++ b/nostarch/chapter07.md @@ -20,7 +20,7 @@ optionally one library crate. As a package grows, you can extract parts into separate crates that become external dependencies. This chapter covers all these techniques. For very large projects comprising a set of interrelated packages that evolve together, Cargo provides *workspaces*, which we’ll cover -in the “Cargo Workspaces” section in Chapter 14. +in “Cargo Workspaces” in Chapter 14. We’ll also discuss encapsulating implementation details, which lets you reuse code at a higher level: once you’ve implemented an operation, other code can @@ -59,14 +59,14 @@ The first parts of the module system we’ll cover are packages and crates. A *crate* is the smallest amount of code that the Rust compiler considers at a time. Even if you run `rustc` rather than `cargo` and pass a single source code -file (as we did all the way back in the “Writing and Running a Rust Program” -section of Chapter 1), the compiler considers that file to be a crate. Crates -can contain modules, and the modules may be defined in other files that get -compiled with the crate, as we’ll see in the coming sections. +file (as we did all the way back in “Writing and Running a Rust Program” in +Chapter 1), the compiler considers that file to be a crate. Crates can contain +modules, and the modules may be defined in other files that get compiled with +the crate, as we’ll see in the coming sections. A crate can come in one of two forms: a binary crate or a library crate. *Binary crates* are programs you can compile to an executable that you can run, -such as a command-line program or a server. Each must have a function called +such as a command line program or a server. Each must have a function called `main` that defines what happens when the executable runs. All the crates we’ve created so far have been binary crates. @@ -78,22 +78,18 @@ Most of the time when Rustaceans say “crate”, they mean library crate, and t use “crate” interchangeably with the general programming concept of a “library”. The *crate root* is a source file that the Rust compiler starts from and makes -up the root module of your crate (we’ll explain modules in depth in the -“Defining Modules to Control Scope and Privacy” -section). +up the root module of your crate (we’ll explain modules in depth in “Defining +Modules to Control Scope and Privacy”). A *package* is a bundle of one or more crates that provides a set of functionality. A package contains a *Cargo.toml* file that describes how to build those crates. Cargo is actually a package that contains the binary crate -for the command-line tool you’ve been using to build your code. The Cargo +for the command line tool you’ve been using to build your code. The Cargo package also contains a library crate that the binary crate depends on. Other projects can depend on the Cargo library crate to use the same logic the Cargo -command-line tool uses. - -A crate can come in one of two forms: a binary crate or a library crate. A -package can contain as many binary crates as you like, but at most only one -library crate. A package must contain at least one crate, whether that’s a -library or binary crate. +command line tool uses. A package can contain as many binary crates as you +like, but at most only one library crate. A package must contain at least one +crate, whether that’s a library or binary crate. Let’s walk through what happens when we create a package. First we enter the command `cargo new my-project`: @@ -187,7 +183,7 @@ backyard The crate root file in this case is *src/main.rs*, and it contains: -Filename: src/main.rs +src/main.rs ``` use crate::garden::vegetables::Asparagus; @@ -200,15 +196,19 @@ fn main() { } ``` + + The `pub mod garden;` line tells the compiler to include the code it finds in *src/garden.rs*, which is: -Filename: src/garden.rs +src/garden.rs ``` pub mod vegetables; ``` + + Here, `pub mod vegetables;` means the code in *src/garden/vegetables.rs* is included too. That code is: @@ -245,7 +245,7 @@ modules. Create a new library named `restaurant` by running `cargo new restauran define some modules and function signatures; this code is the front of house section. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -265,8 +265,7 @@ mod front_of_house { } ``` -Listing 7-1: A `front_of_house` module containing other -modules that then contain functions +Listing 7-1: A `front_of_house` module containing other modules that then contain functions We define a module with the `mod` keyword followed by the name of the module (in this case, `front_of_house`). The body of the module then goes inside curly @@ -288,6 +287,7 @@ known as the *module tree*. Listing 7-2 shows the module tree for the structure in Listing 7-1. + ``` crate └── front_of_house @@ -300,8 +300,7 @@ crate └── take_payment ``` -Listing 7-2: The module tree for the code in Listing -7-1 +Listing 7-2: The module tree for the code in Listing 7-1 This tree shows how some of the modules nest inside other modules; for example, `hosting` nests inside `front_of_house`. The tree also shows that some modules @@ -347,7 +346,7 @@ The `eat_at_restaurant` function is part of our library crate’s public API, so we mark it with the `pub` keyword. In the “Exposing Paths with the `pub` Keyword” section, we’ll go into more detail about `pub`. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -365,8 +364,7 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-3: Calling the `add_to_waitlist` function using -absolute and relative paths +Listing 7-3: Calling the `add_to_waitlist` function using absolute and relative paths The first time we call the `add_to_waitlist` function in `eat_at_restaurant`, we use an absolute path. The `add_to_waitlist` function is defined in the same @@ -400,6 +398,7 @@ each other. Let’s try to compile Listing 7-3 and find out why it won’t compile yet! The errors we get are shown in Listing 7-4. + ``` $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) @@ -435,8 +434,7 @@ For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` (lib) due to 2 previous errors ``` -Listing 7-4: Compiler errors from building the code in -Listing 7-3 +Listing 7-4: Compiler errors from building the code in Listing 7-3 The error messages say that module `hosting` is private. In other words, we have the correct paths for the `hosting` module and the `add_to_waitlist` @@ -459,14 +457,14 @@ inner code you can change without breaking outer code. However, Rust does give you the option to expose inner parts of child modules’ code to outer ancestor modules by using the `pub` keyword to make an item public. -### Exposing Paths with the `pub` Keyword +### Exposing Paths with the pub Keyword Let’s return to the error in Listing 7-4 that told us the `hosting` module is private. We want the `eat_at_restaurant` function in the parent module to have access to the `add_to_waitlist` function in the child module, so we mark the `hosting` module with the `pub` keyword, as shown in Listing 7-5. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -475,21 +473,15 @@ mod front_of_house { } } -pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); - - // Relative path - front_of_house::hosting::add_to_waitlist(); -} +// -- snip -- ``` -Listing 7-5: Declaring the `hosting` module as `pub` to -use it from `eat_at_restaurant` +Listing 7-5: Declaring the `hosting` module as `pub` to use it from `eat_at_restaurant` Unfortunately, the code in Listing 7-5 still results in compiler errors, as shown in Listing 7-6. + ``` $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) @@ -521,8 +513,7 @@ For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` (lib) due to 2 previous errors ``` -Listing 7-6: Compiler errors from building the code in -Listing 7-5 +Listing 7-6: Compiler errors from building the code in Listing 7-5 What happened? Adding the `pub` keyword in front of `mod hosting` makes the module public. With this change, if we can access `front_of_house`, we can @@ -540,7 +531,7 @@ modules. Let’s also make the `add_to_waitlist` function public by adding the `pub` keyword before its definition, as in Listing 7-7. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -549,18 +540,10 @@ mod front_of_house { } } -pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); - - // Relative path - front_of_house::hosting::add_to_waitlist(); -} +// -- snip -- ``` -Listing 7-7: Adding the `pub` keyword to `mod hosting` -and `fn add_to_waitlist` lets us call the function from -`eat_at_restaurant` +Listing 7-7: Adding the `pub` keyword to `mod hosting` and `fn add_to_waitlist` lets us call the function from `eat_at_restaurant` Now the code will compile! To see why adding the `pub` keyword lets us use these paths in `eat_at_restaurant` with respect to the privacy rules, let’s look @@ -588,32 +571,31 @@ If you plan on sharing your library crate so other projects can use your code, your public API is your contract with users of your crate that determines how they can interact with your code. There are many considerations around managing changes to your public API to make it easier for people to depend on your -crate. These considerations are out of the scope of this book; if you’re +crate. These considerations are beyond the scope of this book; if you’re interested in this topic, see The Rust API Guidelines at *https://rust-lang.github.io/api-guidelines/*. - > - > #### Best Practices for Packages with a Binary and a Library - > - > We mentioned that a package can contain both a *src/main.rs* binary crate - > root as well as a *src/lib.rs* library crate root, and both crates will have - > the package name by default. Typically, packages with this pattern of - > containing both a library and a binary crate will have just enough code in the - > binary crate to start an executable that calls code within the library crate. - > This lets other projects benefit from most of the functionality that the - > package provides because the library crate’s code can be shared. - > - > The module tree should be defined in *src/lib.rs*. Then, any public items can - > be used in the binary crate by starting paths with the name of the package. - > The binary crate becomes a user of the library crate just like a completely - > external crate would use the library crate: it can only use the public API. - > This helps you design a good API; not only are you the author, you’re also a - > client! - > - > In Chapter 12, we’ll demonstrate this organizational - > practice with a command-line program that will contain both a binary crate - > and a library crate. - -### Starting Relative Paths with `super` +> #### Best Practices for Packages with a Binary and a Library +> +> We mentioned that a package can contain both a *src/main.rs* binary crate +> root as well as a *src/lib.rs* library crate root, and both crates will have +> the package name by default. Typically, packages with this pattern of +> containing both a library and a binary crate will have just enough code in the +> binary crate to start an executable that calls code within the library crate. +> This lets other projects benefit from most of the functionality that the +> package provides because the library crate’s code can be shared. +> +> The module tree should be defined in *src/lib.rs*. Then, any public items can +> be used in the binary crate by starting paths with the name of the package. +> The binary crate becomes a user of the library crate just like a completely +> external crate would use the library crate: it can only use the public API. +> This helps you design a good API; not only are you the author, you’re also a +> client! +> +> In Chapter 12, we’ll demonstrate this organizational +> practice with a command line program that will contain both a binary crate +> and a library crate. + +### Starting Relative Paths with super We can construct relative paths that begin in the parent module, rather than the current module or the crate root, by using `super` at the start of the @@ -629,7 +611,7 @@ function `fix_incorrect_order` defined in the `back_of_house` module calls the function `deliver_order` defined in the parent module by specifying the path to `deliver_order`, starting with `super`. -Filename: src/lib.rs +src/lib.rs ``` fn deliver_order() {} @@ -644,8 +626,7 @@ mod back_of_house { } ``` -Listing 7-8: Calling a function using a relative path -starting with `super` +Listing 7-8: Calling a function using a relative path starting with `super` The `fix_incorrect_order` function is in the `back_of_house` module, so we can use `super` to go to the parent module of `back_of_house`, which in this case @@ -669,7 +650,7 @@ comes with a meal, but the chef decides which fruit accompanies the meal based on what’s in season and in stock. The available fruit changes quickly, so customers can’t choose the fruit or even see which fruit they’ll get. -Filename: src/lib.rs +src/lib.rs ``` mod back_of_house { @@ -689,20 +670,19 @@ mod back_of_house { } pub fn eat_at_restaurant() { - // Order a breakfast in the summer with Rye toast + // Order a breakfast in the summer with Rye toast. let mut meal = back_of_house::Breakfast::summer("Rye"); - // Change our mind about what bread we'd like + // Change our mind about what bread we'd like. meal.toast = String::from("Wheat"); println!("I'd like {} toast please", meal.toast); // The next line won't compile if we uncomment it; we're not allowed - // to see or modify the seasonal fruit that comes with the meal + // to see or modify the seasonal fruit that comes with the meal. // meal.seasonal_fruit = String::from("blueberries"); } ``` -Listing 7-9: A struct with some public fields and some -private fields +Listing 7-9: A struct with some public fields and some private fields Because the `toast` field in the `back_of_house::Breakfast` struct is public, in `eat_at_restaurant` we can write and read to the `toast` field using dot @@ -720,7 +700,7 @@ have such a function, we couldn’t create an instance of `Breakfast` in In contrast, if we make an enum public, all of its variants are then public. We only need the `pub` before the `enum` keyword, as shown in Listing 7-10. -Filename: src/lib.rs +src/lib.rs ``` mod back_of_house { @@ -736,8 +716,7 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-10: Designating an enum as public makes all its -variants public +Listing 7-10: Designating an enum as public makes all its variants public. Because we made the `Appetizer` enum public, we can use the `Soup` and `Salad` variants in `eat_at_restaurant`. @@ -752,7 +731,7 @@ There’s one more situation involving `pub` that we haven’t covered, and that our last module system feature: the `use` keyword. We’ll cover `use` by itself first, and then we’ll show how to combine `pub` and `use`. -## Bringing Paths into Scope with the `use` Keyword +## Bringing Paths into Scope with the use Keyword Having to write out the paths to call functions can feel inconvenient and repetitive. In Listing 7-7, whether we chose the absolute or relative path to @@ -766,7 +745,7 @@ scope of the `eat_at_restaurant` function so we only have to specify `hosting::add_to_waitlist` to call the `add_to_waitlist` function in `eat_at_restaurant`. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -782,8 +761,7 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-11: Bringing a module into scope with -`use` +Listing 7-11: Bringing a module into scope with `use` Adding `use` and a path in a scope is similar to creating a symbolic link in the filesystem. By adding `use crate::front_of_house::hosting` in the crate @@ -796,7 +774,7 @@ Note that `use` only creates the shortcut for the particular scope in which the child module named `customer`, which is then a different scope than the `use` statement, so the function body won’t compile. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -814,8 +792,7 @@ mod customer { } ``` -Listing 7-12: A `use` statement only applies in the scope -it’s in +Listing 7-12: A `use` statement only applies in the scope it’s in. The compiler error shows that the shortcut no longer applies within the `customer` module: @@ -852,13 +829,13 @@ fix this problem, move the `use` within the `customer` module too, or reference the shortcut in the parent module with `super::hosting` within the child `customer` module. -### Creating Idiomatic `use` Paths +### Creating Idiomatic use Paths In Listing 7-11, you might have wondered why we specified `use crate::front_of_house::hosting` and then called `hosting::add_to_waitlist` in `eat_at_restaurant`, rather than specifying the `use` path all the way out to the `add_to_waitlist` function to achieve the same result, as in Listing 7-13. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -874,8 +851,7 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-13: Bringing the `add_to_waitlist` function -into scope with `use`, which is unidiomatic +Listing 7-13: Bringing the `add_to_waitlist` function into scope with `use`, which is unidiomatic Although both Listing 7-11 and Listing 7-13 accomplish the same task, Listing 7-11 is the idiomatic way to bring a function into scope with `use`. Bringing @@ -890,7 +866,7 @@ it’s idiomatic to specify the full path. Listing 7-14 shows the idiomatic way to bring the standard library’s `HashMap` struct into the scope of a binary crate. -Filename: src/main.rs +src/main.rs ``` use std::collections::HashMap; @@ -901,8 +877,7 @@ fn main() { } ``` -Listing 7-14: Bringing `HashMap` into scope in an -idiomatic way +Listing 7-14: Bringing `HashMap` into scope in an idiomatic way There’s no strong reason behind this idiom: it’s just the convention that has emerged, and folks have gotten used to reading and writing Rust code this way. @@ -912,7 +887,7 @@ into scope with `use` statements, because Rust doesn’t allow that. Listing 7-1 shows how to bring two `Result` types into scope that have the same name but different parent modules, and how to refer to them. -Filename: src/lib.rs +src/lib.rs ``` use std::fmt; @@ -927,22 +902,21 @@ fn function2() -> io::Result<()> { } ``` -Listing 7-15: Bringing two types with the same name into -the same scope requires using their parent modules. +Listing 7-15: Bringing two types with the same name into the same scope requires using their parent modules. As you can see, using the parent modules distinguishes the two `Result` types. If instead we specified `use std::fmt::Result` and `use std::io::Result`, we’d have two `Result` types in the same scope, and Rust wouldn’t know which one we meant when we used `Result`. -### Providing New Names with the `as` Keyword +### Providing New Names with the as Keyword There’s another solution to the problem of bringing two types of the same name into the same scope with `use`: after the path, we can specify `as` and a new local name, or *alias*, for the type. Listing 7-16 shows another way to write the code in Listing 7-15 by renaming one of the two `Result` types using `as`. -Filename: src/lib.rs +src/lib.rs ``` use std::fmt::Result; @@ -957,15 +931,14 @@ fn function2() -> IoResult<()> { } ``` -Listing 7-16: Renaming a type when it’s brought into -scope with the `as` keyword +Listing 7-16: Renaming a type when it’s brought into scope with the `as` keyword In the second `use` statement, we chose the new name `IoResult` for the `std::io::Result` type, which won’t conflict with the `Result` from `std::fmt` that we’ve also brought into scope. Listing 7-15 and Listing 7-16 are considered idiomatic, so the choice is up to you! -### Re-exporting Names with `pub use` +### Re-exporting Names with pub use When we bring a name into scope with the `use` keyword, the name available in the new scope is private. To enable the code that calls our code to refer to @@ -977,7 +950,7 @@ their scope. Listing 7-17 shows the code in Listing 7-11 with `use` in the root module changed to `pub use`. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house { @@ -993,8 +966,7 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-17: Making a name available for any code to use -from a new scope with `pub use` +Listing 7-17: Making a name available for any code to use from a new scope with `pub use` Before this change, external code would have to call the `add_to_waitlist` function by using the path @@ -1006,13 +978,11 @@ Re-exporting is useful when the internal structure of your code is different from how programmers calling your code would think about the domain. For example, in this restaurant metaphor, the people running the restaurant think about “front of house” and “back of house.” But customers visiting a restaurant -probably won’t think about the parts of the restaurant in those terms. With -`pub use`, we can write our code with one structure but expose a different -structure. Doing so makes our library well organized for programmers working on -the library and programmers calling the library. We’ll look at another example -of `pub use` and how it affects your crate’s documentation in the “Exporting a -Convenient Public API with `pub use`” section of -Chapter 14. +probably won’t think about the parts of the restaurant in those terms. With `pub use`, we can write our code with one structure but expose a different structure. +Doing so makes our library well organized for programmers working on the library +and programmers calling the library. We’ll look at another example of `pub use` +and how it affects your crate’s documentation in “Exporting a Convenient Public +API with `pub use`” in Chapter 14. ### Using External Packages @@ -1026,21 +996,23 @@ added this line to *Cargo.toml*: * ch14-03-cargo-workspaces.md --> -Filename: Cargo.toml +Cargo.toml ``` rand = "0.8.5" ``` + + Adding `rand` as a dependency in *Cargo.toml* tells Cargo to download the `rand` package and any dependencies from crates.io at *https://crates.io/* and make `rand` available to our project. Then, to bring `rand` definitions into the scope of our package, we added a -`use` line starting with the name of the crate, `rand`, and listed the items -we wanted to bring into scope. Recall that in the “Generating a Random -Number” section in Chapter 2, we brought the `Rng` trait -into scope and called the `rand::thread_rng` function: +`use` line starting with the name of the crate, `rand`, and listed the items we +wanted to bring into scope. Recall that in “Generating a Random +Number” in Chapter 2, we brought the `Rng` trait into +scope and called the `rand::thread_rng` function: ``` use rand::Rng; @@ -1068,14 +1040,14 @@ use std::collections::HashMap; This is an absolute path starting with `std`, the name of the standard library crate. -### Using Nested Paths to Clean Up Large `use` Lists +### Using Nested Paths to Clean Up Large use Lists If we’re using multiple items defined in the same crate or same module, listing each item on its own line can take up a lot of vertical space in our files. For example, these two `use` statements we had in the guessing game in Listing 2-4 bring items from `std` into scope: -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -1084,12 +1056,14 @@ use std::io; // --snip-- ``` + + Instead, we can use nested paths to bring the same items into scope in one line. We do this by specifying the common part of the path, followed by two colons, and then curly brackets around a list of the parts of the paths that differ, as shown in Listing 7-18. -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -1097,8 +1071,7 @@ use std::{cmp::Ordering, io}; // --snip-- ``` -Listing 7-18: Specifying a nested path to bring multiple -items with the same prefix into scope +Listing 7-18: Specifying a nested path to bring multiple items with the same prefix into scope In bigger programs, bringing many items into scope from the same crate or module using nested paths can reduce the number of separate `use` statements @@ -1109,28 +1082,26 @@ two `use` statements that share a subpath. For example, Listing 7-19 shows two `use` statements: one that brings `std::io` into scope and one that brings `std::io::Write` into scope. -Filename: src/lib.rs +src/lib.rs ``` use std::io; use std::io::Write; ``` -Listing 7-19: Two `use` statements where one is a subpath -of the other +Listing 7-19: Two `use` statements where one is a subpath of the other The common part of these two paths is `std::io`, and that’s the complete first path. To merge these two paths into one `use` statement, we can use `self` in the nested path, as shown in Listing 7-20. -Filename: src/lib.rs +src/lib.rs ``` use std::io::{self, Write}; ``` -Listing 7-20: Combining the paths in Listing 7-19 into -one `use` statement +Listing 7-20: Combining the paths in Listing 7-19 into one `use` statement This line brings `std::io` and `std::io::Write` into scope. @@ -1148,12 +1119,12 @@ the current scope. Be careful when using the glob operator! Glob can make it harder to tell what names are in scope and where a name used in your program was defined. -The glob operator is often used when testing to bring everything under test -into the `tests` module; we’ll talk about that in the “How to Write -Tests” section in Chapter 11. The glob operator -is also sometimes used as part of the prelude pattern: see the standard -library documentation -for more information on that pattern. +The glob operator is often used when testing to bring everything under test into +the `tests` module; we’ll talk about that in “How to Write +Tests” in Chapter 11. The glob operator is also +sometimes used as part of the prelude pattern: see the standard library +documentation for more +information on that pattern. ## Separating Modules into Different Files @@ -1173,7 +1144,7 @@ the `mod front_of_house;` declaration, so that *src/lib.rs* contains the code shown in Listing 7-21. Note that this won’t compile until we create the *src/front_of_house.rs* file in Listing 7-22. -Filename: src/lib.rs +src/lib.rs ``` mod front_of_house; @@ -1185,15 +1156,14 @@ pub fn eat_at_restaurant() { } ``` -Listing 7-21: Declaring the `front_of_house` module whose -body will be in *src/front_of_house.rs* +Listing 7-21: Declaring the `front_of_house` module whose body will be in *src/front_of_house.rs* Next, place the code that was in the curly brackets into a new file named *src/front_of_house.rs*, as shown in Listing 7-22. The compiler knows to look in this file because it came across the module declaration in the crate root with the name `front_of_house`. -Filename: src/front_of_house.rs +src/front_of_house.rs ``` pub mod hosting { @@ -1201,8 +1171,7 @@ pub mod hosting { } ``` -Listing 7-22: Definitions inside the `front_of_house` -module in *src/front_of_house.rs* +Listing 7-22: Definitions inside the `front_of_house` module in *src/front_of_house.rs* Note that you only need to load a file using a `mod` declaration *once* in your module tree. Once the compiler knows the file is part of the project (and knows @@ -1221,51 +1190,54 @@ named for its ancestors in the module tree, in this case *src/front_of_house*. To start moving `hosting`, we change *src/front_of_house.rs* to contain only the declaration of the `hosting` module: -Filename: src/front_of_house.rs +src/front_of_house.rs ``` pub mod hosting; ``` + + Then we create a *src/front_of_house* directory and a *hosting.rs* file to contain the definitions made in the `hosting` module: -Filename: src/front_of_house/hosting.rs +src/front_of_house/hosting.rs ``` pub fn add_to_waitlist() {} ``` + + If we instead put *hosting.rs* in the *src* directory, the compiler would expect the *hosting.rs* code to be in a `hosting` module declared in the crate root, and not declared as a child of the `front_of_house` module. The compiler’s rules for which files to check for which modules’ code mean the directories and files more closely match the module tree. - > - > ### Alternate File Paths - > - > So far we’ve covered the most idiomatic file paths the Rust compiler uses, - > but Rust also supports an older style of file path. For a module named - > `front_of_house` declared in the crate root, the compiler will look for the - > module’s code in: - > - > * *src/front_of_house.rs* (what we covered) - > * *src/front_of_house/mod.rs* (older style, still supported path) - > - > For a module named `hosting` that is a submodule of `front_of_house`, the - > compiler will look for the module’s code in: - > - > * *src/front_of_house/hosting.rs* (what we covered) - > * *src/front_of_house/hosting/mod.rs* (older style, still supported path) - > - > If you use both styles for the same module, you’ll get a compiler error. - > Using a mix of both styles for different modules in the same project is - > allowed, but might be confusing for people navigating your project. - > - > The main downside to the style that uses files named *mod.rs* is that your - > project can end up with many files named *mod.rs*, which can get confusing - > when you have them open in your editor at the same time. +> ### Alternate File Paths +> +> So far we’ve covered the most idiomatic file paths the Rust compiler uses, +> but Rust also supports an older style of file path. For a module named +> `front_of_house` declared in the crate root, the compiler will look for the +> module’s code in: +> +> * *src/front_of_house.rs* (what we covered) +> * *src/front_of_house/mod.rs* (older style, still supported path) +> +> For a module named `hosting` that is a submodule of `front_of_house`, the +> compiler will look for the module’s code in: +> +> * *src/front_of_house/hosting.rs* (what we covered) +> * *src/front_of_house/hosting/mod.rs* (older style, still supported path) +> +> If you use both styles for the same module, you’ll get a compiler error. +> Using a mix of both styles for different modules in the same project is +> allowed, but might be confusing for people navigating your project. +> +> The main downside to the style that uses files named *mod.rs* is that your +> project can end up with many files named *mod.rs*, which can get confusing +> when you have them open in your editor at the same time. We’ve moved each module’s code to a separate file, and the module tree remains the same. The function calls in `eat_at_restaurant` will work without any diff --git a/nostarch/chapter08.md b/nostarch/chapter08.md index fd1cd93085..77b8da5658 100644 --- a/nostarch/chapter08.md +++ b/nostarch/chapter08.md @@ -11,12 +11,12 @@ directory, so all fixes need to be made in `/src/`. Rust’s standard library includes a number of very useful data structures called *collections*. Most other data types represent one specific value, but collections can contain multiple values. Unlike the built-in array and tuple -types, the data these collections point to is stored on the heap, which means -the amount of data does not need to be known at compile time and can grow or -shrink as the program runs. Each kind of collection has different capabilities -and costs, and choosing an appropriate one for your current situation is a -skill you’ll develop over time. In this chapter, we’ll discuss three -collections that are used very often in Rust programs: +types, the data that these collections point to is stored on the heap, which +means the amount of data does not need to be known at compile time and can grow +or shrink as the program runs. Each kind of collection has different +capabilities and costs, and choosing an appropriate one for your current +situation is a skill you’ll develop over time. In this chapter, we’ll discuss +three collections that are used very often in Rust programs: * A *vector* allows you to store a variable number of values next to each other. * A *string* is a collection of characters. We’ve mentioned the `String` type @@ -43,12 +43,12 @@ lines of text in a file or the prices of items in a shopping cart. To create a new empty vector, we call the `Vec::new` function, as shown in Listing 8-1. + ``` let v: Vec = Vec::new(); ``` -Listing 8-1: Creating a new, empty vector to hold values -of type `i32` +Listing 8-1: Creating a new, empty vector to hold values of type `i32` Note that we added a type annotation here. Because we aren’t inserting any values into this vector, Rust doesn’t know what kind of elements we intend to @@ -67,12 +67,12 @@ new vector that holds the values you give it. Listing 8-2 creates a new because that’s the default integer type, as we discussed in the “Data Types” section of Chapter 3. + ``` let v = vec![1, 2, 3]; ``` -Listing 8-2: Creating a new vector containing -values +Listing 8-2: Creating a new vector containing values Because we’ve given initial `i32` values, Rust can infer that the type of `v` is `Vec`, and the type annotation isn’t necessary. Next, we’ll look at how @@ -83,6 +83,7 @@ to modify a vector. To create a vector and then add elements to it, we can use the `push` method, as shown in Listing 8-3. + ``` let mut v = Vec::new(); @@ -92,8 +93,7 @@ as shown in Listing 8-3. v.push(8); ``` -Listing 8-3: Using the `push` method to add values to a -vector +Listing 8-3: Using the `push` method to add values to a vector As with any variable, if we want to be able to change its value, we need to make it mutable using the `mut` keyword, as discussed in Chapter 3. The numbers @@ -109,6 +109,7 @@ the values that are returned from these functions for extra clarity. Listing 8-4 shows both methods of accessing a value in a vector, with indexing syntax and the `get` method. + ``` let v = vec![1, 2, 3, 4, 5]; @@ -122,8 +123,7 @@ syntax and the `get` method. } ``` -Listing 8-4: Using indexing syntax and using the `get` -method to access an item in a vector +Listing 8-4: Using indexing syntax and using the `get` method to access an item in a vector Note a few details here. We use the index value of `2` to get the third element because vectors are indexed by number, starting at zero. Using `&` and `[]` @@ -137,6 +137,7 @@ existing elements. As an example, let’s see what happens when we have a vector of five elements and then we try to access an element at index 100 with each technique, as shown in Listing 8-5. + ``` let v = vec![1, 2, 3, 4, 5]; @@ -144,8 +145,7 @@ technique, as shown in Listing 8-5. let does_not_exist = v.get(100); ``` -Listing 8-5: Attempting to access the element at index -100 in a vector containing five elements +Listing 8-5: Attempting to access the element at index 100 in a vector containing five elements When we run this code, the first `[]` method will cause the program to panic because it references a nonexistent element. This method is best used when you @@ -172,6 +172,7 @@ to the first element in a vector and try to add an element to the end. This program won’t work if we also try to refer to that element later in the function. + ``` let mut v = vec![1, 2, 3, 4, 5]; @@ -182,8 +183,7 @@ function. println!("The first element is: {first}"); ``` -Listing 8-6: Attempting to add an element to a vector -while holding a reference to an item +Listing 8-6: Attempting to add an element to a vector while holding a reference to an item Compiling this code will result in this error: @@ -226,6 +226,7 @@ elements rather than use indices to access one at a time. Listing 8-7 shows how to use a `for` loop to get immutable references to each element in a vector of `i32` values and print them. + ``` let v = vec![100, 32, 57]; for i in &v { @@ -233,13 +234,13 @@ to use a `for` loop to get immutable references to each element in a vector of } ``` -Listing 8-7: Printing each element in a vector by -iterating over the elements using a `for` loop +Listing 8-7: Printing each element in a vector by iterating over the elements using a `for` loop We can also iterate over mutable references to each element in a mutable vector in order to make changes to all the elements. The `for` loop in Listing 8-8 will add `50` to each element. + ``` let mut v = vec![100, 32, 57]; for i in &mut v { @@ -247,14 +248,12 @@ will add `50` to each element. } ``` -Listing 8-8: Iterating over mutable references to -elements in a vector +Listing 8-8: Iterating over mutable references to elements in a vector To change the value that the mutable reference refers to, we have to use the `*` dereference operator to get to the value in `i` before we can use the `+=` operator. We’ll talk more about the dereference operator in the “Following the -Pointer to the Value with the Dereference Operator” -section of Chapter 15. +Pointer to the Value” section of Chapter 15. Iterating over a vector, whether immutably or mutably, is safe because of the borrow checker’s rules. If we attempted to insert or remove items in the `for` @@ -278,6 +277,7 @@ value types, and all the enum variants will be considered the same type: that of the enum. Then we can create a vector to hold that enum and so, ultimately, hold different types. We’ve demonstrated this in Listing 8-9. + ``` enum SpreadsheetCell { Int(i32), @@ -292,8 +292,7 @@ hold different types. We’ve demonstrated this in Listing 8-9. ]; ``` -Listing 8-9: Defining an `enum` to store values of -different types in one vector +Listing 8-9: Defining an `enum` to store values of different types in one vector Rust needs to know what types will be in the vector at compile time so it knows exactly how much memory on the heap will be needed to store each element. We @@ -305,7 +304,7 @@ at compile time that every possible case is handled, as discussed in Chapter 6. If you don’t know the exhaustive set of types a program will get at runtime to store in a vector, the enum technique won’t work. Instead, you can use a trait -object, which we’ll cover in Chapter 17. +object, which we’ll cover in Chapter 18. Now that we’ve discussed some of the most common ways to use vectors, be sure to review the API documentation for all of the many @@ -317,6 +316,7 @@ addition to `push`, a `pop` method removes and returns the last element. Like any other `struct`, a vector is freed when it goes out of scope, as annotated in Listing 8-10. + ``` { let v = vec![1, 2, 3, 4]; @@ -325,8 +325,7 @@ annotated in Listing 8-10. } // <- v goes out of scope and is freed here ``` -Listing 8-10: Showing where the vector and its elements -are dropped +Listing 8-10: Showing where the vector and its elements are dropped When the vector gets dropped, all of its contents are also dropped, meaning the integers it holds will be cleaned up. The borrow checker ensures that any @@ -378,6 +377,7 @@ of bytes with some extra guarantees, restrictions, and capabilities. An example of a function that works the same way with `Vec` and `String` is the `new` function to create an instance, shown in Listing 8-11. + ``` let mut s = String::new(); ``` @@ -390,17 +390,17 @@ string. For that, we use the `to_string` method, which is available on any type that implements the `Display` trait, as string literals do. Listing 8-12 shows two examples. + ``` let data = "initial contents"; let s = data.to_string(); - // the method also works on a literal directly: + // The method also works on a literal directly: let s = "initial contents".to_string(); ``` -Listing 8-12: Using the `to_string` method to create a -`String` from a string literal +Listing 8-12: Using the `to_string` method to create a `String` from a string literal This code creates a string containing `initial contents`. @@ -408,12 +408,12 @@ We can also use the function `String::from` to create a `String` from a string literal. The code in Listing 8-13 is equivalent to the code in Listing 8-12 that uses `to_string`. + ``` let s = String::from("initial contents"); ``` -Listing 8-13: Using the `String::from` function to create -a `String` from a string literal +Listing 8-13: Using the `String::from` function to create a `String` from a string literal Because strings are used for so many things, we can use many different generic APIs for strings, providing us with a lot of options. Some of them can seem @@ -424,6 +424,7 @@ readability. Remember that strings are UTF-8 encoded, so we can include any properly encoded data in them, as shown in Listing 8-14. + ``` let hello = String::from("السلام عليكم"); let hello = String::from("Dobrý den"); @@ -438,8 +439,7 @@ data in them, as shown in Listing 8-14. let hello = String::from("Hola"); ``` -Listing 8-14: Storing greetings in different languages in -strings +Listing 8-14: Storing greetings in different languages in strings All of these are valid `String` values. @@ -449,24 +449,25 @@ A `String` can grow in size and its contents can change, just like the contents of a `Vec`, if you push more data into it. In addition, you can conveniently use the `+` operator or the `format!` macro to concatenate `String` values. -#### Appending to a String with `push_str` and `push` +#### Appending to a String with push_str and push We can grow a `String` by using the `push_str` method to append a string slice, as shown in Listing 8-15. + ``` let mut s = String::from("foo"); s.push_str("bar"); ``` -Listing 8-15: Appending a string slice to a `String` -using the `push_str` method +Listing 8-15: Appending a string slice to a `String` using the `push_str` method After these two lines, `s` will contain `foobar`. The `push_str` method takes a string slice because we don’t necessarily want to take ownership of the parameter. For example, in the code in Listing 8-16, we want to be able to use `s2` after appending its contents to `s1`. + ``` let mut s1 = String::from("foo"); let s2 = "bar"; @@ -474,8 +475,7 @@ parameter. For example, in the code in Listing 8-16, we want to be able to use println!("s2 is {s2}"); ``` -Listing 8-16: Using a string slice after appending its -contents to a `String` +Listing 8-16: Using a string slice after appending its contents to a `String` If the `push_str` method took ownership of `s2`, we wouldn’t be able to print its value on the last line. However, this code works as we’d expect! @@ -484,29 +484,29 @@ The `push` method takes a single character as a parameter and adds it to the `String`. Listing 8-17 adds the letter *l* to a `String` using the `push` method. + ``` let mut s = String::from("lo"); s.push('l'); ``` -Listing 8-17: Adding one character to a `String` value -using `push` +Listing 8-17: Adding one character to a `String` value using `push` As a result, `s` will contain `lol`. -#### Concatenation with the `+` Operator or the `format!` Macro +#### Concatenation with the + Operator or the format! Macro Often, you’ll want to combine two existing strings. One way to do so is to use the `+` operator, as shown in Listing 8-18. + ``` let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used ``` -Listing 8-18: Using the `+` operator to combine two -`String` values into a new `String` value +Listing 8-18: Using the `+` operator to combine two `String` values into a new `String` value The string `s3` will contain `Hello, world!`. The reason `s1` is no longer valid after the addition, and the reason we used a reference to `s2`, has to do @@ -582,33 +582,31 @@ string by referencing them by index is a valid and common operation. However, if you try to access parts of a `String` using indexing syntax in Rust, you’ll get an error. Consider the invalid code in Listing 8-19. + ``` let s1 = String::from("hello"); let h = s1[0]; ``` -Listing 8-19: Attempting to use indexing syntax with a -String +Listing 8-19: Attempting to use indexing syntax with a String This code will result in the following error: ``` $ cargo run Compiling collections v0.1.0 (file:///projects/collections) -error[E0277]: the type `String` cannot be indexed by `{integer}` +error[E0277]: the type `str` cannot be indexed by `{integer}` --> src/main.rs:3:16 | 3 | let h = s1[0]; - | ^ `String` cannot be indexed by `{integer}` + | ^ string indices are ranges of `usize` | - = help: the trait `Index<{integer}>` is not implemented for `String` - = help: the following other types implement trait `Index`: - > - >> - >> - >> - >> - >> + = help: the trait `SliceIndex` is not implemented for `{integer}`, which is required by `String: Index<_>` + = note: you can use `.chars().nth()` or `.bytes().nth()` + for more information, see chapter 8 in The Book: + = help: the trait `SliceIndex<[_]>` is implemented for `usize` + = help: for that trait implementation, expected `[_]`, found `str` + = note: required for `String` to implement `Index<{integer}>` For more information about this error, try `rustc --explain E0277`. error: could not compile `collections` (bin "collections") due to 1 previous error @@ -654,8 +652,8 @@ seem that `answer` should in fact be `208`, but `208` is not a valid character on its own. Returning `208` is likely not what a user would want if they asked for the first letter of this string; however, that’s the only data that Rust has at byte index 0. Users generally don’t want the byte value returned, even -if the string contains only Latin letters: if `&"hello"[0]` were valid code -that returned the byte value, it would return `104`, not `h`. +if the string contains only Latin letters: if `&"hi"[0]` were valid code that +returned the byte value, it would return `104`, not `h`. The answer, then, is that to avoid returning an unexpected value and causing bugs that might not be discovered immediately, Rust doesn’t compile this code @@ -729,7 +727,7 @@ index were accessed in a vector: ``` $ cargo run Compiling collections v0.1.0 (file:///projects/collections) - Finished dev [unoptimized + debuginfo] target(s) in 0.43s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/collections` thread 'main' panicked at src/main.rs:4:19: byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте` @@ -830,6 +828,7 @@ One way to create an empty hash map is to use `new` and to add elements with names are *Blue* and *Yellow*. The Blue team starts with 10 points, and the Yellow team starts with 50. + ``` use std::collections::HashMap; @@ -839,8 +838,7 @@ Yellow team starts with 50. scores.insert(String::from("Yellow"), 50); ``` -Listing 8-20: Creating a new hash map and inserting some -keys and values +Listing 8-20: Creating a new hash map and inserting some keys and values Note that we need to first `use` the `HashMap` from the collections portion of the standard library. Of our three common collections, this one is the least @@ -858,6 +856,7 @@ must have the same type. We can get a value out of the hash map by providing its key to the `get` method, as shown in Listing 8-21. + ``` use std::collections::HashMap; @@ -870,8 +869,7 @@ method, as shown in Listing 8-21. let score = scores.get(&team_name).copied().unwrap_or(0); ``` -Listing 8-21: Accessing the score for the Blue team -stored in the hash map +Listing 8-21: Accessing the score for the Blue team stored in the hash map Here, `score` will have the value that’s associated with the Blue team, and the result will be `10`. The `get` method returns an `Option<&V>`; if there’s no @@ -880,7 +878,7 @@ handles the `Option` by calling `copied` to get an `Option` rather than an `Option<&i32>`, then `unwrap_or` to set `score` to zero if `scores` doesn’t have an entry for the key. -We can iterate over each key–value pair in a hash map in a similar manner as we +We can iterate over each key-value pair in a hash map in a similar manner as we do with vectors, using a `for` loop: ``` @@ -909,6 +907,7 @@ For types that implement the `Copy` trait, like `i32`, the values are copied into the hash map. For owned values like `String`, the values will be moved and the hash map will be the owner of those values, as demonstrated in Listing 8-22. + ``` use std::collections::HashMap; @@ -921,8 +920,7 @@ the hash map will be the owner of those values, as demonstrated in Listing 8-22. // see what compiler error you get! ``` -Listing 8-22: Showing that keys and values are owned by -the hash map once they’re inserted +Listing 8-22: Showing that keys and values are owned by the hash map once they’re inserted We aren’t able to use the variables `field_name` and `field_value` after they’ve been moved into the hash map with the call to `insert`. @@ -930,9 +928,8 @@ they’ve been moved into the hash map with the call to `insert`. If we insert references to values into the hash map, the values won’t be moved into the hash map. The values that the references point to must be valid for at least as long as the hash map is valid. We’ll talk more about these issues in -the “Validating References with -Lifetimes” section in -Chapter 10. +“Validating References with +Lifetimes” in Chapter 10. ### Updating a Hash Map @@ -953,9 +950,10 @@ new value. Let’s look at how to do each of these! If we insert a key and a value into a hash map and then insert that same key with a different value, the value associated with that key will be replaced. Even though the code in Listing 8-23 calls `insert` twice, the hash map will -only contain one key–value pair because we’re inserting the value for the Blue +only contain one key-value pair because we’re inserting the value for the Blue team’s key both times. + ``` use std::collections::HashMap; @@ -967,13 +965,13 @@ team’s key both times. println!("{scores:?}"); ``` -Listing 8-23: Replacing a value stored with a particular -key +Listing 8-23: Replacing a value stored with a particular key This code will print `{"Blue": 25}`. The original value of `10` has been overwritten. + #### Adding a Key and Value Only If a Key Isn’t Present @@ -990,6 +988,7 @@ we want to check whether the key for the Yellow team has a value associated with it. If it doesn’t, we want to insert the value `50`, and the same for the Blue team. Using the `entry` API, the code looks like Listing 8-24. + ``` use std::collections::HashMap; @@ -1002,8 +1001,7 @@ Blue team. Using the `entry` API, the code looks like Listing 8-24. println!("{scores:?}"); ``` -Listing 8-24: Using the `entry` method to only insert if -the key does not already have a value +Listing 8-24: Using the `entry` method to only insert if the key does not already have a value The `or_insert` method on `Entry` is defined to return a mutable reference to the value for the corresponding `Entry` key if that key exists, and if not, it @@ -1026,6 +1024,7 @@ the words as keys and increment the value to keep track of how many times we’v seen that word. If it’s the first time we’ve seen a word, we’ll first insert the value `0`. + ``` use std::collections::HashMap; @@ -1041,13 +1040,12 @@ the value `0`. println!("{map:?}"); ``` -Listing 8-25: Counting occurrences of words using a hash -map that stores words and counts +Listing 8-25: Counting occurrences of words using a hash map that stores words and counts This code will print `{"world": 2, "hello": 1, "wonderful": 1}`. You might see -the same key–value pairs printed in a different order: recall from the -“Accessing Values in a Hash Map” section that -iterating over a hash map happens in an arbitrary order. +the same key-value pairs printed in a different order: recall from “Accessing +Values in a Hash Map” that iterating over a hash map +happens in an arbitrary order. The `split_whitespace` method returns an iterator over subslices, separated by whitespace, of the value in `text`. The `or_insert` method returns a mutable @@ -1061,7 +1059,7 @@ changes are safe and allowed by the borrowing rules. By default, `HashMap` uses a hashing function called *SipHash* that can provide resistance to denial-of-service (DoS) attacks involving hash -tables^siphash at *[https://en.wikipedia.org/wiki/SipHash](https://en.wikipedia.org/wiki/SipHash)*. This is not the fastest hashing algorithm +tables[^siphash]. This is not the fastest hashing algorithm available, but the trade-off for better security that comes with the drop in performance is worth it. If you profile your code and find that the default hash function is too slow for your purposes, you can switch to another function diff --git a/nostarch/chapter09.md b/nostarch/chapter09.md index a7188076c7..56965a7247 100644 --- a/nostarch/chapter09.md +++ b/nostarch/chapter09.md @@ -12,8 +12,8 @@ Errors are a fact of life in software, so Rust has a number of features for handling situations in which something goes wrong. In many cases, Rust requires you to acknowledge the possibility of an error and take some action before your code will compile. This requirement makes your program more robust by ensuring -that you’ll discover errors and handle them appropriately before you’ve -deployed your code to production! +that you’ll discover errors and handle them appropriately before deploying your +code to production! Rust groups errors into two major categories: *recoverable* and *unrecoverable* errors. For a recoverable error, such as a *file not found* error, we most @@ -31,7 +31,7 @@ about returning `Result` values. Additionally, we’ll explore considerations when deciding whether to try to recover from an error or to stop execution. -## Unrecoverable Errors with `panic!` +## Unrecoverable Errors with panic! Sometimes bad things happen in your code, and there’s nothing you can do about it. In these cases, Rust has the `panic!` macro. There are two ways to cause a @@ -43,28 +43,28 @@ environment variable, you can also have Rust display the call stack when a panic occurs to make it easier to track down the source of the panic. > ### Unwinding the Stack or Aborting in Response to a Panic -> +> > By default, when a panic occurs the program starts *unwinding*, which means > Rust walks back up the stack and cleans up the data from each function it > encounters. However, walking back and cleaning up is a lot of work. Rust, > therefore, allows you to choose the alternative of immediately *aborting*, > which ends the program without cleaning up. -> +> > Memory that the program was using will then need to be cleaned up by the > operating system. If in your project you need to make the resultant binary as > small as possible, you can switch from unwinding to aborting upon a panic by > adding `panic = 'abort'` to the appropriate `[profile]` sections in your > *Cargo.toml* file. For example, if you want to abort on panic in release mode, > add this: -> -> ```toml -> profile.release +> +> ````toml +> [profile.release] > panic = 'abort' -> ``` +> ```` Let’s try calling `panic!` in a simple program: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -72,12 +72,14 @@ fn main() { } ``` + + When you run the program, you’ll see something like this: ``` $ cargo run Compiling panic v0.1.0 (file:///projects/panic) - Finished dev [unoptimized + debuginfo] target(s) in 0.25s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/panic` thread 'main' panicked at src/main.rs:2:5: crash and burn @@ -96,6 +98,7 @@ the error message will be someone else’s code where the `panic!` macro is called, not the line of our code that eventually led to the `panic!` call. + We can use the backtrace of the functions the `panic!` call came from to figure @@ -105,7 +108,7 @@ a `panic!` call comes from a library because of a bug in our code instead of from our code calling the macro directly. Listing 9-1 has some code that attempts to access an index in a vector beyond the range of valid indexes. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -115,8 +118,7 @@ fn main() { } ``` -Listing 9-1: Attempting to access an element beyond the -end of a vector, which will cause a call to `panic!` +Listing 9-1: Attempting to access an element beyond the end of a vector, which will cause a call to `panic!` Here, we’re attempting to access the 100th element of our vector (which is at index 99 because indexing starts at zero), but the vector has only three @@ -139,7 +141,7 @@ continue. Let’s try it and see: ``` $ cargo run Compiling panic v0.1.0 (file:///projects/panic) - Finished dev [unoptimized + debuginfo] target(s) in 0.27s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/panic` thread 'main' panicked at src/main.rs:4:6: index out of bounds: the len is 3 but the index is 99 @@ -168,32 +170,32 @@ copy the backtrace output below check the backtrace number mentioned in the text below the listing --> + ``` $ RUST_BACKTRACE=1 cargo run thread 'main' panicked at src/main.rs:4:6: index out of bounds: the len is 3 but the index is 99 stack backtrace: 0: rust_begin_unwind - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/std/src/panicking.rs:662:5 1: core::panicking::panic_fmt - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/core/src/panicking.rs:74:14 2: core::panicking::panic_bounds_check - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:208:5 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/core/src/panicking.rs:276:5 3: >::index - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:255:10 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/core/src/slice/index.rs:302:10 4: core::slice::index:: for [T]>::index - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:18:9 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/core/src/slice/index.rs:16:9 5: as core::ops::index::Index>::index - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/alloc/src/vec/mod.rs:2770:9 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/alloc/src/vec/mod.rs:2920:9 6: panic::main at ./src/main.rs:4:6 7: core::ops::function::FnOnce::call_once - at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5 + at /rustc/f6e511eec7342f59a25f7c0534f1dbea00d01b14/library/core/src/ops/function.rs:250:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. ``` -Listing 9-2: The backtrace generated by a call to -`panic!` displayed when the environment variable `RUST_BACKTRACE` is set +Listing 9-2: The backtrace generated by a call to `panic!` displayed when the environment variable `RUST_BACKTRACE` is set That’s a lot of output! The exact output you see might be different depending on your operating system and Rust version. In order to get backtraces with this @@ -215,7 +217,7 @@ handle error conditions in the “To `panic!` or Not to `panic!`” section later in this chapter. Next, we’ll look at how to recover from an error using `Result`. -## Recoverable Errors with `Result` +## Recoverable Errors with Result Most errors aren’t serious enough to require the program to stop entirely. Sometimes when a function fails it’s for a reason that you can easily interpret @@ -245,7 +247,7 @@ return may differ. Let’s call a function that returns a `Result` value because the function could fail. In Listing 9-3 we try to open a file. -Filename: src/main.rs +src/main.rs ``` use std::fs::File; @@ -279,7 +281,7 @@ on the value `File::open` returns. Listing 9-4 shows one way to handle the `Result` using a basic tool, the `match` expression that we discussed in Chapter 6. -Filename: src/main.rs +src/main.rs ``` use std::fs::File; @@ -294,8 +296,7 @@ fn main() { } ``` -Listing 9-4: Using a `match` expression to handle the -`Result` variants that might be returned +Listing 9-4: Using a `match` expression to handle the `Result` variants that might be returned Note that, like the `Option` enum, the `Result` enum and its variants have been brought into scope by the prelude, so we don’t need to specify `Result::` @@ -314,7 +315,7 @@ code, we’ll see the following output from the `panic!` macro: ``` $ cargo run Compiling error-handling v0.1.0 (file:///projects/error-handling) - Finished dev [unoptimized + debuginfo] target(s) in 0.73s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/error-handling` thread 'main' panicked at src/main.rs:8:23: Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" } @@ -333,7 +334,7 @@ reason—for example, because we didn’t have permission to open the file—we want the code to `panic!` in the same way it did in Listing 9-4. For this, we add an inner `match` expression, shown in Listing 9-5. -Filename: src/main.rs +src/main.rs @@ -360,8 +361,7 @@ fn main() { } ``` -Listing 9-5: Handling different kinds of errors in -different ways +Listing 9-5: Handling different kinds of errors in different ways The type of the value that `File::open` returns inside the `Err` variant is `io::Error`, which is a struct provided by the standard library. This struct @@ -380,22 +380,22 @@ file can’t be created, a different error message is printed. The second arm of the outer `match` stays the same, so the program panics on any error besides the missing file error. -> #### Alternatives to Using `match` with `Result` -> +> #### Alternatives to Using match with Result +> > That’s a lot of `match`! The `match` expression is very useful but also very > much a primitive. In Chapter 13, you’ll learn about closures, which are used > with many of the methods defined on `Result`. These methods can be more > concise than using `match` when handling `Result` values in your code. -> +> > For example, here’s another way to write the same logic as shown in Listing > 9-5, this time using closures and the `unwrap_or_else` method: -> +> > -> -> ```rust,ignore +> +> ````rust,ignore > use std::fs::File; > use std::io::ErrorKind; -> +> > fn main() { > let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { > if error.kind() == ErrorKind::NotFound { @@ -407,15 +407,15 @@ the missing file error. > } > }); > } -> ``` -> +> ```` +> > Although this code has the same behavior as Listing 9-5, it doesn’t contain > any `match` expressions and is cleaner to read. Come back to this example > after you’ve read Chapter 13, and look up the `unwrap_or_else` method in the > standard library documentation. Many more of these methods can clean up huge > nested `match` expressions when you’re dealing with errors. -#### Shortcuts for Panic on Error: `unwrap` and `expect` +#### Shortcuts for Panic on Error: unwrap and expect Using `match` works well enough, but it can be a bit verbose and doesn’t always communicate intent well. The `Result` type has many helper methods @@ -425,7 +425,7 @@ Listing 9-4. If the `Result` value is the `Ok` variant, `unwrap` will return the value inside the `Ok`. If the `Result` is the `Err` variant, `unwrap` will call the `panic!` macro for us. Here is an example of `unwrap` in action: -Filename: src/main.rs +src/main.rs ``` use std::fs::File; @@ -435,6 +435,8 @@ fn main() { } ``` + + If we run this code without a *hello.txt* file, we’ll see an error message from the `panic!` call that the `unwrap` method makes: @@ -454,7 +456,7 @@ Using `expect` instead of `unwrap` and providing good error messages can convey your intent and make tracking down the source of a panic easier. The syntax of `expect` looks like this: -Filename: src/main.rs +src/main.rs ``` use std::fs::File; @@ -465,6 +467,8 @@ fn main() { } ``` + + We use `expect` in the same way as `unwrap`: to return the file handle or call the `panic!` macro. The error message used by `expect` in its call to `panic!` will be the parameter that we pass to `expect`, rather than the default @@ -499,7 +503,7 @@ For example, Listing 9-6 shows a function that reads a username from a file. If the file doesn’t exist or can’t be read, this function will return those errors to the code that called the function. -Filename: src/main.rs +src/main.rs + #### Trait Bound Syntax @@ -984,7 +971,7 @@ The generic type `T` specified as the type of the `item1` and `item2` parameters constrains the function such that the concrete type of the value passed as an argument for `item1` and `item2` must be the same. -#### Specifying Multiple Trait Bounds with the `+` Syntax +#### Specifying Multiple Trait Bounds with the + Syntax We can also specify more than one trait bound. Say we wanted `notify` to use display formatting as well as `summarize` on `item`: we specify in the `notify` @@ -1004,7 +991,7 @@ pub fn notify(item: &T) { With the two trait bounds specified, the body of `notify` can call `summarize` and use `{}` to format `item`. -#### Clearer Trait Bounds with `where` Clauses +#### Clearer Trait Bounds with where Clauses Using too many trait bounds has its downsides. Each generic has its own trait bounds, so functions with multiple generic type parameters can contain lots of @@ -1096,7 +1083,7 @@ Returning either a `NewsArticle` or a `Tweet` isn’t allowed due to restriction around how the `impl Trait` syntax is implemented in the compiler. We’ll cover how to write a function with this behavior in the “Using Trait Objects That Allow for Values of Different -Types” section of Chapter 17. +Types” section of Chapter 18. ### Using Trait Bounds to Conditionally Implement Methods @@ -1110,7 +1097,7 @@ is a type alias for the type of the `impl` block, which in this case is `cmp_display` method if its inner type `T` implements the `PartialOrd` trait that enables comparison *and* the `Display` trait that enables printing. -Filename: src/lib.rs +src/lib.rs ``` use std::fmt::Display; @@ -1137,8 +1124,7 @@ impl Pair { } ``` -Listing 10-15: Conditionally implementing methods on a -generic type depending on trait bounds +Listing 10-15: Conditionally implementing methods on a generic type depending on trait bounds We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait @@ -1193,9 +1179,9 @@ when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid. -Annotating lifetimes is not a concept most other programming languages have, so -this is going to feel unfamiliar. Although we won’t cover lifetimes in their -entirety in this chapter, we’ll discuss common ways you might encounter +Annotating lifetimes is not even a concept most other programming languages +have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in +their entirety in this chapter, we’ll discuss common ways you might encounter lifetime syntax so you can get comfortable with the concept. ### Preventing Dangling References with Lifetimes @@ -1205,6 +1191,7 @@ program to reference data other than the data it’s intended to reference. Consider the program in Listing 10-16, which has an outer scope and an inner scope. + ``` fn main() { let r; @@ -1218,10 +1205,9 @@ fn main() { } ``` -Listing 10-16: An attempt to use a reference whose value -has gone out of scope +Listing 10-16: An attempt to use a reference whose value has gone out of scope -> Note: The examples in Listing 10-16, 10-17, and 10-23 declare variables +> Note: The examples in Listings 10-16, 10-17, and 10-23 declare variables > without giving them an initial value, so the variable name exists in the outer > scope. At first glance, this might appear to be in conflict with Rust’s having > no null values. However, if we try to use a variable before giving it a value, @@ -1248,8 +1234,8 @@ error[E0597]: `x` does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | -9 | println!("r: {}", r); - | - borrow later used here +9 | println!("r: {r}"); + | --- borrow later used here For more information about this error, try `rustc --explain E0597`. error: could not compile `chapter10` (bin "chapter10") due to 1 previous error @@ -1269,6 +1255,7 @@ The Rust compiler has a *borrow checker* that compares scopes to determine whether all borrows are valid. Listing 10-17 shows the same code as Listing 10-16 but with annotations showing the lifetimes of the variables. + ``` fn main() { let r; // ---------+-- 'a @@ -1282,8 +1269,7 @@ fn main() { } // ---------+ ``` -Listing 10-17: Annotations of the lifetimes of `r` and -`x`, named `'a` and `'b`, respectively +Listing 10-17: Annotations of the lifetimes of `r` and `x`, named `'a` and `'b`, respectively Here, we’ve annotated the lifetime of `r` with `'a` and the lifetime of `x` with `'b`. As you can see, the inner `'b` block is much smaller than the outer @@ -1295,6 +1281,7 @@ with a lifetime of `'b`. The program is rejected because `'b` is shorter than Listing 10-18 fixes the code so it doesn’t have a dangling reference and it compiles without any errors. + ``` fn main() { let x = 5; // ----------+-- 'b @@ -1306,14 +1293,13 @@ fn main() { } // ----------+ ``` -Listing 10-18: A valid reference because the data has a -longer lifetime than the reference +Listing 10-18: A valid reference because the data has a longer lifetime than the reference Here, `x` has the lifetime `'b`, which in this case is larger than `'a`. This means `r` can reference `x` because Rust knows that the reference in `r` will always be valid while `x` is valid. -Now that you know what the lifetimes of references are and how Rust analyzes +Now that you know where the lifetimes of references are and how Rust analyzes lifetimes to ensure references will always be valid, let’s explore generic lifetimes of parameters and return values in the context of functions. @@ -1324,7 +1310,7 @@ function will take two string slices and return a single string slice. After we’ve implemented the `longest` function, the code in Listing 10-19 should print `The longest string is abcd`. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1336,20 +1322,19 @@ fn main() { } ``` -Listing 10-19: A `main` function that calls the `longest` -function to find the longer of two string slices +Listing 10-19: A `main` function that calls the `longest` function to find the longer of two string slices Note that we want the function to take string slices, which are references, rather than strings, because we don’t want the `longest` function to take -ownership of its parameters. Refer to the “String Slices as -Parameters” section in Chapter 4 -for more discussion about why the parameters we use in Listing 10-19 are the -ones we want. +ownership of its parameters. Refer to “String Slices as +Parameters” in Chapter 4 for more +discussion about why the parameters we use in Listing 10-19 are the ones we +want. If we try to implement the `longest` function as shown in Listing 10-20, it won’t compile. -Filename: src/main.rs +src/main.rs ``` fn longest(x: &str, y: &str) -> &str { @@ -1361,9 +1346,7 @@ fn longest(x: &str, y: &str) -> &str { } ``` -Listing 10-20: An implementation of the `longest` -function that returns the longer of two string slices but does not yet -compile +Listing 10-20: An implementation of the `longest` function that returns the longer of two string slices but does not yet compile Instead, we get the following error that talks about lifetimes: @@ -1444,7 +1427,7 @@ relationship between lifetimes of the parameters and the return value. We’ll name the lifetime `'a` and then add it to each reference, as shown in Listing 10-21. -Filename: src/main.rs +src/main.rs ``` fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { @@ -1456,9 +1439,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { } ``` -Listing 10-21: The `longest` function definition -specifying that all the references in the signature must have the same lifetime -`'a` +Listing 10-21: The `longest` function definition specifying that all the references in the signature must have the same lifetime `'a` This code should compile and produce the result we want when we use it with the `main` function in Listing 10-19. @@ -1502,7 +1483,7 @@ Let’s look at how the lifetime annotations restrict the `longest` function by passing in references that have different concrete lifetimes. Listing 10-22 is a straightforward example. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1516,14 +1497,12 @@ fn main() { } ``` -Listing 10-22: Using the `longest` function with -references to `String` values that have different concrete lifetimes +Listing 10-22: Using the `longest` function with references to `String` values that have different concrete lifetimes In this example, `string1` is valid until the end of the outer scope, `string2` is valid until the end of the inner scope, and `result` references something that is valid until the end of the inner scope. Run this code and you’ll see -that the borrow checker approves; it will compile and print `The longest string -is long string is long`. +that the borrow checker approves; it will compile and print `The longest string is long string is long`. Next, let’s try an example that shows that the lifetime of the reference in `result` must be the smaller lifetime of the two arguments. We’ll move the @@ -1533,7 +1512,7 @@ assignment of the value to the `result` variable inside the scope with inner scope, after the inner scope has ended. The code in Listing 10-23 will not compile. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1547,8 +1526,7 @@ fn main() { } ``` -Listing 10-23: Attempting to use `result` after `string2` -has gone out of scope +Listing 10-23: Attempting to use `result` after `string2` has gone out of scope When we try to compile this code, we get this error: @@ -1565,7 +1543,7 @@ error[E0597]: `string2` does not live long enough 7 | } | - `string2` dropped here while still borrowed 8 | println!("The longest string is {result}"); - | ------ borrow later used here + | -------- borrow later used here For more information about this error, try `rustc --explain E0597`. error: could not compile `chapter10` (bin "chapter10") due to 1 previous error @@ -1598,7 +1576,7 @@ function is doing. For example, if we changed the implementation of the string slice, we wouldn’t need to specify a lifetime on the `y` parameter. The following code will compile: -Filename: src/main.rs +src/main.rs ``` fn longest<'a>(x: &'a str, y: &str) -> &'a str { @@ -1606,6 +1584,8 @@ fn longest<'a>(x: &'a str, y: &str) -> &'a str { } ``` + + We’ve specified a lifetime parameter `'a` for the parameter `x` and the return type, but not for the parameter `y`, because the lifetime of `y` does not have any relationship with the lifetime of `x` or the return value. @@ -1618,7 +1598,7 @@ reference because the value will go out of scope at the end of the function. Consider this attempted implementation of the `longest` function that won’t compile: -Filename: src/main.rs +src/main.rs ``` fn longest<'a>(x: &str, y: &str) -> &'a str { @@ -1627,6 +1607,8 @@ fn longest<'a>(x: &str, y: &str) -> &'a str { } ``` + + Here, even though we’ve specified a lifetime parameter `'a` for the return type, this implementation will fail to compile because the return value lifetime is not related to the lifetime of the parameters at all. Here is the @@ -1668,7 +1650,7 @@ to hold references, but in that case we would need to add a lifetime annotation on every reference in the struct’s definition. Listing 10-24 has a struct named `ImportantExcerpt` that holds a string slice. -Filename: src/main.rs +src/main.rs ``` struct ImportantExcerpt<'a> { @@ -1677,15 +1659,14 @@ struct ImportantExcerpt<'a> { fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); - let first_sentence = novel.split('.').next().expect("Could not find a '.'"); + let first_sentence = novel.split('.').next().unwrap(); let i = ImportantExcerpt { part: first_sentence, }; } ``` -Listing 10-24: A struct that holds a reference, requiring -a lifetime annotation +Listing 10-24: A struct that holds a reference, requiring a lifetime annotation This struct has the single field `part` that holds a string slice, which is a reference. As with generic data types, we declare the name of the generic @@ -1708,7 +1689,7 @@ lifetime parameters for functions or structs that use references. However, we had a function in Listing 4-9, shown again in Listing 10-25, that compiled without lifetime annotations. -Filename: src/lib.rs +src/lib.rs ``` fn first_word(s: &str) -> &str { @@ -1724,9 +1705,7 @@ fn first_word(s: &str) -> &str { } ``` -Listing 10-25: A function we defined in Listing 4-9 that -compiled without lifetime annotations, even though the parameter and return -type are references +Listing 10-25: A function we defined in Listing 4-9 that compiled without lifetime annotations, even though the parameter and return type are references The reason this function compiles without lifetime annotations is historical: in early versions (pre-1.0) of Rust, this code wouldn’t have compiled because @@ -1753,11 +1732,11 @@ The patterns programmed into Rust’s analysis of references are called the a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly. -The elision rules don’t provide full inference. If Rust deterministically -applies the rules but there is still ambiguity as to what lifetimes the -references have, the compiler won’t guess what the lifetime of the remaining -references should be. Instead of guessing, the compiler will give you an error -that you can resolve by adding the lifetime annotations. +The elision rules don’t provide full inference. If there is still ambiguity as +to what lifetimes the references have after Rust applies the rules, the +compiler won’t guess what the lifetime of the remaining references should be. +Instead of guessing, the compiler will give you an error that you can resolve +by adding the lifetime annotations. Lifetimes on function or method parameters are called *input lifetimes*, and lifetimes on return values are called *output lifetimes*. @@ -1772,12 +1751,10 @@ These rules apply to `fn` definitions as well as `impl` blocks. The first rule is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: `fn foo<'a>(x: &'a i32)`; a function with two -parameters gets two separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, -y: &'b i32)`; and so on. +parameters gets two separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`; and so on. The second rule is that, if there is exactly one input lifetime parameter, that -lifetime is assigned to all output lifetime parameters: `fn foo<'a>(x: &'a i32) --> &'a i32`. +lifetime is assigned to all output lifetime parameters: `fn foo<'a>(x: &'a i32) -> &'a i32`. The third rule is that, if there are multiple input lifetime parameters, but one of them is `&self` or `&mut self` because this is a method, the lifetime of @@ -1842,7 +1819,7 @@ annotate lifetimes in method signatures very often. ### Lifetime Annotations in Method Definitions When we implement methods on a struct with lifetimes, we use the same syntax as -that of generic type parameters shown in Listing 10-11. Where we declare and +that of generic type parameters, as shown in Listing 10-11. Where we declare and use the lifetime parameters depends on whether they’re related to the struct fields or the method parameters and return values. @@ -1900,7 +1877,7 @@ let s: &'static str = "I have a static lifetime."; The text of this string is stored directly in the program’s binary, which is always available. Therefore, the lifetime of all string literals is `'static`. -You might see suggestions to use the `'static` lifetime in error messages. But +You might see suggestions in error messages to use the `'static` lifetime. But before specifying `'static` as the lifetime for a reference, think about whether the reference you have actually lives the entire lifetime of your program or not, and whether you want it to. Most of the time, an error message @@ -1954,7 +1931,7 @@ that this flexible code won’t have any dangling references. And all of this analysis happens at compile time, which doesn’t affect runtime performance! Believe it or not, there is much more to learn on the topics we discussed in -this chapter: Chapter 17 discusses trait objects, which are another way to use +this chapter: Chapter 18 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read the Rust Reference at *../reference/index.html*. But next, you’ll learn how to write tests in diff --git a/nostarch/chapter11.md b/nostarch/chapter11.md index d44a501497..75e194ec45 100644 --- a/nostarch/chapter11.md +++ b/nostarch/chapter11.md @@ -8,10 +8,10 @@ directory, so all fixes need to be made in `/src/`. # Writing Automated Tests -In his 1972 essay “The Humble Programmer,” Edsger W. Dijkstra said that -“Program testing can be a very effective way to show the presence of bugs, but -it is hopelessly inadequate for showing their absence.” That doesn’t mean we -shouldn’t try to test as much as we can! +In his 1972 essay “The Humble Programmer,” Edsger W. Dijkstra said that “program +testing can be a very effective way to show the presence of bugs, but it is +hopelessly inadequate for showing their absence.” That doesn’t mean we shouldn’t +try to test as much as we can! Correctness in our programs is the extent to which our code does what we intend it to do. Rust is designed with a high degree of concern about the correctness @@ -85,8 +85,7 @@ $ cd adder The contents of the *src/lib.rs* file in your `adder` library should look like Listing 11-1. - -Filename: src/lib.rs +src/lib.rs ``` -pub fn add(left: usize, right: usize) -> usize { +pub fn add(left: u64, right: u64) -> u64 { left + right } @@ -116,7 +115,10 @@ mod tests { } ``` -Listing 11-1: The code generated automatically by cargo new +Listing 11-1: The code generated automatically by `cargo new` + +The file starts with an example `add` function, so that we have something +to test. For now, let’s focus solely on the `it_works` function. Note the `#[test]` annotation: this attribute indicates this is a test function, so the test @@ -125,9 +127,9 @@ functions in the `tests` module to help set up common scenarios or perform common operations, so we always need to indicate which functions are tests. The example function body uses the `assert_eq!` macro to assert that `result`, -which contains the result of adding 2 and 2, equals 4. This assertion serves as -an example of the format for a typical test. Let’s run it to see that this test -passes. +which contains the result of calling `add` with 2 and 2, equals 4. This +assertion serves as an example of the format for a typical test. Let’s run it +to see that this test passes. The `cargo test` command runs all tests in our project, as shown in Listing 11-2. @@ -137,7 +139,7 @@ The `cargo test` command runs all tests in our project, as shown in Listing $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s - Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) + Running unittests src/lib.rs (file:///projects/adder/target/debug/deps/adder-7acb243c25ffd9dc) running 1 test test tests::it_works ... ok @@ -149,7 +151,6 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ``` Listing 11-2: The output from running the automatically generated test @@ -161,17 +162,16 @@ and that the result of running that test is `ok`. The overall summary `test resu It’s possible to mark a test as ignored so it doesn’t run in a particular instance; we’ll cover that in the “Ignoring Some Tests Unless Specifically Requested” section later in this chapter. Because we -haven’t done that here, the summary shows `0 ignored`. +haven’t done that here, the summary shows `0 ignored`. We can also pass an +argument to the `cargo test` command to run only tests whose name matches a +string; this is called *filtering* and we’ll cover that in the “Running a +Subset of Tests by Name” section. Here we haven’t +filtered the tests being run, so the end of the summary shows `0 filtered out`. The `0 measured` statistic is for benchmark tests that measure performance. Benchmark tests are, as of this writing, only available in nightly Rust. See the documentation about benchmark tests at *../unstable-book/library-features/test.html* to learn more. -We can pass an argument to the `cargo test` command to run only tests whose -name matches a string; this is called *filtering* and we’ll cover that in the -“Running a Subset of Tests by Name” section. Here we -haven’t filtered the tests being run, so the end of the summary shows `0 filtered out`. - The next part of the test output starting at `Doc-tests adder` is for the results of any documentation tests. We don’t have any documentation tests yet, but Rust can compile any code examples that appear in our API documentation. @@ -186,7 +186,7 @@ the `it_works` function to a different name, such as `exploration`, like so: Filename: src/lib.rs ``` -pub fn add(left: usize, right: usize) -> usize { +pub fn add(left: u64, right: u64) -> u64 { left + right } @@ -231,11 +231,10 @@ marked as failed. In Chapter 9, we talked about how the simplest way to panic is to call the `panic!` macro. Enter the new test as a function named `another`, so your *src/lib.rs* file looks like Listing 11-3. - -Filename: src/lib.rs +src/lib.rs ``` -pub fn add(left: usize, right: usize) -> usize { +pub fn add(left: u64, right: u64) -> u64 { left + right } @@ -256,7 +255,7 @@ mod tests { } ``` -Listing 11-3: Adding a second test that will fail because we call the panic! macro +Listing 11-3: Adding a second test that will fail because we call the `panic!` macro Run the tests again using `cargo test`. The output should look like Listing 11-4, which shows that our `exploration` test passed and `another` failed. @@ -311,7 +310,7 @@ had one test pass and one test fail. Now that you’ve seen what the test results look like in different scenarios, let’s look at some macros other than `panic!` that are useful in tests. -### Checking Results with the `assert!` Macro +### Checking Results with the assert! Macro The `assert!` macro, provided by the standard library, is useful when you want to ensure that some condition in a test evaluates to `true`. We give the @@ -324,8 +323,7 @@ In Chapter 5, Listing 5-15, we used a `Rectangle` struct and a `can_hold` method, which are repeated here in Listing 11-5. Let’s put this code in the *src/lib.rs* file, then write some tests for it using the `assert!` macro. - -Filename: src/lib.rs +src/lib.rs ``` #[derive(Debug)] @@ -341,7 +339,7 @@ impl Rectangle { } ``` -Listing 11-5: The Rectangle struct and its can_hold method from Chapter 5 +Listing 11-5: The `Rectangle` struct and its `can_hold` method from Chapter 5 The `can_hold` method returns a Boolean, which means it’s a perfect use case for the `assert!` macro. In Listing 11-6, we write a test that exercises the @@ -349,8 +347,7 @@ for the `assert!` macro. In Listing 11-6, we write a test that exercises the a height of 7 and asserting that it can hold another `Rectangle` instance that has a width of 5 and a height of 1. - -Filename: src/lib.rs +src/lib.rs ``` #[cfg(test)] @@ -373,7 +370,7 @@ mod tests { } ``` -Listing 11-6: A test for can_hold that checks whether a larger rectangle can indeed hold a smaller rectangle +Listing 11-6: A test for `can_hold` that checks whether a larger rectangle can indeed hold a smaller rectangle Note the `use super::*;` line inside the `tests` module. The `tests` module is a regular module that follows the usual visibility rules we covered in Chapter @@ -509,7 +506,7 @@ Our tests caught the bug! Because `larger.width` is `8` and `smaller.width` is `5`, the comparison of the widths in `can_hold` now returns `false`: 8 is not less than 5. -### Testing Equality with the `assert_eq!` and `assert_ne!` Macros +### Testing Equality with the assert_eq! and assert_ne! Macros A common way to verify functionality is to test for equality between the result of the code under test and the value you expect the code to return. You could @@ -525,8 +522,7 @@ expression, without printing the values that led to the `false` value. In Listing 11-7, we write a function named `add_two` that adds `2` to its parameter, then we test this function using the `assert_eq!` macro. - -Filename: src/lib.rs +src/lib.rs ``` pub fn add_two(a: usize) -> usize { @@ -545,7 +541,7 @@ mod tests { } ``` -Listing 11-7: Testing the function add_two using the assert_eq! macro +Listing 11-7: Testing the function `add_two` using the `assert_eq!` macro Let’s check that it passes! @@ -611,19 +607,19 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; error: test failed, to rerun pass `--lib` ``` -Our test caught the bug! The `it_adds_two` test failed, and the message tells -us ``assertion `left == right` failed`` and what the `left` and `right` values -are. This message helps us start debugging: the `left` argument, where we had -the result of calling `add_two(2)`, was `5` but the `right` argument was `4`. -You can imagine that this would be especially helpful when we have a lot of -tests going on. +Our test caught the bug! The `it_adds_two` test failed, and the message tells us +that the assertion that failed was ``assertion `left == right` failed`` and what +the `left` and `right` values are. This message helps us start debugging: the +`left` argument, where we had the result of calling `add_two(2)`, was `5` but +the `right` argument was `4`. You can imagine that this would be especially +helpful when we have a lot of tests going on. Note that in some languages and test frameworks, the parameters to equality assertion functions are called `expected` and `actual`, and the order in which we specify the arguments matters. However, in Rust, they’re called `left` and -`right`, and the order in which we specify the value we expect and the value -the code produces doesn’t matter. We could write the assertion in this test as -`assert_eq!(4, result)`, which would produce the same failure message +`right`, and the order in which we specify the value we expect and the value the +code produces doesn’t matter. We could write the assertion in this test as +`assert_eq!(4, result)`, which would result in the same failure message that that displays `` assertion failed: `(left == right)` ``. The `assert_ne!` macro will pass if the two values we give it are not equal and @@ -652,13 +648,11 @@ details about these and other derivable traits. You can also add a custom message to be printed with the failure message as optional arguments to the `assert!`, `assert_eq!`, and `assert_ne!` macros. Any arguments specified after the required arguments are passed along to the -`format!` macro (discussed in Chapter 8 in the “Concatenation with the `+` -Operator or the `format!` -Macro” -section), so you can pass a format string that contains `{}` placeholders and -values to go in those placeholders. Custom messages are useful for documenting -what an assertion means; when a test fails, you’ll have a better idea of what -the problem is with the code. +`format!` macro (discussed in “Concatenation with the `+` Operator or the +`format!` Macro” in Chapter 8), so you can pass a format string that contains `{}` +placeholders and values to go in those placeholders. Custom messages are useful +for documenting what an assertion means; when a test fails, you’ll have a better +idea of what the problem is with the code. For example, let’s say we have a function that greets people by name and we want to test that the name we pass into the function appears in the output: @@ -772,7 +766,7 @@ error: test failed, to rerun pass `--lib` We can see the value we actually got in the test output, which would help us debug what happened instead of what we were expecting to happen. -### Checking for Panics with `should_panic` +### Checking for Panics with should_panic In addition to checking return values, it’s important to check that our code handles error conditions as we expect. For example, consider the `Guess` type @@ -788,8 +782,7 @@ inside the function doesn’t panic. Listing 11-8 shows a test that checks that the error conditions of `Guess::new` happen when we expect them to. - -Filename: src/lib.rs +src/lib.rs ``` pub struct Guess { @@ -818,7 +811,7 @@ mod tests { } ``` -Listing 11-8: Testing that a condition will cause a panic! +Listing 11-8: Testing that a condition will cause a `panic!` We place the `#[should_panic]` attribute after the `#[test]` attribute and before the test function it applies to. Let’s look at the result when this test @@ -896,8 +889,7 @@ consider the modified code for `Guess` in Listing 11-9 where the `new` function panics with different messages depending on whether the value is too small or too large. - -Filename: src/lib.rs +src/lib.rs ``` // --snip-- @@ -930,7 +922,7 @@ mod tests { } ``` -Listing 11-9: Testing for a panic! with a panic message containing a specified substring +Listing 11-9: Testing for a `panic!` with a panic message containing a specified substring This test will pass because the value we put in the `should_panic` attribute’s `expected` parameter is a substring of the message that the `Guess::new` @@ -989,7 +981,7 @@ The failure message indicates that this test did indeed panic as we expected, but the panic message did not include the expected string `less than or equal to 100`. The panic message that we did get in this case was `Guess value must be greater than or equal to 1, got 200.` Now we can start figuring out where our bug is! -### Using `Result` in Tests +### Using Result in Tests Our tests so far all panic when they fail. We can also write tests that use `Result`! Here’s the test from Listing 11-1, rewritten to use `Result` and return an `Err` instead of panicking: @@ -1038,7 +1030,8 @@ binary. To separate these two types of arguments, you list the arguments that go to `cargo test` followed by the separator `--` and then the ones that go to the test binary. Running `cargo test --help` displays the options you can use with `cargo test`, and running `cargo test -- --help` displays the options you -can use after the separator. +can use after the separator. Those options are also documented in the “Tests” +section at *https://doc.rust-lang.org/rustc/tests/index.html* of the the rustc book at *https://doc.rust-lang.org/rustc/index.html*. ### Running Tests in Parallel or Consecutively @@ -1083,8 +1076,7 @@ printed to standard output with the rest of the failure message. As an example, Listing 11-10 has a silly function that prints the value of its parameter and returns 10, as well as a test that passes and a test that fails. - -Filename: src/lib.rs +src/lib.rs ``` fn prints_and_returns_10(a: i32) -> i32 { @@ -1110,7 +1102,7 @@ mod tests { } ``` -Listing 11-10: Tests for a function that calls println! +Listing 11-10: Tests for a function that calls `println!` When we run these tests with `cargo test`, we’ll see the following output: @@ -1183,8 +1175,8 @@ failures: I got the value 8 thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9: assertion `left == right` failed - left: 5 - right: 10 + left: 10 + right: 5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace @@ -1206,8 +1198,7 @@ or names of the test(s) you want to run as an argument. To demonstrate how to run a subset of tests, we’ll first create three tests for our `add_two` function, as shown in Listing 11-11, and choose which ones to run. - -Filename: src/lib.rs +src/lib.rs ``` pub fn add_two(a: usize) -> usize { @@ -1416,7 +1407,7 @@ code that they’re testing. The convention is to create a module named `tests` in each file to contain the test functions and to annotate the module with `cfg(test)`. -#### The Tests Module and `#[cfg(test)]` +#### The Tests Module and \#[cfg(test)] The `#[cfg(test)]` annotation on the `tests` module tells Rust to compile and run the test code only when you run `cargo test`, not when you run `cargo build`. This saves compile time when you only want to build the library and @@ -1432,7 +1423,7 @@ this chapter, Cargo generated this code for us: Filename: src/lib.rs ``` -pub fn add(left: usize, right: usize) -> usize { +pub fn add(left: u64, right: u64) -> u64 { left + right } @@ -1464,8 +1455,7 @@ impossible to test private functions. Regardless of which testing ideology you adhere to, Rust’s privacy rules do allow you to test private functions. Consider the code in Listing 11-12 with the private function `internal_adder`. - -Filename: src/lib.rs +src/lib.rs ``` pub fn add_two(a: usize) -> usize { @@ -1492,12 +1482,11 @@ Listing 11-12: Testing a private function Note that the `internal_adder` function is not marked as `pub`. Tests are just Rust code, and the `tests` module is just another module. As we discussed in -the “Paths for Referring to an Item in the Module Tree” -section, items in child modules can use the items in their ancestor modules. In -this test, we bring all of the `tests` module’s parent’s items into scope with -`use super::*`, and then the test can call `internal_adder`. If you don’t think -private functions should be tested, there’s nothing in Rust that will compel -you to do so. +“Paths for Referring to an Item in the Module Tree”, +items in child modules can use the items in their ancestor modules. In this +test, we bring all of the `tests` module’s parent’s items into scope with `use super::*`, and then the test can call `internal_adder`. If you don’t think +private functions should be tested, there’s nothing in Rust that will compel you +to do so. ### Integration Tests @@ -1509,7 +1498,7 @@ work correctly on their own could have problems when integrated, so test coverage of the integrated code is important as well. To create integration tests, you first need a *tests* directory. -#### The *tests* Directory +#### The tests Directory We create a *tests* directory at the top level of our project directory, next to *src*. Cargo knows to look for integration test files in this directory. We @@ -1532,8 +1521,7 @@ adder Enter the code in Listing 11-13 into the *tests/integration_test.rs* file. - -Filename: tests/integration_test.rs +tests/integration_test.rs ``` use adder::add_two; @@ -1545,7 +1533,7 @@ fn it_adds_two() { } ``` -Listing 11-13: An integration test of a function in the adder crate +Listing 11-13: An integration test of a function in the `adder` crate Each file in the *tests* directory is a separate crate, so we need to bring our library into each test crate’s scope. For that reason we add `use adder::add_two;` at the top of the code, which we didn’t need in the unit tests. @@ -1697,14 +1685,13 @@ project directory now looks like this: └── integration_test.rs ``` -This is the older naming convention that Rust also understands that we -mentioned in the “Alternate File Paths” section of -Chapter 7. Naming the file this way tells Rust not to treat the `common` module -as an integration test file. When we move the `setup` function code into -*tests/common/mod.rs* and delete the *tests/common.rs* file, the section in the -test output will no longer appear. Files in subdirectories of the *tests* -directory don’t get compiled as separate crates or have sections in the test -output. +This is the older naming convention that Rust also understands that we mentioned +in “Alternate File Paths” in Chapter 7. Naming the +file this way tells Rust not to treat the `common` module as an integration test +file. When we move the `setup` function code into *tests/common/mod.rs* and +delete the *tests/common.rs* file, the section in the test output will no longer +appear. Files in subdirectories of the *tests* directory don’t get compiled as +separate crates or have sections in the test output. After we’ve created *tests/common/mod.rs*, we can use it from any of the integration test files as a module. Here’s an example of calling the `setup` diff --git a/nostarch/chapter12.md b/nostarch/chapter12.md index 3ce134974b..a916242989 100644 --- a/nostarch/chapter12.md +++ b/nostarch/chapter12.md @@ -44,7 +44,7 @@ Our `grep` project will combine a number of concepts you’ve learned so far: * Writing tests (Chapter 11) We’ll also briefly introduce closures, iterators, and trait objects, which -Chapter 13 and Chapter 17 will +Chapter 13 and Chapter 18 will cover in detail. ## Accepting Command Line Arguments @@ -87,8 +87,7 @@ the iterator produces. The code in Listing 12-1 allows your `minigrep` program to read any command line arguments passed to it, and then collect the values into a vector. - -Filename: src/main.rs +src/main.rs ``` use std::env; @@ -111,15 +110,14 @@ from `std::env`. It’s also less ambiguous than adding `use std::env::args` and then calling the function with just `args`, because `args` might easily be mistaken for a function that’s defined in the current module. - > - > ### The `args` Function and Invalid Unicode - > - > Note that `std::env::args` will panic if any argument contains invalid - > Unicode. If your program needs to accept arguments containing invalid - > Unicode, use `std::env::args_os` instead. That function returns an iterator - > that produces `OsString` values instead of `String` values. We’ve chosen to - > use `std::env::args` here for simplicity because `OsString` values differ per - > platform and are more complex to work with than `String` values. +> ### The args Function and Invalid Unicode +> +> Note that `std::env::args` will panic if any argument contains invalid +> Unicode. If your program needs to accept arguments containing invalid +> Unicode, use `std::env::args_os` instead. That function returns an iterator +> that produces `OsString` values instead of `String` values. We’ve chosen to +> use `std::env::args` here for simplicity because `OsString` values differ per +> platform and are more complex to work with than `String` values. On the first line of `main`, we call `env::args`, and we immediately use `collect` to turn the iterator into a vector containing all the values produced @@ -169,8 +167,7 @@ arguments. Now we need to save the values of the two arguments in variables so we can use the values throughout the rest of the program. We do that in Listing 12-2. - -Filename: src/main.rs +src/main.rs ``` use std::env; @@ -223,8 +220,7 @@ has an Emily Dickinson poem that will work well! Create a file called *poem.txt* at the root level of your project, and enter the poem “I’m Nobody! Who are you?” - -Filename: poem.txt +poem.txt ``` I'm nobody! Who are you? @@ -243,8 +239,7 @@ Listing 12-3: A poem by Emily Dickinson makes a good test case. With the text in place, edit *src/main.rs* and add code to read the file, as shown in Listing 12-4. - -Filename: src/main.rs +src/main.rs ``` use std::env; @@ -379,8 +374,7 @@ We’ll extract the functionality for parsing arguments into a function that *src/lib.rs*. Listing 12-5 shows the new start of `main` that calls a new function `parse_config`, which we’ll define in *src/main.rs* for the moment. - -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -399,7 +393,7 @@ fn parse_config(args: &[String]) -> (&str, &str) { } ``` -Listing 12-5: Extracting a parse_config function from main +Listing 12-5: Extracting a `parse_config` function from `main` We’re still collecting the command line arguments into a vector, but instead of assigning the argument value at index 1 to the variable `query` and the @@ -434,8 +428,7 @@ other and what their purpose is. Listing 12-6 shows the improvements to the `parse_config` function. - -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -465,7 +458,7 @@ fn parse_config(args: &[String]) -> Config { } ``` -Listing 12-6: Refactoring parse_config to return an instance of a Config struct +Listing 12-6: Refactoring `parse_config` to return an instance of a `Config` struct We’ve added a struct named `Config` defined to have fields named `query` and `file_path`. The signature of `parse_config` now indicates that it returns a @@ -484,19 +477,18 @@ However, cloning the data also makes our code very straightforward because we don’t have to manage the lifetimes of the references; in this circumstance, giving up a little performance to gain simplicity is a worthwhile trade-off. - > - > ### The Trade-Offs of Using `clone` - > - > There’s a tendency among many Rustaceans to avoid using `clone` to fix - > ownership problems because of its runtime cost. In - > Chapter 13, you’ll learn how to use more efficient - > methods in this type of situation. But for now, it’s okay to copy a few - > strings to continue making progress because you’ll make these copies only - > once and your file path and query string are very small. It’s better to have - > a working program that’s a bit inefficient than to try to hyperoptimize code - > on your first pass. As you become more experienced with Rust, it’ll be - > easier to start with the most efficient solution, but for now, it’s - > perfectly acceptable to call `clone`. +> ### The Trade-Offs of Using clone +> +> There’s a tendency among many Rustaceans to avoid using `clone` to fix +> ownership problems because of its runtime cost. In +> Chapter 13, you’ll learn how to use more efficient +> methods in this type of situation. But for now, it’s okay to copy a few +> strings to continue making progress because you’ll make these copies only +> once and your file path and query string are very small. It’s better to have +> a working program that’s a bit inefficient than to try to hyperoptimize code +> on your first pass. As you become more experienced with Rust, it’ll be +> easier to start with the most efficient solution, but for now, it’s +> perfectly acceptable to call `clone`. We’ve updated `main` so it places the instance of `Config` returned by `parse_config` into a variable named `config`, and we updated the code that @@ -508,7 +500,7 @@ that their purpose is to configure how the program will work. Any code that uses these values knows to find them in the `config` instance in the fields named for their purpose. -#### Creating a Constructor for `Config` +#### Creating a Constructor for Config So far, we’ve extracted the logic responsible for parsing the command line arguments from `main` and placed it in the `parse_config` function. Doing so @@ -526,8 +518,7 @@ changing `parse_config` into a `new` function associated with `Config`, we’ll be able to create instances of `Config` by calling `Config::new`. Listing 12-7 shows the changes we need to make. - -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -550,7 +541,7 @@ impl Config { } ``` -Listing 12-7: Changing parse_config into Config::new +Listing 12-7: Changing `parse_config` into `Config::new` We’ve updated `main` where we were calling `parse_config` to instead call `Config::new`. We’ve changed the name of `parse_config` to `new` and moved it @@ -584,8 +575,7 @@ In Listing 12-8, we add a check in the `new` function that will verify that the slice is long enough before accessing index 1 and index 2. If the slice isn’t long enough, the program panics and displays a better error message. - -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -631,7 +621,7 @@ we’ll use the other technique you learned about in Chapter 9—returning a -#### Returning a `Result` Instead of Calling `panic!` +#### Returning a Result Instead of Calling panic! We can instead return a `Result` value that will contain a `Config` instance in the successful case and will describe the problem in the error case. We’re also @@ -646,8 +636,7 @@ function we’re now calling `Config::build` and the body of the function needed to return a `Result`. Note that this won’t compile until we update `main` as well, which we’ll do in the next listing. - -Filename: src/main.rs +src/main.rs ``` impl Config { @@ -664,7 +653,7 @@ impl Config { } ``` -Listing 12-9: Returning a Result from Config::build +Listing 12-9: Returning a `Result` from `Config::build` Our `build` function returns a `Result` with a `Config` instance in the success case and a string literal in the error case. Our error values will always be @@ -683,7 +672,7 @@ process more cleanly in the error case. -#### Calling `Config::build` and Handling Errors +#### Calling Config::build and Handling Errors To handle the error case and print a user-friendly message, we need to update `main` to handle the `Result` being returned by `Config::build`, as shown in @@ -692,8 +681,7 @@ tool with a nonzero error code away from `panic!` and instead implement it by hand. A nonzero exit status is a convention to signal to the process that called our program that the program exited with an error state. - -Filename: src/main.rs +src/main.rs ``` use std::process; @@ -709,7 +697,7 @@ fn main() { // --snip-- ``` -Listing 12-10: Exiting with an error code if building a Config fails +Listing 12-10: Exiting with an error code if building a `Config` fails In this listing, we’ve used a method we haven’t covered in detail yet: `unwrap_or_else`, which is defined on `Result` by the standard library. @@ -743,7 +731,7 @@ Problem parsing arguments: not enough arguments Great! This output is much friendlier for our users. -### Extracting Logic from `main` +### Extracting Logic from main Now that we’ve finished refactoring the configuration parsing, let’s turn to the program’s logic. As we stated in “Separation of Concerns for Binary @@ -757,8 +745,7 @@ Listing 12-11 shows the extracted `run` function. For now, we’re just making the small, incremental improvement of extracting the function. We’re still defining the function in *src/main.rs*. - -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -780,13 +767,13 @@ fn run(config: Config) { // --snip-- ``` -Listing 12-11: Extracting a run function containing the rest of the program logic +Listing 12-11: Extracting a `run` function containing the rest of the program logic The `run` function now contains all the remaining logic from `main`, starting from reading the file. The `run` function takes the `Config` instance as an argument. -#### Returning Errors from the `run` Function +#### Returning Errors from the run Function With the remaining program logic separated into the `run` function, we can improve the error handling, as we did with `Config::build` in Listing 12-9. @@ -796,8 +783,7 @@ us further consolidate the logic around handling errors into `main` in a user-friendly way. Listing 12-12 shows the changes we need to make to the signature and body of `run`. - -Filename: src/main.rs +src/main.rs ``` use std::error::Error; @@ -813,7 +799,7 @@ fn run(config: Config) -> Result<(), Box> { } ``` -Listing 12-12: Changing the run function to return Result +Listing 12-12: Changing the `run` function to return `Result` We’ve made three significant changes here. First, we changed the return type of the `run` function to `Result<(), Box>`. This function previously @@ -822,7 +808,7 @@ returned the unit type, `()`, and we keep that as the value returned in the For the error type, we used the *trait object* `Box` (and we’ve brought `std::error::Error` into scope with a `use` statement at the top). -We’ll cover trait objects in Chapter 17. For now, just +We’ll cover trait objects in Chapter 18. For now, just know that `Box` means the function will return a type that implements the `Error` trait, but we don’t have to specify what particular type the return value will be. This gives us flexibility to return error values that @@ -882,7 +868,7 @@ might indicate that an error occurred. But we’re not checking to see whether o not there was an error, and the compiler reminds us that we probably meant to have some error-handling code here! Let’s rectify that problem now. -#### Handling Errors Returned from `run` in `main` +#### Handling Errors Returned from run in main We’ll check for errors and handle them using a technique similar to one we used with `Config::build` in Listing 12-10, but with a slight difference: @@ -931,8 +917,7 @@ The contents of *src/lib.rs* should have the signatures shown in Listing 12-13 (we’ve omitted the bodies of the functions for brevity). Note that this won’t compile until we modify *src/main.rs* in Listing 12-14. - -Filename: src/lib.rs +src/lib.rs ``` use std::error::Error; @@ -954,7 +939,7 @@ pub fn run(config: Config) -> Result<(), Box> { } ``` -Listing 12-13: Moving Config and run into src/lib.rs +Listing 12-13: Moving `Config` and `run` into *src/lib.rs* We’ve made liberal use of the `pub` keyword: on `Config`, on its fields and its `build` method, and on the `run` function. We now have a library crate that has @@ -963,8 +948,7 @@ a public API we can test! Now we need to bring the code we moved to *src/lib.rs* into the scope of the binary crate in *src/main.rs*, as shown in Listing 12-14. - -Filename: src/main.rs +src/main.rs ``` use std::env; @@ -980,7 +964,7 @@ fn main() { } ``` -Listing 12-14: Using the minigrep library crate in src/main.rs +Listing 12-14: Using the `minigrep` library crate in *src/main.rs* We add a `use minigrep::Config` line to bring the `Config` type from the library crate into the binary crate’s scope, and we prefix the `run` function @@ -1032,8 +1016,7 @@ the behavior we want the `search` function to have: it will take a query and the text to search, and it will return only the lines from the text that contain the query. Listing 12-15 shows this test, which won’t compile yet. - -Filename: src/lib.rs +src/lib.rs ``` #[cfg(test)] @@ -1053,7 +1036,7 @@ Pick three."; } ``` -Listing 12-15: Creating a failing test for the search function we wish we had +Listing 12-15: Creating a failing test for the `search` function we wish we had This test searches for the string `"duct"`. The text we’re searching is three lines, only one of which contains `"duct"` (note that the backslash after the @@ -1068,8 +1051,7 @@ adding a definition of the `search` function that always returns an empty vector, as shown in Listing 12-16. Then the test should compile and fail because an empty vector doesn’t match a vector containing the line `"safe, fast, productive."` - -Filename: src/lib.rs +src/lib.rs ``` pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { @@ -1077,7 +1059,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 12-16: Defining just enough of the search function so our test will compile +Listing 12-16: Defining just enough of the `search` function so our test will compile Notice that we need to define an explicit lifetime `'a` in the signature of `search` and use that lifetime with the `contents` argument and the return @@ -1172,14 +1154,13 @@ that and implement `search`, our program needs to follow these steps: Let’s work through each step, starting with iterating through lines. -#### Iterating Through Lines with the `lines` Method +#### Iterating Through Lines with the lines Method Rust has a helpful method to handle line-by-line iteration of strings, conveniently named `lines`, that works as shown in Listing 12-17. Note that this won’t compile yet. - -Filename: src/lib.rs +src/lib.rs ``` pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { @@ -1189,7 +1170,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 12-17: Iterating through each line in contents +Listing 12-17: Iterating through each line in `contents` The `lines` method returns an iterator. We’ll talk about iterators in depth in Chapter 13, but recall that you saw this way @@ -1203,8 +1184,7 @@ Fortunately, strings have a helpful method named `contains` that does this for us! Add a call to the `contains` method in the `search` function, as shown in Listing 12-18. Note that this still won’t compile yet. - -Filename: src/lib.rs +src/lib.rs ``` pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { @@ -1216,7 +1196,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 12-18: Adding functionality to see whether the line contains the string in query +Listing 12-18: Adding functionality to see whether the line contains the string in `query` At the moment, we’re building up functionality. To get the code to compile, we need to return a value from the body as we indicated we would in the function @@ -1229,8 +1209,7 @@ to return. For that, we can make a mutable vector before the `for` loop and call the `push` method to store a `line` in the vector. After the `for` loop, we return the vector, as shown in Listing 12-19. - -Filename: src/lib.rs +src/lib.rs ``` pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { @@ -1285,7 +1264,7 @@ but it doesn’t take advantage of some useful features of iterators. We’ll return to this example in Chapter 13, where we’ll explore iterators in detail, and look at how to improve it. -#### Using the `search` Function in the `run` Function +#### Using the search Function in the run Function Now that the `search` function is working and tested, we need to call `search` from our `run` function. We need to pass the `config.query` value and the @@ -1358,7 +1337,7 @@ users enter it each time they want it to apply, but by instead making it an environment variable, we allow our users to set the environment variable once and have all their searches be case insensitive in that terminal session. -### Writing a Failing Test for the Case-Insensitive `search` Function +### Writing a Failing Test for the Case-Insensitive search Function We first add a new `search_case_insensitive` function that will be called when the environment variable has a value. We’ll continue to follow the TDD process, @@ -1367,8 +1346,7 @@ the new `search_case_insensitive` function and rename our old test from `one_result` to `case_sensitive` to clarify the differences between the two tests, as shown in Listing 12-20. - -Filename: src/lib.rs +src/lib.rs ``` #[cfg(test)] @@ -1422,15 +1400,14 @@ the `search_case_insensitive` function. Feel free to add a skeleton implementation that always returns an empty vector, similar to the way we did for the `search` function in Listing 12-16 to see the test compile and fail. -### Implementing the `search_case_insensitive` Function +### Implementing the search_case_insensitive Function The `search_case_insensitive` function, shown in Listing 12-21, will be almost the same as the `search` function. The only difference is that we’ll lowercase the `query` and each `line` so that whatever the case of the input arguments, they’ll be the same case when we check whether the line contains the query. - -Filename: src/lib.rs +src/lib.rs ``` pub fn search_case_insensitive<'a>( @@ -1450,16 +1427,16 @@ pub fn search_case_insensitive<'a>( } ``` -Listing 12-21: Defining the search_case_insensitive function to lowercase the query and the line before comparing them +Listing 12-21: Defining the `search_case_insensitive` function to lowercase the query and the line before comparing them -First we lowercase the `query` string and store it in a shadowed variable with -the same name. Calling `to_lowercase` on the query is necessary so that no -matter whether the user’s query is `"rust"`, `"RUST"`, `"Rust"`, or `"rUsT"`, -we’ll treat the query as if it were `"rust"` and be insensitive to the case. -While `to_lowercase` will handle basic Unicode, it won’t be 100% accurate. If -we were writing a real application, we’d want to do a bit more work here, but -this section is about environment variables, not Unicode, so we’ll leave it at -that here. +First we lowercase the `query` string and store it in a new variable with the +same name, shadowing the original. Calling `to_lowercase` on the query is +necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`, +`"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be +insensitive to the case. While `to_lowercase` will handle basic Unicode, it +won’t be 100% accurate. If we were writing a real application, we’d want to do a +bit more work here, but this section is about environment variables, not +Unicode, so we’ll leave it at that here. Note that `query` is now a `String` rather than a string slice because calling `to_lowercase` creates new data rather than referencing existing data. Say the @@ -1522,8 +1499,7 @@ function to check the `ignore_case` field’s value and use that to decide whether to call the `search` function or the `search_case_insensitive` function, as shown in Listing 12-22. This still won’t compile yet. - -Filename: src/lib.rs +src/lib.rs ``` pub fn run(config: Config) -> Result<(), Box> { @@ -1543,7 +1519,7 @@ pub fn run(config: Config) -> Result<(), Box> { } ``` -Listing 12-22: Calling either search or search_case_insensitive based on the value in config.ignore_case +Listing 12-22: Calling either `search` or `search_case_insensitive` based on the value in `config.ignore_case` Finally, we need to check for the environment variable. The functions for working with environment variables are in the `env` module in the standard @@ -1552,8 +1528,7 @@ we’ll use the `var` function from the `env` module to check to see if any valu has been set for an environment variable named `IGNORE_CASE`, as shown in Listing 12-23. - -Filename: src/lib.rs +src/lib.rs ``` use std::env; @@ -1579,7 +1554,7 @@ impl Config { } ``` -Listing 12-23: Checking for any value in an environment variable named IGNORE_CASE +Listing 12-23: Checking for any value in an environment variable named `IGNORE_CASE` Here, we create a new variable, `ignore_case`. To set its value, we call the `env::var` function and pass it the name of the `IGNORE_CASE` environment @@ -1721,8 +1696,7 @@ the `eprintln!` macro that prints to the standard error stream, so let’s chang the two places we were calling `println!` to print errors to use `eprintln!` instead. - -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1740,7 +1714,7 @@ fn main() { } ``` -Listing 12-24: Writing error messages to standard error instead of standard output using eprintln! +Listing 12-24: Writing error messages to standard error instead of standard output using `eprintln!` Let’s now run the program again in the same way, without any arguments and redirecting standard output with `>`: diff --git a/nostarch/chapter13.md b/nostarch/chapter13.md index 52c1f5f977..a3153a75b9 100644 --- a/nostarch/chapter13.md +++ b/nostarch/chapter13.md @@ -23,15 +23,19 @@ More specifically, we’ll cover: * *Closures*, a function-like construct you can store in a variable * *Iterators*, a way of processing a series of elements * How to use closures and iterators to improve the I/O project in Chapter 12 -* The performance of closures and iterators (spoiler alert: they’re faster than -you might think!) +* The performance of closures and iterators (Spoiler alert: they’re faster than + you might think!) We’ve already covered some other Rust features, such as pattern matching and enums, that are also influenced by the functional style. Because mastering closures and iterators is an important part of writing idiomatic, fast Rust code, we’ll devote this entire chapter to them. -## Closures: Anonymous Functions That Capture Their Environment + + + + +## Closures: Anonymous Functions that Capture Their Environment Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then @@ -40,11 +44,17 @@ functions, closures can capture values from the scope in which they’re defined We’ll demonstrate how these closure features allow for code reuse and behavior customization. + + + + + + ### Capturing the Environment with Closures We’ll first examine how we can use closures to capture values from the -environment they’re defined in for later use. Here’s the scenario: every so -often, our T-shirt company gives away an exclusive, limited-edition shirt to +environment they’re defined in for later use. Here’s the scenario: Every so +often, our t-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion. People on the mailing list can optionally add their favorite color to their profile. If the person chosen for a free shirt has their favorite color set, they get that color shirt. If the @@ -56,11 +66,11 @@ enum called `ShirtColor` that has the variants `Red` and `Blue` (limiting the number of colors available for simplicity). We represent the company’s inventory with an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirt colors currently in stock. -The method `giveaway` defined on `Inventory` gets the optional shirt color -preference of the free-shirt winner, and returns the shirt color the person -will get. This setup is shown in Listing 13-1. +The method `giveaway` defined on `Inventory` gets the optional shirt +color preference of the free shirt winner, and returns the shirt color the +person will get. This setup is shown in Listing 13-1: -Filename: src/main.rs +src/main.rs ``` #[derive(Debug, PartialEq, Copy, Clone)] @@ -74,11 +84,8 @@ struct Inventory { } impl Inventory { - fn giveaway( - &self, - user_preference: Option, - ) -> ShirtColor { - 1 user_preference.unwrap_or_else(|| self.most_stocked()) + fn giveaway(&self, user_preference: Option) -> ShirtColor { + user_preference.unwrap_or_else(|| self.most_stocked()) } fn most_stocked(&self) -> ShirtColor { @@ -101,22 +108,18 @@ impl Inventory { fn main() { let store = Inventory { - 2 shirts: vec![ - ShirtColor::Blue, - ShirtColor::Red, - ShirtColor::Blue, - ], + shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue], }; let user_pref1 = Some(ShirtColor::Red); - 3 let giveaway1 = store.giveaway(user_pref1); + let giveaway1 = store.giveaway(user_pref1); println!( "The user with preference {:?} gets {:?}", user_pref1, giveaway1 ); let user_pref2 = None; - 4 let giveaway2 = store.giveaway(user_pref2); + let giveaway2 = store.giveaway(user_pref2); println!( "The user with preference {:?} gets {:?}", user_pref2, giveaway2 @@ -127,32 +130,36 @@ fn main() { Listing 13-1: Shirt company giveaway situation The `store` defined in `main` has two blue shirts and one red shirt remaining -to distribute for this limited-edition promotion [2]. We call the `giveaway` -method for a user with a preference for a red shirt [3] and a user without any -preference [4]. +to distribute for this limited-edition promotion. We call the `giveaway` method +for a user with a preference for a red shirt and a user without any preference. Again, this code could be implemented in many ways, and here, to focus on -closures, we’ve stuck to concepts you’ve already learned, except for the body -of the `giveaway` method that uses a closure. In the `giveaway` method, we get -the user preference as a parameter of type `Option` and call the -`unwrap_or_else` method on `user_preference` [1]. The `unwrap_or_else` method -on `Option` is defined by the standard library. It takes one argument: a -closure without any arguments that returns a value `T` (the same type stored in -the `Some` variant of the `Option`, in this case `ShirtColor`). If the -`Option` is the `Some` variant, `unwrap_or_else` returns the value from -within the `Some`. If the `Option` is the `None` variant, `unwrap_or_else` -calls the closure and returns the value returned by the closure. +closures, we’ve stuck to concepts you’ve already learned except for the body of +the `giveaway` method that uses a closure. In the `giveaway` method, we get the +user preference as a parameter of type `Option` and call the +`unwrap_or_else` method on `user_preference`. The `unwrap_or_else` method on +`Option` is defined by the standard library. +It takes one argument: a closure without any arguments that returns a value `T` +(the same type stored in the `Some` variant of the `Option`, in this case +`ShirtColor`). If the `Option` is the `Some` variant, `unwrap_or_else` +returns the value from within the `Some`. If the `Option` is the `None` +variant, `unwrap_or_else` calls the closure and returns the value returned by +the closure. We specify the closure expression `|| self.most_stocked()` as the argument to `unwrap_or_else`. This is a closure that takes no parameters itself (if the -closure had parameters, they would appear between the two vertical pipes). The +closure had parameters, they would appear between the two vertical bars). The body of the closure calls `self.most_stocked()`. We’re defining the closure here, and the implementation of `unwrap_or_else` will evaluate the closure later if the result is needed. -Running this code prints the following: +Running this code prints: ``` +$ cargo run + Compiling shirt-company v0.1.0 (file:///projects/shirt-company) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s + Running `target/debug/shirt-company` The user with preference Some(Red) gets Red The user with preference None gets Blue ``` @@ -187,27 +194,26 @@ explicitness and clarity at the cost of being more verbose than is strictly necessary. Annotating the types for a closure would look like the definition shown in Listing 13-2. In this example, we’re defining a closure and storing it in a variable rather than defining the closure in the spot we pass it as an -argument, as we did in Listing 13-1. +argument as we did in Listing 13-1. -Filename: src/main.rs +src/main.rs ``` -let expensive_closure = |num: u32| -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - num -}; + let expensive_closure = |num: u32| -> u32 { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num + }; ``` -Listing 13-2: Adding optional type annotations of the parameter and return -value types in the closure +Listing 13-2: Adding optional type annotations of the parameter and return value types in the closure With type annotations added, the syntax of closures looks more similar to the -syntax of functions. Here, we define a function that adds 1 to its parameter -and a closure that has the same behavior, for comparison. We’ve added some -spaces to line up the relevant parts. This illustrates how closure syntax is -similar to function syntax except for the use of pipes and the amount of syntax -that is optional: +syntax of functions. Here we define a function that adds 1 to its parameter and +a closure that has the same behavior, for comparison. We’ve added some spaces +to line up the relevant parts. This illustrates how closure syntax is similar +to function syntax except for the use of pipes and the amount of syntax that is +optional: ``` fn add_one_v1 (x: u32) -> u32 { x + 1 } @@ -216,13 +222,13 @@ let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ; ``` -The first line shows a function definition and the second line shows a fully +The first line shows a function definition, and the second line shows a fully annotated closure definition. In the third line, we remove the type annotations -from the closure definition. In the fourth line, we remove the curly brackets, -which are optional because the closure body has only one expression. These are -all valid definitions that will produce the same behavior when they’re called. -The `add_one_v3` and `add_one_v4` lines require the closures to be evaluated to -be able to compile because the types will be inferred from their usage. This is +from the closure definition. In the fourth line, we remove the brackets, which +are optional because the closure body has only one expression. These are all +valid definitions that will produce the same behavior when they’re called. The +`add_one_v3` and `add_one_v4` lines require the closures to be evaluated to be +able to compile because the types will be inferred from their usage. This is similar to `let v = Vec::new();` needing either type annotations or values of some type to be inserted into the `Vec` for Rust to be able to infer the type. @@ -235,29 +241,46 @@ Because there are no type annotations, we can call the closure with any type, which we’ve done here with `String` the first time. If we then try to call `example_closure` with an integer, we’ll get an error. -Filename: src/main.rs +src/main.rs ``` -let example_closure = |x| x; + let example_closure = |x| x; -let s = example_closure(String::from("hello")); -let n = example_closure(5); + let s = example_closure(String::from("hello")); + let n = example_closure(5); ``` -Listing 13-3: Attempting to call a closure whose types are inferred with two -different types +Listing 13-3: Attempting to call a closure whose types are inferred with two different types The compiler gives us this error: ``` +$ cargo run + Compiling closure-example v0.1.0 (file:///projects/closure-example) error[E0308]: mismatched types --> src/main.rs:5:29 | 5 | let n = example_closure(5); - | ^- help: try using a conversion method: -`.to_string()` - | | - | expected struct `String`, found integer + | --------------- ^- help: try using a conversion method: `.to_string()` + | | | + | | expected `String`, found integer + | arguments to this function are incorrect + | +note: expected because the closure was earlier called with an argument of type `String` + --> src/main.rs:4:29 + | +4 | let s = example_closure(String::from("hello")); + | --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String` + | | + | in this closure call +note: closure parameter defined here + --> src/main.rs:2:28 + | +2 | let example_closure = |x| x; + | ^ + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `closure-example` (bin "closure-example") due to 1 previous error ``` The first time we call `example_closure` with the `String` value, the compiler @@ -275,29 +298,28 @@ captured values. In Listing 13-4, we define a closure that captures an immutable reference to the vector named `list` because it only needs an immutable reference to print -the value. +the value: -Filename: src/main.rs +src/main.rs ``` fn main() { let list = vec![1, 2, 3]; - println!("Before defining closure: {:?}", list); + println!("Before defining closure: {list:?}"); - 1 let only_borrows = || println!("From closure: {:?}", list); + let only_borrows = || println!("From closure: {list:?}"); - println!("Before calling closure: {:?}", list); - 2 only_borrows(); - println!("After calling closure: {:?}", list); + println!("Before calling closure: {list:?}"); + only_borrows(); + println!("After calling closure: {list:?}"); } ``` -Listing 13-4: Defining and calling a closure that captures an immutable -reference +Listing 13-4: Defining and calling a closure that captures an immutable reference -This example also illustrates that a variable can bind to a closure definition -[1], and we can later call the closure by using the variable name and -parentheses as if the variable name were a function name [2]. +This example also illustrates that a variable can bind to a closure definition, +and we can later call the closure by using the variable name and parentheses as +if the variable name were a function name. Because we can have multiple immutable references to `list` at the same time, `list` is still accessible from the code before the closure definition, after @@ -305,6 +327,12 @@ the closure definition but before the closure is called, and after the closure is called. This code compiles, runs, and prints: ``` +$ cargo run + Locking 1 package to latest compatible version + Adding closure-example v0.1.0 (/Users/chris/dev/rust-lang/book/tmp/listings/ch13-functional-features/listing-13-04) + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` Before defining closure: [1, 2, 3] Before calling closure: [1, 2, 3] From closure: [1, 2, 3] @@ -312,19 +340,19 @@ After calling closure: [1, 2, 3] ``` Next, in Listing 13-5, we change the closure body so that it adds an element to -the `list` vector. The closure now captures a mutable reference. +the `list` vector. The closure now captures a mutable reference: -Filename: src/main.rs +src/main.rs ``` fn main() { let mut list = vec![1, 2, 3]; - println!("Before defining closure: {:?}", list); + println!("Before defining closure: {list:?}"); let mut borrows_mutably = || list.push(7); borrows_mutably(); - println!("After calling closure: {:?}", list); + println!("After calling closure: {list:?}"); } ``` @@ -333,6 +361,12 @@ Listing 13-5: Defining and calling a closure that captures a mutable reference This code compiles, runs, and prints: ``` +$ cargo run + Locking 1 package to latest compatible version + Adding closure-example v0.1.0 (/Users/chris/dev/rust-lang/book/tmp/listings/ch13-functional-features/listing-13-05) + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` Before defining closure: [1, 2, 3] After calling closure: [1, 2, 3, 7] ``` @@ -354,40 +388,45 @@ the data so that it’s owned by the new thread. We’ll discuss threads and why you would want to use them in detail in Chapter 16 when we talk about concurrency, but for now, let’s briefly explore spawning a new thread using a closure that needs the `move` keyword. Listing 13-6 shows Listing 13-4 modified -to print the vector in a new thread rather than in the main thread. +to print the vector in a new thread rather than in the main thread: -Filename: src/main.rs +src/main.rs ``` use std::thread; fn main() { let list = vec![1, 2, 3]; - println!("Before defining closure: {:?}", list); + println!("Before defining closure: {list:?}"); - 1 thread::spawn(move || { - 2 println!("From thread: {:?}", list) - }).join().unwrap(); + thread::spawn(move || println!("From thread: {list:?}")) + .join() + .unwrap(); } ``` -Listing 13-6: Using `move` to force the closure for the thread to take -ownership of `list` +Listing 13-6: Using `move` to force the closure for the thread to take ownership of `list` We spawn a new thread, giving the thread a closure to run as an argument. The closure body prints out the list. In Listing 13-4, the closure only captured `list` using an immutable reference because that’s the least amount of access to `list` needed to print it. In this example, even though the closure body -still only needs an immutable reference [2], we need to specify that `list` -should be moved into the closure by putting the `move` keyword [1] at the -beginning of the closure definition. The new thread might finish before the -rest of the main thread finishes, or the main thread might finish first. If the -main thread maintains ownership of `list` but ends before the new thread and -drops `list`, the immutable reference in the thread would be invalid. -Therefore, the compiler requires that `list` be moved into the closure given to -the new thread so the reference will be valid. Try removing the `move` keyword -or using `list` in the main thread after the closure is defined to see what -compiler errors you get! +still only needs an immutable reference, we need to specify that `list` should +be moved into the closure by putting the `move` keyword at the beginning of the +closure definition. The new thread might finish before the rest of the main +thread finishes, or the main thread might finish first. If the main thread +maintained ownership of `list` but ended before the new thread did and dropped +`list`, the immutable reference in the thread would be invalid. Therefore, the +compiler requires that `list` be moved into the closure given to the new thread +so the reference will be valid. Try removing the `move` keyword or using `list` +in the main thread after the closure is defined to see what compiler errors you +get! + + + + + + ### Moving Captured Values Out of Closures and the Fn Traits @@ -395,11 +434,10 @@ Once a closure has captured a reference or captured ownership of a value from the environment where the closure is defined (thus affecting what, if anything, is moved *into* the closure), the code in the body of the closure defines what happens to the references or values when the closure is evaluated later (thus -affecting what, if anything, is moved *out of* the closure). - -A closure body can do any of the following: move a captured value out of the -closure, mutate the captured value, neither move nor mutate the value, or -capture nothing from the environment to begin with. +affecting what, if anything, is moved *out of* the closure). A closure body can +do any of the following: move a captured value out of the closure, mutate the +captured value, neither move nor mutate the value, or capture nothing from the +environment to begin with. The way a closure captures and handles values from the environment affects which traits the closure implements, and traits are how functions and structs @@ -407,18 +445,18 @@ can specify what kinds of closures they can use. Closures will automatically implement one, two, or all three of these `Fn` traits, in an additive fashion, depending on how the closure’s body handles the values: -* `FnOnce` applies to closures that can be called once. All closures implement -at least this trait because all closures can be called. A closure that moves -captured values out of its body will only implement `FnOnce` and none of the -other `Fn` traits because it can only be called once. -* `FnMut` applies to closures that don’t move captured values out of their -body, but that might mutate the captured values. These closures can be called -more than once. -* `Fn` applies to closures that don’t move captured values out of their body -and that don’t mutate captured values, as well as closures that capture nothing -from their environment. These closures can be called more than once without -mutating their environment, which is important in cases such as calling a -closure multiple times concurrently. +1. `FnOnce` applies to closures that can be called once. All closures implement + at least this trait, because all closures can be called. A closure that + moves captured values out of its body will only implement `FnOnce` and none + of the other `Fn` traits, because it can only be called once. +1. `FnMut` applies to closures that don’t move captured values out of their + body, but that might mutate the captured values. These closures can be + called more than once. +1. `Fn` applies to closures that don’t move captured values out of their body + and that don’t mutate captured values, as well as closures that capture + nothing from their environment. These closures can be called more than once + without mutating their environment, which is important in cases such as + calling a closure multiple times concurrently. Let’s look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-1: @@ -449,29 +487,29 @@ the closure we provide when calling `unwrap_or_else`. The trait bound specified on the generic type `F` is `FnOnce() -> T`, which means `F` must be able to be called once, take no arguments, and return a `T`. Using `FnOnce` in the trait bound expresses the constraint that -`unwrap_or_else` is only going to call `f` one time, at most. In the body of +`unwrap_or_else` is only going to call `f` at most one time. In the body of `unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won’t be called. If the `Option` is `None`, `f` will be called once. Because all -closures implement `FnOnce`, `unwrap_or_else` accepts the largest variety of +closures implement `FnOnce`, `unwrap_or_else` accepts all three kinds of closures and is as flexible as it can be. -> Note: Functions can implement all three of the `Fn` traits too. If what we -want to do doesn’t require capturing a value from the environment, we can use -the name of a function rather than a closure where we need something that -implements one of the `Fn` traits. For example, on an `Option>` value, -we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the -value is `None`. +> Note: If what we want to do doesn’t require capturing a value from the +> environment, we can use the name of a function rather than a closure. For +> example, we could call `unwrap_or_else(Vec::new)` on a `Option>` value +> to get a new, empty vector if the value is `None`. The compiler automatically +> implements whichever of the `Fn` traits is applicable for a function +> definition. -Now let’s look at the standard library method `sort_by_key`, defined on slices, +Now let’s look at the standard library method `sort_by_key` defined on slices, to see how that differs from `unwrap_or_else` and why `sort_by_key` uses `FnMut` instead of `FnOnce` for the trait bound. The closure gets one argument in the form of a reference to the current item in the slice being considered, and returns a value of type `K` that can be ordered. This function is useful when you want to sort a slice by a particular attribute of each item. In Listing 13-7, we have a list of `Rectangle` instances and we use `sort_by_key` -to order them by their `width` attribute from low to high. +to order them by their `width` attribute from low to high: -Filename: src/main.rs +src/main.rs ``` #[derive(Debug)] @@ -488,7 +526,7 @@ fn main() { ]; list.sort_by_key(|r| r.width); - println!("{:#?}", list); + println!("{list:#?}"); } ``` @@ -497,6 +535,10 @@ Listing 13-7: Using `sort_by_key` to order rectangles by width This code prints: ``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/rectangles` [ Rectangle { width: 3, @@ -514,18 +556,21 @@ This code prints: ``` The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls -the closure multiple times: once for each item in the slice. The closure `|r| -r.width` doesn’t capture, mutate, or move anything out from its environment, so +the closure multiple times: once for each item in the slice. The closure `|r| r.width` doesn’t capture, mutate, or move out anything from its environment, so it meets the trait bound requirements. In contrast, Listing 13-8 shows an example of a closure that implements just the `FnOnce` trait, because it moves a value out of the environment. The -compiler won’t let us use this closure with `sort_by_key`. +compiler won’t let us use this closure with `sort_by_key`: -Filename: src/main.rs +src/main.rs ``` ---snip-- +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} fn main() { let mut list = [ @@ -535,22 +580,22 @@ fn main() { ]; let mut sort_operations = vec![]; - let value = String::from("by key called"); + let value = String::from("closure called"); list.sort_by_key(|r| { sort_operations.push(value); r.width }); - println!("{:#?}", list); + println!("{list:#?}"); } ``` Listing 13-8: Attempting to use an `FnOnce` closure with `sort_by_key` This is a contrived, convoluted way (that doesn’t work) to try and count the -number of times `sort_by_key` gets called when sorting `list`. This code +number of times `sort_by_key` calls the closure when sorting `list`. This code attempts to do this counting by pushing `value`—a `String` from the closure’s -environment—into the `sort_operations` vector. The closure captures `value` and +environment—into the `sort_operations` vector. The closure captures `value` then moves `value` out of the closure by transferring ownership of `value` to the `sort_operations` vector. This closure can be called once; trying to call it a second time wouldn’t work because `value` would no longer be in the @@ -560,52 +605,63 @@ that `value` can’t be moved out of the closure because the closure must implement `FnMut`: ``` -error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` -closure +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) +error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure --> src/main.rs:18:30 | -15 | let value = String::from("by key called"); - | ----- captured outer variable +15 | let value = String::from("closure called"); + | ----- captured outer variable 16 | -17 | list.sort_by_key(|r| { - | ______________________- -18 | | sort_operations.push(value); - | | ^^^^^ move occurs because `value` has -type `String`, which does not implement the `Copy` trait -19 | | r.width -20 | | }); - | |_____- captured by this `FnMut` closure +17 | list.sort_by_key(|r| { + | --- captured by this `FnMut` closure +18 | sort_operations.push(value); + | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait + | +help: consider cloning the value if the performance cost is acceptable + | +18 | sort_operations.push(value.clone()); + | ++++++++ + +For more information about this error, try `rustc --explain E0507`. +error: could not compile `rectangles` (bin "rectangles") due to 1 previous error ``` The error points to the line in the closure body that moves `value` out of the environment. To fix this, we need to change the closure body so that it doesn’t -move values out of the environment. Keeping a counter in the environment and -incrementing its value in the closure body is a more straightforward way to -count the number of times `sort_by_key` is called. The closure in Listing 13-9 -works with `sort_by_key` because it is only capturing a mutable reference to -the `num_sort_operations` counter and can therefore be called more than once. +move values out of the environment. To count the number of times the closure +is called, keeping a counter in the environment and incrementing its value in +the closure body is a more straightforward way to calculate that. The closure +in Listing 13-9 works with `sort_by_key` because it is only capturing a mutable +reference to the `num_sort_operations` counter and can therefore be called more +than once: -Filename: src/main.rs +src/main.rs ``` ---snip-- +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} fn main() { - --snip-- + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; let mut num_sort_operations = 0; list.sort_by_key(|r| { num_sort_operations += 1; r.width }); - println!( - "{:#?}, sorted in {num_sort_operations} operations", - list - ); + println!("{list:#?}, sorted in {num_sort_operations} operations"); } ``` -Listing 13-9: Using an `FnMut` closure with `sort_by_key` is allowed. +Listing 13-9: Using an `FnMut` closure with `sort_by_key` is allowed The `Fn` traits are important when defining or using functions or types that make use of closures. In the next section, we’ll discuss iterators. Many @@ -625,33 +681,37 @@ Listing 13-10 creates an iterator over the items in the vector `v1` by calling the `iter` method defined on `Vec`. This code by itself doesn’t do anything useful. +src/main.rs + ``` -let v1 = vec![1, 2, 3]; + let v1 = vec![1, 2, 3]; -let v1_iter = v1.iter(); + let v1_iter = v1.iter(); ``` Listing 13-10: Creating an iterator The iterator is stored in the `v1_iter` variable. Once we’ve created an -iterator, we can use it in a variety of ways. In Listing 3-5, we iterated over -an array using a `for` loop to execute some code on each of its items. Under -the hood, this implicitly created and then consumed an iterator, but we glossed -over how exactly that works until now. +iterator, we can use it in a variety of ways. In Listing 3-5 in Chapter 3, we +iterated over an array using a `for` loop to execute some code on each of its +items. Under the hood this implicitly created and then consumed an iterator, +but we glossed over how exactly that works until now. In the example in Listing 13-11, we separate the creation of the iterator from the use of the iterator in the `for` loop. When the `for` loop is called using the iterator in `v1_iter`, each element in the iterator is used in one iteration of the loop, which prints out each value. +src/main.rs + ``` -let v1 = vec![1, 2, 3]; + let v1 = vec![1, 2, 3]; -let v1_iter = v1.iter(); + let v1_iter = v1.iter(); -for val in v1_iter { - println!("Got: {val}"); -} + for val in v1_iter { + println!("Got: {val}"); + } ``` Listing 13-11: Using an iterator in a `for` loop @@ -662,7 +722,7 @@ you would likely write this same functionality by starting a variable at index incrementing the variable value in a loop until it reached the total number of items in the vector. -Iterators handle all of that logic for you, cutting down on repetitive code you +Iterators handle all that logic for you, cutting down on repetitive code you could potentially mess up. Iterators give you more flexibility to use the same logic with many different kinds of sequences, not just data structures you can index into, like vectors. Let’s examine how iterators do that. @@ -682,36 +742,36 @@ pub trait Iterator { } ``` -Notice that this definition uses some new syntax: `type Item` and `Self::Item`, +Notice this definition uses some new syntax: `type Item` and `Self::Item`, which are defining an *associated type* with this trait. We’ll talk about -associated types in depth in Chapter 19. For now, all you need to know is that +associated types in depth in Chapter 20. For now, all you need to know is that this code says implementing the `Iterator` trait requires that you also define an `Item` type, and this `Item` type is used in the return type of the `next` method. In other words, the `Item` type will be the type returned from the iterator. The `Iterator` trait only requires implementors to define one method: the -`next` method, which returns one item of the iterator at a time, wrapped in -`Some`, and, when iteration is over, returns `None`. +`next` method, which returns one item of the iterator at a time wrapped in +`Some` and, when iteration is over, returns `None`. We can call the `next` method on iterators directly; Listing 13-12 demonstrates what values are returned from repeated calls to `next` on the iterator created from the vector. -Filename: src/lib.rs +src/lib.rs ``` -#[test] -fn iterator_demonstration() { - let v1 = vec![1, 2, 3]; + #[test] + fn iterator_demonstration() { + let v1 = vec![1, 2, 3]; - let mut v1_iter = v1.iter(); + let mut v1_iter = v1.iter(); - assert_eq!(v1_iter.next(), Some(&1)); - assert_eq!(v1_iter.next(), Some(&2)); - assert_eq!(v1_iter.next(), Some(&3)); - assert_eq!(v1_iter.next(), None); -} + assert_eq!(v1_iter.next(), Some(&1)); + assert_eq!(v1_iter.next(), Some(&2)); + assert_eq!(v1_iter.next(), Some(&3)); + assert_eq!(v1_iter.next(), None); + } ``` Listing 13-12: Calling the `next` method on an iterator @@ -730,7 +790,7 @@ ownership of `v1` and returns owned values, we can call `into_iter` instead of `iter`. Similarly, if we want to iterate over mutable references, we can call `iter_mut` instead of `iter`. -### Methods That Consume the Iterator +### Methods that Consume the Iterator The `Iterator` trait has a number of different methods with default implementations provided by the standard library; you can find out about these @@ -739,35 +799,34 @@ trait. Some of these methods call the `next` method in their definition, which is why you’re required to implement the `next` method when implementing the `Iterator` trait. -Methods that call `next` are called *consuming adapters* because calling them +Methods that call `next` are called *consuming adapters*, because calling them uses up the iterator. One example is the `sum` method, which takes ownership of the iterator and iterates through the items by repeatedly calling `next`, thus consuming the iterator. As it iterates through, it adds each item to a running total and returns the total when iteration is complete. Listing 13-13 has a -test illustrating a use of the `sum` method. +test illustrating a use of the `sum` method: -Filename: src/lib.rs +src/lib.rs ``` -#[test] -fn iterator_sum() { - let v1 = vec![1, 2, 3]; + #[test] + fn iterator_sum() { + let v1 = vec![1, 2, 3]; - let v1_iter = v1.iter(); + let v1_iter = v1.iter(); - let total: i32 = v1_iter.sum(); + let total: i32 = v1_iter.sum(); - assert_eq!(total, 6); -} + assert_eq!(total, 6); + } ``` -Listing 13-13: Calling the `sum` method to get the total of all items in the -iterator +Listing 13-13: Calling the `sum` method to get the total of all items in the iterator We aren’t allowed to use `v1_iter` after the call to `sum` because `sum` takes ownership of the iterator we call it on. -### Methods That Produce Other Iterators +### Methods that Produce Other Iterators *Iterator adapters* are methods defined on the `Iterator` trait that don’t consume the iterator. Instead, they produce different iterators by changing @@ -777,14 +836,14 @@ Listing 13-14 shows an example of calling the iterator adapter method `map`, which takes a closure to call on each item as the items are iterated through. The `map` method returns a new iterator that produces the modified items. The closure here creates a new iterator in which each item from the vector will be -incremented by 1. +incremented by 1: -Filename: src/main.rs +src/main.rs ``` -let v1: Vec = vec![1, 2, 3]; + let v1: Vec = vec![1, 2, 3]; -v1.iter().map(|x| x + 1); + v1.iter().map(|x| x + 1); ``` Listing 13-14: Calling the iterator adapter `map` to create a new iterator @@ -792,14 +851,24 @@ Listing 13-14: Calling the iterator adapter `map` to create a new iterator However, this code produces a warning: ``` +$ cargo run + Compiling iterators v0.1.0 (file:///projects/iterators) warning: unused `Map` that must be used --> src/main.rs:4:5 | 4 | v1.iter().map(|x| x + 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `#[warn(unused_must_use)]` on by default = note: iterators are lazy and do nothing unless consumed + = note: `#[warn(unused_must_use)]` on by default +help: use `let _ = ...` to ignore the resulting value + | +4 | let _ = v1.iter().map(|x| x + 1); + | +++++++ + +warning: `iterators` (bin "iterators") generated 1 warning + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s + Running `target/debug/iterators` ``` The code in Listing 13-14 doesn’t do anything; the closure we’ve specified @@ -807,25 +876,25 @@ never gets called. The warning reminds us why: iterator adapters are lazy, and we need to consume the iterator here. To fix this warning and consume the iterator, we’ll use the `collect` method, -which we used with `env::args` in Listing 12-1. This method consumes the -iterator and collects the resultant values into a collection data type. +which we used in Chapter 12 with `env::args` in Listing 12-1. This method +consumes the iterator and collects the resulting values into a collection data +type. -In Listing 13-15, we collect into a vector the results of iterating over the -iterator that’s returned from the call to `map`. This vector will end up -containing each item from the original vector, incremented by 1. +In Listing 13-15, we collect the results of iterating over the iterator that’s +returned from the call to `map` into a vector. This vector will end up +containing each item from the original vector incremented by 1. -Filename: src/main.rs +src/main.rs ``` -let v1: Vec = vec![1, 2, 3]; + let v1: Vec = vec![1, 2, 3]; -let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); -assert_eq!(v2, vec![2, 3, 4]); + assert_eq!(v2, vec![2, 3, 4]); ``` -Listing 13-15: Calling the `map` method to create a new iterator, and then -calling the `collect` method to consume the new iterator and create a vector +Listing 13-15: Calling the `map` method to create a new iterator and then calling the `collect` method to consume the new iterator and create a vector Because `map` takes a closure, we can specify any operation we want to perform on each item. This is a great example of how closures let you customize some @@ -836,7 +905,7 @@ You can chain multiple calls to iterator adapters to perform complex actions in a readable way. But because all iterators are lazy, you have to call one of the consuming adapter methods to get results from calls to iterator adapters. -### Using Closures That Capture Their Environment +### Using Closures that Capture Their Environment Many iterator adapters take closures as arguments, and commonly the closures we’ll specify as arguments to iterator adapters will be closures that capture @@ -851,7 +920,7 @@ In Listing 13-16, we use `filter` with a closure that captures the `shoe_size` variable from its environment to iterate over a collection of `Shoe` struct instances. It will return only shoes that are the specified size. -Filename: src/lib.rs +src/lib.rs ``` #[derive(PartialEq, Debug)] @@ -904,25 +973,24 @@ mod tests { } ``` -Listing 13-16: Using the `filter` method with a closure that captures -`shoe_size` +Listing 13-16: Using the `filter` method with a closure that captures `shoe_size` The `shoes_in_size` function takes ownership of a vector of shoes and a shoe size as parameters. It returns a vector containing only shoes of the specified size. -In the body of `shoes_in_size`, we call `into_iter` to create an iterator that -takes ownership of the vector. Then we call `filter` to adapt that iterator -into a new iterator that only contains elements for which the closure returns -`true`. +In the body of `shoes_in_size`, we call `into_iter` to create an iterator +that takes ownership of the vector. Then we call `filter` to adapt that +iterator into a new iterator that only contains elements for which the closure +returns `true`. The closure captures the `shoe_size` parameter from the environment and compares the value with each shoe’s size, keeping only shoes of the size specified. Finally, calling `collect` gathers the values returned by the adapted iterator into a vector that’s returned by the function. -The test shows that when we call `shoes_in_size`, we get back only shoes that -have the same size as the value we specified. +The test shows that when we call `shoes_in_size`, we get back only shoes +that have the same size as the value we specified. ## Improving Our I/O Project @@ -937,15 +1005,13 @@ In Listing 12-6, we added code that took a slice of `String` values and created an instance of the `Config` struct by indexing into the slice and cloning the values, allowing the `Config` struct to own those values. In Listing 13-17, we’ve reproduced the implementation of the `Config::build` function as it was -in Listing 12-23. +in Listing 12-23: -Filename: src/lib.rs +src/lib.rs ``` impl Config { - pub fn build( - args: &[String] - ) -> Result { + pub fn build(args: &[String]) -> Result { if args.len() < 3 { return Err("not enough arguments"); } @@ -972,7 +1038,7 @@ we would remove them in the future. Well, that time is now! We needed `clone` here because we have a slice with `String` elements in the parameter `args`, but the `build` function doesn’t own `args`. To return ownership of a `Config` instance, we had to clone the values from the `query` -and `filename` fields of `Config` so the `Config` instance can own its values. +and `file_path` fields of `Config` so the `Config` instance can own its values. With our new knowledge about iterators, we can change the `build` function to take ownership of an iterator as its argument instead of borrowing a slice. @@ -999,7 +1065,7 @@ fn main() { process::exit(1); }); - --snip-- + // --snip-- } ``` @@ -1007,17 +1073,16 @@ We’ll first change the start of the `main` function that we had in Listing 12-24 to the code in Listing 13-18, which this time uses an iterator. This won’t compile until we update `Config::build` as well. -Filename: src/main.rs +src/main.rs ``` fn main() { - let config = - Config::build(env::args()).unwrap_or_else(|err| { - eprintln!("Problem parsing arguments: {err}"); - process::exit(1); - }); + let config = Config::build(env::args()).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {err}"); + process::exit(1); + }); - --snip-- + // --snip-- } ``` @@ -1030,17 +1095,17 @@ we’re passing ownership of the iterator returned from `env::args` to Next, we need to update the definition of `Config::build`. In your I/O project’s *src/lib.rs* file, let’s change the signature of `Config::build` to -look like Listing 13-19. This still won’t compile, because we need to update -the function body. +look like Listing 13-19. This still won’t compile because we need to update the +function body. -Filename: src/lib.rs +src/lib.rs ``` impl Config { pub fn build( mut args: impl Iterator, ) -> Result { - --snip-- + // --snip-- ``` Listing 13-19: Updating the signature of `Config::build` to expect an iterator @@ -1052,8 +1117,9 @@ the `Iterator` trait and returns `String` values. We’ve updated the signature of the `Config::build` function so the parameter `args` has a generic type with the trait bounds `impl Iterator` instead of `&[String]`. This usage of the `impl Trait` syntax we discussed in -“Traits as Parameters” on page XX means that `args` can be any type that -implements the `Iterator` type and returns `String` items. +the “Traits as Parameters” section of Chapter 10 +means that `args` can be any type that implements the `Iterator` trait and +returns `String` items. Because we’re taking ownership of `args` and we’ll be mutating `args` by iterating over it, we can add the `mut` keyword into the specification of the @@ -1063,9 +1129,9 @@ iterating over it, we can add the `mut` keyword into the specification of the Next, we’ll fix the body of `Config::build`. Because `args` implements the `Iterator` trait, we know we can call the `next` method on it! Listing 13-20 -updates the code from Listing 12-23 to use the `next` method. +updates the code from Listing 12-23 to use the `next` method: -Filename: src/lib.rs +src/lib.rs ``` impl Config { @@ -1099,24 +1165,21 @@ Listing 13-20: Changing the body of `Config::build` to use iterator methods Remember that the first value in the return value of `env::args` is the name of the program. We want to ignore that and get to the next value, so first we call -`next` and do nothing with the return value. Then we call `next` to get the -value we want to put in the `query` field of `Config`. If `next` returns +`next` and do nothing with the return value. Second, we call `next` to get the +value we want to put in the `query` field of `Config`. If `next` returns a `Some`, we use a `match` to extract the value. If it returns `None`, it means not enough arguments were given and we return early with an `Err` value. We do -the same thing for the `filename` value. +the same thing for the `file_path` value. ### Making Code Clearer with Iterator Adapters We can also take advantage of iterators in the `search` function in our I/O -project, which is reproduced here in Listing 13-21 as it was in Listing 12-19. +project, which is reproduced here in Listing 13-21 as it was in Listing 12-19: -Filename: src/lib.rs +src/lib.rs ``` -pub fn search<'a>( - query: &str, - contents: &'a str, -) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { @@ -1135,16 +1198,13 @@ We can write this code in a more concise way using iterator adapter methods. Doing so also lets us avoid having a mutable intermediate `results` vector. The functional programming style prefers to minimize the amount of mutable state to make code clearer. Removing the mutable state might enable a future enhancement -to make searching happen in parallel because we wouldn’t have to manage -concurrent access to the `results` vector. Listing 13-22 shows this change. +to make searching happen in parallel, because we wouldn’t have to manage +concurrent access to the `results` vector. Listing 13-22 shows this change: -Filename: src/lib.rs +src/lib.rs ``` -pub fn search<'a>( - query: &str, - contents: &'a str, -) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents .lines() .filter(|line| line.contains(query)) @@ -1152,17 +1212,17 @@ pub fn search<'a>( } ``` -Listing 13-22: Using iterator adapter methods in the implementation of the -`search` function +Listing 13-22: Using iterator adapter methods in the implementation of the `search` function Recall that the purpose of the `search` function is to return all lines in `contents` that contain the `query`. Similar to the `filter` example in Listing -13-16, this code uses the `filter` adapter to keep only the lines for which -`line.contains(query)` returns `true`. We then collect the matching lines into -another vector with `collect`. Much simpler! Feel free to make the same change -to use iterator methods in the `search_case_insensitive` function as well. +13-16, this code uses the `filter` adapter to keep only the lines that +`line.contains(query)` returns `true` for. We then collect the matching lines +into another vector with `collect`. Much simpler! Feel free to make the same +change to use iterator methods in the `search_case_insensitive` function as +well. -### Choosing Between Loops and Iterators +### Choosing Between Loops or Iterators The next logical question is which style you should choose in your own code and why: the original implementation in Listing 13-21 or the version using @@ -1176,7 +1236,8 @@ that are unique to this code, such as the filtering condition each element in the iterator must pass. But are the two implementations truly equivalent? The intuitive assumption -might be that the lower-level loop will be faster. Let’s talk about performance. +might be that the more low-level loop will be faster. Let’s talk about +performance. ## Comparing Performance: Loops vs. Iterators @@ -1194,32 +1255,34 @@ test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700) test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) ``` -The iterator version was slightly faster! We won’t explain the benchmark code -here because the point is not to prove that the two versions are equivalent but -to get a general sense of how these two implementations compare -performance-wise. +The two implementations have similar performance! We won’t explain the +benchmark code here, because the point is not to prove that the two versions +are equivalent but to get a general sense of how these two implementations +compare performance-wise. For a more comprehensive benchmark, you should check using various texts of various sizes as the `contents`, different words and words of different lengths as the `query`, and all kinds of other variations. The point is this: iterators, although a high-level abstraction, get compiled down to roughly the same code as if you’d written the lower-level code yourself. Iterators are one -of Rust’s *zero-cost abstractions*, by which we mean that using the abstraction +of Rust’s *zero-cost abstractions*, by which we mean using the abstraction imposes no additional runtime overhead. This is analogous to how Bjarne Stroustrup, the original designer and implementor of C++, defines *zero-overhead* in “Foundations of C++” (2012): > In general, C++ implementations obey the zero-overhead principle: What you -don’t use, you don’t pay for. And further: What you do use, you couldn’t hand -code any better.As another example, the following code is taken from an audio -decoder. The decoding algorithm uses the linear prediction mathematical -operation to estimate future values based on a linear function of the previous -samples. This code uses an iterator chain to do some math on three variables in -scope: a `buffer` slice of data, an array of 12 `coefficients`, and an amount -by which to shift data in `qlp_shift`. We’ve declared the variables within this -example but not given them any values; although this code doesn’t have much -meaning outside of its context, it’s still a concise, real-world example of how -Rust translates high-level ideas to low-level code. +> don’t use, you don’t pay for. And further: What you do use, you couldn’t hand +> code any better. + +As another example, the following code is taken from an audio decoder. The +decoding algorithm uses the linear prediction mathematical operation to +estimate future values based on a linear function of the previous samples. This +code uses an iterator chain to do some math on three variables in scope: a +`buffer` slice of data, an array of 12 `coefficients`, and an amount by which +to shift data in `qlp_shift`. We’ve declared the variables within this example +but not given them any values; although this code doesn’t have much meaning +outside of its context, it’s still a concise, real-world example of how Rust +translates high-level ideas to low-level code. ``` let buffer: &mut [i32]; @@ -1238,9 +1301,9 @@ for i in 12..buffer.len() { To calculate the value of `prediction`, this code iterates through each of the 12 values in `coefficients` and uses the `zip` method to pair the coefficient -values with the previous 12 values in `buffer`. Then, for each pair, it -multiplies the values together, sums all the results, and shifts the bits in -the sum `qlp_shift` bits to the right. +values with the previous 12 values in `buffer`. Then, for each pair, we +multiply the values together, sum all the results, and shift the bits in the +sum `qlp_shift` bits to the right. Calculations in applications like audio decoders often prioritize performance most highly. Here, we’re creating an iterator, using two adapters, and then @@ -1254,7 +1317,7 @@ the loop. All of the coefficients get stored in registers, which means accessing the values is very fast. There are no bounds checks on the array access at runtime. -All of these optimizations that Rust is able to apply make the resultant code +All these optimizations that Rust is able to apply make the resulting code extremely efficient. Now that you know this, you can use iterators and closures without fear! They make code seem like it’s higher level but don’t impose a runtime performance penalty for doing so. @@ -1270,4 +1333,3 @@ Rust’s goal to strive to provide zero-cost abstractions. Now that we’ve improved the expressiveness of our I/O project, let’s look at some more features of `cargo` that will help us share the project with the world. - diff --git a/nostarch/chapter14.md b/nostarch/chapter14.md index f5f2be7196..6d263aa1cb 100644 --- a/nostarch/chapter14.md +++ b/nostarch/chapter14.md @@ -8,19 +8,18 @@ directory, so all fixes need to be made in `/src/`. # More About Cargo and Crates.io -So far, we’ve used only the most basic features of Cargo to build, run, and -test our code, but it can do a lot more. In this chapter, we’ll discuss some of -its other, more advanced features to show you how to do the following: +So far we’ve used only the most basic features of Cargo to build, run, and test +our code, but it can do a lot more. In this chapter, we’ll discuss some of its +other, more advanced features to show you how to do the following: -* Customize your build through release profiles. -* Publish libraries on *https://crates.i**o*. -* Organize large projects with workspaces. -* Install binaries from *https://crates.io*. -* Extend Cargo using custom commands. +* Customize your build through release profiles +* Publish libraries on crates.io +* Organize large projects with workspaces +* Install binaries from crates.io +* Extend Cargo using custom commands Cargo can do even more than the functionality we cover in this chapter, so for -a full explanation of all its features, see its documentation at -*https://doc.rust-lang.org/cargo*. +a full explanation of all its features, see its documentation at *https://doc.rust-lang.org/cargo/*. ## Customizing Builds with Release Profiles @@ -29,18 +28,23 @@ different configurations that allow a programmer to have more control over various options for compiling code. Each profile is configured independently of the others. -Cargo has two main profiles: the `dev` profile Cargo uses when you run `cargo -build`, and the `release` profile Cargo uses when you run `cargo build ---release`. The `dev` profile is defined with good defaults for development, +Cargo has two main profiles: the `dev` profile Cargo uses when you run `cargo build` and the `release` profile Cargo uses when you run `cargo build --release`. The `dev` profile is defined with good defaults for development, and the `release` profile has good defaults for release builds. These profile names might be familiar from the output of your builds: + + ``` $ cargo build - Finished dev [unoptimized + debuginfo] target(s) in 0.0s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s $ cargo build --release - Finished release [optimized] target(s) in 0.0s + Finished `release` profile [optimized] target(s) in 0.32s ``` The `dev` and `release` are these different profiles used by the compiler. @@ -64,7 +68,7 @@ opt-level = 3 The `opt-level` setting controls the number of optimizations Rust will apply to your code, with a range of 0 to 3. Applying more optimizations extends compiling time, so if you’re in development and compiling your code often, -you’ll want fewer optimizations to compile faster even if the resultant code +you’ll want fewer optimizations to compile faster even if the resulting code runs slower. The default `opt-level` for `dev` is therefore `0`. When you’re ready to release your code, it’s best to spend more time compiling. You’ll only compile in release mode once, but you’ll run the compiled program many times, @@ -89,15 +93,15 @@ Cargo will use the defaults for the `dev` profile plus our customization to optimizations than the default, but not as many as in a release build. For the full list of configuration options and defaults for each profile, see -Cargo’s documentation at -*https://doc.rust-lang.org/cargo/reference/profiles.html*. +Cargo’s documentation at *https://doc.rust-lang.org/cargo/reference/profiles.html*. ## Publishing a Crate to Crates.io -We’ve used packages from *https://crates.io* as dependencies of our project, -but you can also share your code with other people by publishing your own -packages. The crate registry at *https://crates.io* distributes the source code -of your packages, so it primarily hosts code that is open source. +We’ve used packages from crates.io as +dependencies of our project, but you can also share your code with other people +by publishing your own packages. The crate registry at +crates.io distributes the source code of +your packages, so it primarily hosts code that is open source. Rust and Cargo have features that make your published package easier for people to find and use. We’ll talk about some of these features next and then explain @@ -119,7 +123,7 @@ Markdown notation for formatting the text. Place documentation comments just before the item they’re documenting. Listing 14-1 shows documentation comments for an `add_one` function in a crate named `my_crate`. -Filename: src/lib.rs +src/lib.rs ``` /// Adds one to the number given. @@ -150,9 +154,12 @@ For convenience, running `cargo doc --open` will build the HTML for your current crate’s documentation (as well as the documentation for all of your crate’s dependencies) and open the result in a web browser. Navigate to the `add_one` function and you’ll see how the text in the documentation comments is -rendered, as shown in Figure 14-1. +rendered, as shown in Figure 14-1: -Figure 14-1: HTML documentation for the `add_one` function +Rendered HTML documentation for the `add_one` function of `my_crate` + +Figure 14-1: HTML documentation for the `add_one` +function #### Commonly Used Sections @@ -160,16 +167,16 @@ We used the `# Examples` Markdown heading in Listing 14-1 to create a section in the HTML with the title “Examples.” Here are some other sections that crate authors commonly use in their documentation: -* **Panics**: The scenarios in which the function being documented could panic. -Callers of the function who don’t want their programs to panic should make sure -they don’t call the function in these situations. +* **Panics**: The scenarios in which the function being documented could + panic. Callers of the function who don’t want their programs to panic should + make sure they don’t call the function in these situations. * **Errors**: If the function returns a `Result`, describing the kinds of -errors that might occur and what conditions might cause those errors to be -returned can be helpful to callers so they can write code to handle the -different kinds of errors in different ways. + errors that might occur and what conditions might cause those errors to be + returned can be helpful to callers so they can write code to handle the + different kinds of errors in different ways. * **Safety**: If the function is `unsafe` to call (we discuss unsafety in -Chapter 19), there should be a section explaining why the function is unsafe -and covering the invariants that the function expects callers to uphold. + Chapter 20), there should be a section explaining why the function is unsafe + and covering the invariants that the function expects callers to uphold. Most documentation comments don’t need all of these sections, but this is a good checklist to remind you of the aspects of your code users will be @@ -178,13 +185,17 @@ interested in knowing about. #### Documentation Comments as Tests Adding example code blocks in your documentation comments can help demonstrate -how to use your library, and doing so has an additional bonus: running `cargo -test` will run the code examples in your documentation as tests! Nothing is +how to use your library, and doing so has an additional bonus: running `cargo test` will run the code examples in your documentation as tests! Nothing is better than documentation with examples. But nothing is worse than examples that don’t work because the code has changed since the documentation was written. If we run `cargo test` with the documentation for the `add_one` -function from Listing 14-1, we will see a section in the test results that -looks like this: +function from Listing 14-1, we will see a section in the test results like this: + + ``` Doc-tests my_crate @@ -192,36 +203,35 @@ looks like this: running 1 test test src/lib.rs - add_one (line 5) ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 -filtered out; finished in 0.27s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s ``` -Now, if we change either the function or the example so the `assert_eq!` in the +Now if we change either the function or the example so the `assert_eq!` in the example panics and run `cargo test` again, we’ll see that the doc tests catch that the example and the code are out of sync with each other! #### Commenting Contained Items -The doc comment `//!` adds documentation to the item that *contains* the -comments rather than to the items *following* the comments. We typically use +The style of doc comment `//!` adds documentation to the item that contains the +comments rather than to the items following the comments. We typically use these doc comments inside the crate root file (*src/lib.rs* by convention) or inside a module to document the crate or the module as a whole. For example, to add documentation that describes the purpose of the `my_crate` crate that contains the `add_one` function, we add documentation comments that start with `//!` to the beginning of the *src/lib.rs* file, as shown in Listing -14-2. +14-2: -Filename: src/lib.rs +src/lib.rs ``` //! # My Crate //! -//! `my_crate` is a collection of utilities to make performing -//! certain calculations more convenient. +//! `my_crate` is a collection of utilities to make performing certain +//! calculations more convenient. /// Adds one to the number given. ---snip-- +// --snip-- ``` Listing 14-2: Documentation for the `my_crate` crate as a whole @@ -232,12 +242,14 @@ that contains this comment rather than an item that follows this comment. In this case, that item is the *src/lib.rs* file, which is the crate root. These comments describe the entire crate. -When we run `cargo doc --open`, these comments will display on the front page -of the documentation for `my_crate` above the list of public items in the -crate, as shown in Figure 14-2. +When we run `cargo doc --open`, these comments will display on the front +page of the documentation for `my_crate` above the list of public items in the +crate, as shown in Figure 14-2: + +Rendered HTML documentation with a comment for the crate as a whole -Figure 14-2: Rendered documentation for `my_crate`, including the comment -describing the crate as a whole +Figure 14-2: Rendered documentation for `my_crate`, +including the comment describing the crate as a whole Documentation comments within items are useful for describing crates and modules especially. Use them to explain the overall purpose of the container to @@ -251,28 +263,28 @@ are and might have difficulty finding the pieces they want to use if your crate has a large module hierarchy. In Chapter 7, we covered how to make items public using the `pub` keyword, and -how to bring items into a scope with the `use` keyword. However, the structure -that makes sense to you while you’re developing a crate might not be very -convenient for your users. You might want to organize your structs in a -hierarchy containing multiple levels, but then people who want to use a type -you’ve defined deep in the hierarchy might have trouble finding out that type -exists. They might also be annoyed at having to enter `use` -`my_crate::`some_module`::`another_module`::`UsefulType`;` rather than `use` -`my_crate::`UsefulType`;`. +bring items into a scope with the `use` keyword. However, the structure that +makes sense to you while you’re developing a crate might not be very convenient +for your users. You might want to organize your structs in a hierarchy +containing multiple levels, but then people who want to use a type you’ve +defined deep in the hierarchy might have trouble finding out that type exists. +They might also be annoyed at having to enter `use` +`my_crate::some_module::another_module::UsefulType;` rather than `use` +`my_crate::UsefulType;`. The good news is that if the structure *isn’t* convenient for others to use from another library, you don’t have to rearrange your internal organization: instead, you can re-export items to make a public structure that’s different -from your private structure by using `pub use`. *Re-exporting* takes a public +from your private structure by using `pub use`. Re-exporting takes a public item in one location and makes it public in another location, as if it were defined in the other location instead. For example, say we made a library named `art` for modeling artistic concepts. Within this library are two modules: a `kinds` module containing two enums named `PrimaryColor` and `SecondaryColor` and a `utils` module containing a -function named `mix`, as shown in Listing 14-3. +function named `mix`, as shown in Listing 14-3: -Filename: src/lib.rs +src/lib.rs ``` //! # Art @@ -300,23 +312,21 @@ pub mod utils { /// Combines two primary colors in equal amounts to create /// a secondary color. - pub fn mix( - c1: PrimaryColor, - c2: PrimaryColor, - ) -> SecondaryColor { - --snip-- + pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { + // --snip-- } } ``` -Listing 14-3: An `art` library with items organized into `kinds` and `utils` -modules +Listing 14-3: An `art` library with items organized into `kinds` and `utils` modules Figure 14-3 shows what the front page of the documentation for this crate -generated by `cargo doc` would look like. +generated by `cargo doc` would look like: -Figure 14-3: Front page of the documentation for `art` that lists the `kinds` -and `utils` modules +Rendered documentation for the `art` crate that lists the `kinds` and `utils` modules + +Figure 14-3: Front page of the documentation for `art` +that lists the `kinds` and `utils` modules Note that the `PrimaryColor` and `SecondaryColor` types aren’t listed on the front page, nor is the `mix` function. We have to click `kinds` and `utils` to @@ -325,9 +335,9 @@ see them. Another crate that depends on this library would need `use` statements that bring the items from `art` into scope, specifying the module structure that’s currently defined. Listing 14-4 shows an example of a crate that uses the -`PrimaryColor` and `mix` items from the `art` crate. +`PrimaryColor` and `mix` items from the `art` crate: -Filename: src/main.rs +src/main.rs ``` use art::kinds::PrimaryColor; @@ -340,8 +350,7 @@ fn main() { } ``` -Listing 14-4: A crate using the `art` crate’s items with its internal structure -exported +Listing 14-4: A crate using the `art` crate’s items with its internal structure exported The author of the code in Listing 14-4, which uses the `art` crate, had to figure out that `PrimaryColor` is in the `kinds` module and `mix` is in the @@ -354,9 +363,9 @@ module names in the `use` statements. To remove the internal organization from the public API, we can modify the `art` crate code in Listing 14-3 to add `pub use` statements to re-export the -items at the top level, as shown in Listing 14-5. +items at the top level, as shown in Listing 14-5: -Filename: src/lib.rs +src/lib.rs ``` //! # Art @@ -368,11 +377,11 @@ pub use self::kinds::SecondaryColor; pub use self::utils::mix; pub mod kinds { - --snip-- + // --snip-- } pub mod utils { - --snip-- + // --snip-- } ``` @@ -382,21 +391,23 @@ The API documentation that `cargo doc` generates for this crate will now list and link re-exports on the front page, as shown in Figure 14-4, making the `PrimaryColor` and `SecondaryColor` types and the `mix` function easier to find. -Figure 14-4: The front page of the documentation for `art` that lists the -re-exports +Rendered documentation for the `art` crate with the re-exports on the front page + +Figure 14-4: The front page of the documentation for `art` +that lists the re-exports The `art` crate users can still see and use the internal structure from Listing 14-3 as demonstrated in Listing 14-4, or they can use the more convenient -structure in Listing 14-5, as shown in Listing 14-6. +structure in Listing 14-5, as shown in Listing 14-6: -Filename: src/main.rs +src/main.rs ``` use art::mix; use art::PrimaryColor; fn main() { - --snip-- + // --snip-- } ``` @@ -409,8 +420,7 @@ definitions of a dependency in the current crate to make that crate’s definitions part of your crate’s public API. Creating a useful public API structure is more of an art than a science, and -you can iterate to find the API that works best for your users. Choosing `pub -use` gives you flexibility in how you structure your crate internally and +you can iterate to find the API that works best for your users. Choosing `pub use` gives you flexibility in how you structure your crate internally and decouples that internal structure from what you present to your users. Look at some of the code of crates you’ve installed to see if their internal structure differs from their public API. @@ -418,21 +428,23 @@ differs from their public API. ### Setting Up a Crates.io Account Before you can publish any crates, you need to create an account on -*https://crates.io* and get an API token. To do so, visit the home page at -*https://crates.io* and log in via a GitHub account. (The GitHub account is -currently a requirement, but the site might support other ways of creating an -account in the future.) Once you’re logged in, visit your account settings at -*https://crates.io/me* and retrieve your API key. Then run the `cargo login` -command with your API key, like this: +crates.io and get an API token. To do so, +visit the home page at crates.io and log +in via a GitHub account. (The GitHub account is currently a requirement, but +the site might support other ways of creating an account in the future.) Once +you’re logged in, visit your account settings at +https://crates.io/me/ and retrieve your +API key. Then run the `cargo login` command and paste your API key when prompted, like this: ``` -$ cargo login abcdefghijklmnopqrstuvwxyz012345 +$ cargo login +abcdefghijklmnopqrstuvwxyz012345 ``` This command will inform Cargo of your API token and store it locally in *~/.cargo/credentials*. Note that this token is a *secret*: do not share it with anyone else. If you do share it with anyone for any reason, you should -revoke it and generate a new token on *https://crates.io*. +revoke it and generate a new token on crates.io. ### Adding Metadata to a New Crate @@ -442,12 +454,12 @@ file. Your crate will need a unique name. While you’re working on a crate locally, you can name a crate whatever you’d like. However, crate names on -*https://crates.io* are allocated on a first-come, first-served basis. Once a -crate name is taken, no one else can publish a crate with that name. Before -attempting to publish a crate, search for the name you want to use. If the name -has been used, you will need to find another name and edit the `name` field in -the *Cargo.toml* file under the `[package]` section to use the new name for -publishing, like so: +crates.io are allocated on a first-come, +first-served basis. Once a crate name is taken, no one else can publish a crate +with that name. Before attempting to publish a crate, search for the name you +want to use. If the name has been used, you will need to find another name and +edit the `name` field in the *Cargo.toml* file under the `[package]` section to +use the new name for publishing, like so: Filename: Cargo.toml @@ -459,31 +471,32 @@ name = "guessing_game" Even if you’ve chosen a unique name, when you run `cargo publish` to publish the crate at this point, you’ll get a warning and then an error: + + ``` $ cargo publish Updating crates.io index -warning: manifest has no description, license, license-file, documentation, -homepage or repository. -See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata -for more info. +warning: manifest has no description, license, license-file, documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. --snip-- error: failed to publish to registry at https://crates.io Caused by: - the remote server responded with an error: missing or empty metadata fields: -description, license. Please see https://doc.rust- -lang.org/cargo/reference/manifest.html for how to upload metadata + the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these field ``` -This results in an error because you’re missing some crucial information: a -description and license are required so people will know what your crate does -and under what terms they can use it. In *Cargo.toml*, add a description that’s -just a sentence or two, because it will appear with your crate in search -results. For the `license` field, you need to give a *license identifier -value*. The Linux Foundation’s Software Package Data Exchange (SPDX) at -*http://spdx.org/licenses* lists the identifiers you can use for this value. -For example, to specify that you’ve licensed your crate using the MIT License, -add the `MIT` identifier: +This errors because you’re missing some crucial information: a description and +license are required so people will know what your crate does and under what +terms they can use it. In *Cargo.toml*, add a description that’s just a +sentence or two, because it will appear with your crate in search results. For +the `license` field, you need to give a *license identifier value*. The Linux +Foundation’s Software Package Data Exchange (SPDX) at *http://spdx.org/licenses/* lists the identifiers +you can use for this value. For example, to specify that you’ve licensed your +crate using the MIT License, add the `MIT` identifier: Filename: Cargo.toml @@ -514,33 +527,39 @@ Filename: Cargo.toml name = "guessing_game" version = "0.1.0" edition = "2021" -description = "A fun game where you guess what number the -computer has chosen." +description = "A fun game where you guess what number the computer has chosen." license = "MIT OR Apache-2.0" [dependencies] ``` -Cargo’s documentation at *https://doc.rust-lang.org/cargo* describes other -metadata you can specify to ensure that others can discover and use your crate -more easily. +Cargo’s documentation at *https://doc.rust-lang.org/cargo/* describes other +metadata you can specify to ensure others can discover and use your crate more +easily. ### Publishing to Crates.io Now that you’ve created an account, saved your API token, chosen a name for your crate, and specified the required metadata, you’re ready to publish! -Publishing a crate uploads a specific version to *https://crates.io* for others -to use. +Publishing a crate uploads a specific version to +crates.io for others to use. Be careful, because a publish is *permanent*. The version can never be -overwritten, and the code cannot be deleted. One major goal of Crates.io is to -act as a permanent archive of code so that builds of all projects that depend -on crates from *https://crates.io* will continue to work. Allowing version -deletions would make fulfilling that goal impossible. However, there is no -limit to the number of crate versions you can publish. +overwritten, and the code cannot be deleted. One major goal of +crates.io is to act as a permanent archive +of code so that builds of all projects that depend on crates from +crates.io will continue to work. Allowing +version deletions would make fulfilling that goal impossible. However, there is +no limit to the number of crate versions you can publish. Run the `cargo publish` command again. It should succeed now: + + ``` $ cargo publish Updating crates.io index @@ -548,7 +567,7 @@ $ cargo publish Verifying guessing_game v0.1.0 (file:///projects/guessing_game) Compiling guessing_game v0.1.0 (file:///projects/guessing_game/target/package/guessing_game-0.1.0) - Finished dev [unoptimized + debuginfo] target(s) in 0.19s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s Uploading guessing_game v0.1.0 (file:///projects/guessing_game) ``` @@ -559,18 +578,22 @@ anyone can easily add your crate as a dependency of their project. When you’ve made changes to your crate and are ready to release a new version, you change the `version` value specified in your *Cargo.toml* file and -republish. Use the Semantic Versioning rules at *http://semver.org* to decide -what an appropriate next version number is, based on the kinds of changes -you’ve made. Then run `cargo publish` to upload the new version. +republish. Use the Semantic Versioning rules at *http://semver.org/* to decide what an +appropriate next version number is based on the kinds of changes you’ve made. +Then run `cargo publish` to upload the new version. + + + + ### Deprecating Versions from Crates.io with cargo yank Although you can’t remove previous versions of a crate, you can prevent any future projects from adding them as a new dependency. This is useful when a crate version is broken for one reason or another. In such situations, Cargo -supports yanking a crate version. +supports *yanking* a crate version. -*Yanking* a version prevents new projects from depending on that version while +Yanking a version prevents new projects from depending on that version while allowing all existing projects that depend on it to continue. Essentially, a yank means that all projects with a *Cargo.lock* will not break, and any future *Cargo.lock* files generated will not use the yanked version. @@ -581,6 +604,11 @@ yank. For example, if we’ve published a crate named `guessing_game` version 1.0.1 and we want to yank it, in the project directory for `guessing_game` we’d run: + + ``` $ cargo yank --vers 1.0.1 Updating crates.io index @@ -615,7 +643,7 @@ can concentrate on the structure of the workspace. There are multiple ways to structure a workspace, so we’ll just show one common way. We’ll have a workspace containing a binary and two libraries. The binary, which will provide the main functionality, will depend on the two libraries. One library will -provide an `add_one` function and the other library an `add_two` function. +provide an `add_one` function, and a second library an `add_two` function. These three crates will be part of the same workspace. We’ll start by creating a new directory for the workspace: @@ -627,25 +655,41 @@ $ cd add Next, in the *add* directory, we create the *Cargo.toml* file that will configure the entire workspace. This file won’t have a `[package]` section. Instead, it will start with a `[workspace]` section that will allow us to add -members to the workspace by specifying the path to the package with our binary -crate; in this case, that path is *adder*: +members to the workspace. We also make a point to use the latest and greatest +version of Cargo’s resolver algorithm in our workspace by setting the +`resolver` to `"2"`. Filename: Cargo.toml ``` [workspace] - -members = [ - "adder", -] +resolver = "2" ``` Next, we’ll create the `adder` binary crate by running `cargo new` within the *add* directory: + + ``` $ cargo new adder - Created binary (application) `adder` package + Creating binary (application) `adder` package + Adding `adder` as member of workspace at `file:///projects/add` +``` + +Running `cargo new` inside a workspace also automatically adds the newly created +package to the `members` key in the `[workspace]` definition in the workspace +`Cargo.toml`, like this: + +``` +[workspace] +resolver = "2" +members = ["adder"] ``` At this point, we can build the workspace by running `cargo build`. The files @@ -682,18 +726,23 @@ Filename: Cargo.toml ``` [workspace] - -members = [ - "adder", - "add_one", -] +resolver = "2" +members = ["adder", "add_one"] ``` Then generate a new library crate named `add_one`: + + ``` $ cargo new add_one --lib - Created library `add_one` package + Creating library `add_one` package + Adding `add_one` as member of workspace at `file:///projects/add` ``` Your *add* directory should now have these directories and files: @@ -723,8 +772,8 @@ pub fn add_one(x: i32) -> i32 { ``` Now we can have the `adder` package with our binary depend on the `add_one` -package that has our library. First we’ll need to add a path dependency on -`add_one` to *adder/Cargo.toml*: +package that has our library. First, we’ll need to add a path dependency on +`add_one` to *adder/Cargo.toml*. Filename: adder/Cargo.toml @@ -737,43 +786,49 @@ Cargo doesn’t assume that crates in a workspace will depend on each other, so we need to be explicit about the dependency relationships. Next, let’s use the `add_one` function (from the `add_one` crate) in the -`adder` crate. Open the *adder/src/main.rs* file and add a `use` line at the -top to bring the new `add_one` library crate into scope. Then change the `main` +`adder` crate. Open the *adder/src/main.rs* file and change the `main` function to call the `add_one` function, as in Listing 14-7. -Filename: adder/src/main.rs +adder/src/main.rs ``` -use add_one; - fn main() { let num = 10; - println!( - "Hello, world! {num} plus one is {}!", - add_one::add_one(num) - ); + println!("Hello, world! {num} plus one is {}!", add_one::add_one(num)); } ``` -Listing 14-7: Using the `add_one` library crate from the `adder` crate +Listing 14-7: Using the `add_one` library crate in the `adder` crate Let’s build the workspace by running `cargo build` in the top-level *add* directory! + + ``` $ cargo build Compiling add_one v0.1.0 (file:///projects/add/add_one) Compiling adder v0.1.0 (file:///projects/add/adder) - Finished dev [unoptimized + debuginfo] target(s) in 0.68s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s ``` -To run the binary crate from the *add* directory, we can specify which package -in the workspace we want to run by using the `-p` argument and the package name -with `cargo run`: +To run the binary crate from the *add* directory, we can specify which +package in the workspace we want to run by using the `-p` argument and the +package name with `cargo run`: + + ``` $ cargo run -p adder - Finished dev [unoptimized + debuginfo] target(s) in 0.0s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s Running `target/debug/adder` Hello, world! 10 plus one is 11! ``` @@ -792,6 +847,12 @@ means the crates will always be compatible with each other. Let’s add the `rand` crate to the `[dependencies]` section in the *add_one/Cargo.toml* file so we can use the `rand` crate in the `add_one` crate: + + Filename: add_one/Cargo.toml ``` @@ -804,6 +865,12 @@ whole workspace by running `cargo build` in the *add* directory will bring in and compile the `rand` crate. We will get one warning because we aren’t referring to the `rand` we brought into scope: + + ``` $ cargo build Updating crates.io index @@ -811,8 +878,17 @@ $ cargo build --snip-- Compiling rand v0.8.5 Compiling add_one v0.1.0 (file:///projects/add/add_one) +warning: unused import: `rand` + --> add_one/src/lib.rs:1:5 + | +1 | use rand; + | ^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion) Compiling adder v0.1.0 (file:///projects/add/adder) - Finished dev [unoptimized + debuginfo] target(s) in 10.18s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s ``` The top-level *Cargo.lock* now contains information about the dependency of @@ -821,9 +897,15 @@ workspace, we can’t use it in other crates in the workspace unless we add `rand` to their *Cargo.toml* files as well. For example, if we add `use rand;` to the *adder/src/main.rs* file for the `adder` package, we’ll get an error: + + ``` $ cargo build - --snip-- + --snip-- Compiling adder v0.1.0 (file:///projects/add/adder) error[E0432]: unresolved import `rand` --> adder/src/main.rs:2:5 @@ -835,10 +917,15 @@ error[E0432]: unresolved import `rand` To fix this, edit the *Cargo.toml* file for the `adder` package and indicate that `rand` is a dependency for it as well. Building the `adder` package will add `rand` to the list of dependencies for `adder` in *Cargo.lock*, but no -additional copies of `rand` will be downloaded. Cargo has ensured that every +additional copies of `rand` will be downloaded. Cargo will ensure that every crate in every package in the workspace using the `rand` package will be using -the same version, saving us space and ensuring that the crates in the workspace -will be compatible with each other. +the same version as long as they specify compatible versions of `rand`, saving +us space and ensuring that the crates in the workspace will be compatible with +each other. + +If crates in the workspace specify incompatible versions of the same dependency, +Cargo will resolve each of them, but will still try to resolve as few versions +as possible. #### Adding a Test to a Workspace @@ -867,32 +954,36 @@ Now run `cargo test` in the top-level *add* directory. Running `cargo test` in a workspace structured like this one will run the tests for all the crates in the workspace: + + ``` $ cargo test Compiling add_one v0.1.0 (file:///projects/add/add_one) Compiling adder v0.1.0 (file:///projects/add/adder) - Finished test [unoptimized + debuginfo] target(s) in 0.27s + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841) running 1 test test tests::it_works ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; -finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running unittests src/main.rs (target/debug/deps/adder-49979ff40686fa8e) running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; -finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests add_one running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; -finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` The first section of the output shows that the `it_works` test in the `add_one` @@ -904,72 +995,83 @@ We can also run tests for one particular crate in a workspace from the top-level directory by using the `-p` flag and specifying the name of the crate we want to test: + + ``` $ cargo test -p add_one - Finished test [unoptimized + debuginfo] target(s) in 0.00s + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74) running 1 test test tests::it_works ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; -finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests add_one running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; -finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` This output shows `cargo test` only ran the tests for the `add_one` crate and didn’t run the `adder` crate tests. -If you publish the crates in the workspace to *https://crates.io*, each crate -in the workspace will need to be published separately. Like `cargo test`, we -can publish a particular crate in our workspace by using the `-p` flag and -specifying the name of the crate we want to publish. +If you publish the crates in the workspace to crates.io at *https://crates.io/*, +each crate in the workspace will need to be published separately. Like `cargo test`, we can publish a particular crate in our workspace by using the `-p` +flag and specifying the name of the crate we want to publish. For additional practice, add an `add_two` crate to this workspace in a similar way as the `add_one` crate! -As your project grows, consider using a workspace: it provides -easier-to-understand, smaller, individual components than one big blob of code. -Furthermore, keeping the crates in a workspace can make coordination between -crates easier if they are often changed at the same time. +As your project grows, consider using a workspace: it’s easier to understand +smaller, individual components than one big blob of code. Furthermore, keeping +the crates in a workspace can make coordination between crates easier if they +are often changed at the same time. + + + + ## Installing Binaries with cargo install The `cargo install` command allows you to install and use binary crates locally. This isn’t intended to replace system packages; it’s meant to be a convenient way for Rust developers to install tools that others have shared on -*https://crates.io*. Note that you can only install packages that have binary -targets. A *binary target* is the runnable program that is created if the crate -has a *src/main.rs* file or another file specified as a binary, as opposed to a -library target that isn’t runnable on its own but is suitable for including -within other programs. Usually, crates have information in the *README* file -about whether a crate is a library, has a binary target, or both. +crates.io. Note that you can only install +packages that have binary targets. A *binary target* is the runnable program +that is created if the crate has a *src/main.rs* file or another file specified +as a binary, as opposed to a library target that isn’t runnable on its own but +is suitable for including within other programs. Usually, crates have +information in the *README* file about whether a crate is a library, has a +binary target, or both. All binaries installed with `cargo install` are stored in the installation root’s *bin* folder. If you installed Rust using *rustup.rs* and don’t have any custom configurations, this directory will be *$HOME/.cargo/bin*. Ensure that -directory is in your `$PATH` to be able to run programs you’ve installed with -`cargo install`. +directory is in your `$PATH`to be able to run programs you’ve installed with`cargo install`. For example, in Chapter 12 we mentioned that there’s a Rust implementation of the `grep` tool called `ripgrep` for searching files. To install `ripgrep`, we can run the following: + + ``` $ cargo install ripgrep Updating crates.io index Downloaded ripgrep v13.0.0 Downloaded 1 crate (243.3 KB) in 0.88s Installing ripgrep v13.0.0 - --snip-- +--snip-- Compiling ripgrep v13.0.0 - Finished release [optimized + debuginfo] target(s) in 3m 10s + Finished `release` profile [optimized + debuginfo] target(s) in 10.64s Installing ~/.cargo/bin/rg Installed package `ripgrep v13.0.0` (executable `rg`) ``` @@ -977,23 +1079,21 @@ $ cargo install ripgrep The second-to-last line of the output shows the location and the name of the installed binary, which in the case of `ripgrep` is `rg`. As long as the installation directory is in your `$PATH`, as mentioned previously, you can -then run `rg --help` and start using a faster, Rustier tool for searching files! +then run `rg --help` and start using a faster, rustier tool for searching files! ## Extending Cargo with Custom Commands Cargo is designed so you can extend it with new subcommands without having to -modify it. If a binary in your `$PATH` is named `cargo-something`, you can run -it as if it were a Cargo subcommand by running `cargo something`. Custom +modify Cargo. If a binary in your `$PATH` is named `cargo-something`, you can +run it as if it was a Cargo subcommand by running `cargo something`. Custom commands like this are also listed when you run `cargo --list`. Being able to use `cargo install` to install extensions and then run them just like the -built-in Cargo tools is a super-convenient benefit of Cargo’s design! +built-in Cargo tools is a super convenient benefit of Cargo’s design! ## Summary -Sharing code with Cargo and *https://crates.io* is part of what makes the Rust -ecosystem useful for many different tasks. Rust’s standard library is small and -stable, but crates are easy to share, use, and improve on a timeline different -from that of the language. Don’t be shy about sharing code that’s useful to you -on *https://crates.io*; it’s likely that it will be useful to someone else as -well! - +Sharing code with Cargo and crates.io is +part of what makes the Rust ecosystem useful for many different tasks. Rust’s +standard library is small and stable, but crates are easy to share, use, and +improve on a timeline different from that of the language. Don’t be shy about +sharing code that’s useful to you on crates.io; it’s likely that it will be useful to someone else as well! diff --git a/nostarch/chapter15.md b/nostarch/chapter15.md index c0e847da1c..c7a5099a59 100644 --- a/nostarch/chapter15.md +++ b/nostarch/chapter15.md @@ -13,7 +13,7 @@ memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4. References are indicated by the `&` symbol and borrow the value they point to. They don’t have any special capabilities other than referring to -data, and they have no overhead. +data, and have no overhead. *Smart pointers*, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities. The concept of @@ -27,32 +27,32 @@ the number of owners and, when no owners remain, cleaning up the data. Rust, with its concept of ownership and borrowing, has an additional difference between references and smart pointers: while references only borrow data, in -many cases smart pointers *own* the data they point to. +many cases, smart pointers *own* the data they point to. Though we didn’t call them as such at the time, we’ve already encountered a few smart pointers in this book, including `String` and `Vec` in Chapter 8. Both -of these types count as smart pointers because they own some memory and allow -you to manipulate it. They also have metadata and extra capabilities or -guarantees. `String`, for example, stores its capacity as metadata and has the -extra ability to ensure its data will always be valid UTF-8. +these types count as smart pointers because they own some memory and allow you +to manipulate it. They also have metadata and extra capabilities or guarantees. +`String`, for example, stores its capacity as metadata and has the extra +ability to ensure its data will always be valid UTF-8. Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the `Deref` and `Drop` traits. The `Deref` trait allows an instance of the smart pointer struct to behave like a reference so you can write your code to work with either references or smart pointers. The `Drop` trait allows you to customize the code that’s run when an instance -of the smart pointer goes out of scope. In this chapter, we’ll discuss both -traits and demonstrate why they’re important to smart pointers. +of the smart pointer goes out of scope. In this chapter, we’ll discuss both of +these traits and demonstrate why they’re important to smart pointers. Given that the smart pointer pattern is a general design pattern used frequently in Rust, this chapter won’t cover every existing smart pointer. Many libraries have their own smart pointers, and you can even write your own. We’ll cover the most common smart pointers in the standard library: -* `Box`, for allocating values on the heap +* `Box` for allocating values on the heap * `Rc`, a reference counting type that enables multiple ownership * `Ref` and `RefMut`, accessed through `RefCell`, a type that enforces -the borrowing rules at runtime instead of compile time + the borrowing rules at runtime instead of compile time In addition, we’ll cover the *interior mutability* pattern where an immutable type exposes an API for mutating an interior value. We’ll also discuss @@ -72,30 +72,31 @@ heap instead of on the stack. But they don’t have many extra capabilities either. You’ll use them most often in these situations: * When you have a type whose size can’t be known at compile time and you want -to use a value of that type in a context that requires an exact size + to use a value of that type in a context that requires an exact size * When you have a large amount of data and you want to transfer ownership but -ensure the data won’t be copied when you do so + ensure the data won’t be copied when you do so * When you want to own a value and you care only that it’s a type that -implements a particular trait rather than being of a specific type + implements a particular trait rather than being of a specific type -We’ll demonstrate the first situation in “Enabling Recursive Types with Boxes” -on page XX. In the second case, transferring ownership of a large amount of -data can take a long time because the data is copied around on the stack. To -improve performance in this situation, we can store the large amount of data on -the heap in a box. Then, only the small amount of pointer data is copied around -on the stack, while the data it references stays in one place on the heap. The -third case is known as a *trait object*, and “Using Trait Objects That Allow -for Values of Different Types” on page XX is devoted to that topic. So what you -learn here you’ll apply again in that section! +We’ll demonstrate the first situation in the “Enabling Recursive Types with +Boxes” section. In the +second case, transferring ownership of a large amount of data can take a long +time because the data is copied around on the stack. To improve performance in +this situation, we can store the large amount of data on the heap in a box. +Then, only the small amount of pointer data is copied around on the stack, +while the data it references stays in one place on the heap. The third case is +known as a *trait object*, and Chapter 18 devotes an entire section, “Using +Trait Objects That Allow for Values of Different Types,” just to that topic. So what you learn here you’ll apply again in +Chapter 18! -### Using Box to Store Data on the Heap +### Using a Box to Store Data on the Heap Before we discuss the heap storage use case for `Box`, we’ll cover the syntax and how to interact with values stored within a `Box`. -Listing 15-1 shows how to use a box to store an `i32` value on the heap. +Listing 15-1 shows how to use a box to store an `i32` value on the heap: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -108,7 +109,7 @@ Listing 15-1: Storing an `i32` value on the heap using a box We define the variable `b` to have the value of a `Box` that points to the value `5`, which is allocated on the heap. This program will print `b = 5`; in -this case, we can access the data in the box similar to how we would if this +this case, we can access the data in the box similarly to how we would if this data were on the stack. Just like any owned value, when a box goes out of scope, as `b` does at the end of `main`, it will be deallocated. The deallocation happens both for the box (stored on the stack) and the data it @@ -118,34 +119,34 @@ Putting a single value on the heap isn’t very useful, so you won’t use boxes themselves in this way very often. Having values like a single `i32` on the stack, where they’re stored by default, is more appropriate in the majority of situations. Let’s look at a case where boxes allow us to define types that we -wouldn’t be allowed to define if we didn’t have boxes. +wouldn’t be allowed to if we didn’t have boxes. ### Enabling Recursive Types with Boxes -A value of a *recursive type* can have another value of the same type as part -of itself. Recursive types pose an issue because at compile time Rust needs to +A value of *recursive type* can have another value of the same type as part of +itself. Recursive types pose an issue because, at compile time, Rust needs to know how much space a type takes up. However, the nesting of values of recursive types could theoretically continue infinitely, so Rust can’t know how much space the value needs. Because boxes have a known size, we can enable recursive types by inserting a box in the recursive type definition. -As an example of a recursive type, let’s explore the *cons list*. This is a -data type commonly found in functional programming languages. The cons list -type we’ll define is straightforward except for the recursion; therefore, the +As an example of a recursive type, let’s explore the *cons list*. This is a data +type commonly found in functional programming languages. The cons list type +we’ll define is straightforward except for the recursion; therefore, the concepts in the example we’ll work with will be useful any time you get into more complex situations involving recursive types. #### More Information About the Cons List A *cons list* is a data structure that comes from the Lisp programming language -and its dialects, is made up of nested pairs, and is the Lisp version of a -linked list. Its name comes from the `cons` function (short for *construct -function*) in Lisp that constructs a new pair from its two arguments. By +and its dialects and is made up of nested pairs, and is the Lisp version of a +linked list. Its name comes from the `cons` function (short for “construct +function”) in Lisp that constructs a new pair from its two arguments. By calling `cons` on a pair consisting of a value and another pair, we can construct cons lists made up of recursive pairs. For example, here’s a pseudocode representation of a cons list containing the -list `1, 2, 3` with each pair in parentheses: +list 1, 2, 3 with each pair in parentheses: ``` (1, (2, (3, Nil))) @@ -155,8 +156,8 @@ Each item in a cons list contains two elements: the value of the current item and the next item. The last item in the list contains only a value called `Nil` without a next item. A cons list is produced by recursively calling the `cons` function. The canonical name to denote the base case of the recursion is `Nil`. -Note that this is not the same as the “null” or “nil” concept in Chapter 6, -which is an invalid or absent value. +Note that this is not the same as the “null” or “nil” concept discussed in +Chapter 6, which is an invalid or absent value. The cons list isn’t a commonly used data structure in Rust. Most of the time when you have a list of items in Rust, `Vec` is a better choice to use. @@ -168,7 +169,7 @@ Listing 15-2 contains an enum definition for a cons list. Note that this code won’t compile yet because the `List` type doesn’t have a known size, which we’ll demonstrate. -Filename: src/main.rs +src/main.rs ``` enum List { @@ -177,22 +178,19 @@ enum List { } ``` -Listing 15-2: The first attempt at defining an enum to represent a cons list -data structure of `i32` values +Listing 15-2: The first attempt at defining an enum to represent a cons list data structure of `i32` values > Note: We’re implementing a cons list that holds only `i32` values for the -purposes of this example. We could have implemented it using generics, as we -discussed in Chapter 10, to define a cons list type that could store values of -any type. +> purposes of this example. We could have implemented it using generics, as we +> discussed in Chapter 10, to define a cons list type that could store values of +> any type. Using the `List` type to store the list `1, 2, 3` would look like the code in -Listing 15-3. +Listing 15-3: -Filename: src/main.rs +src/main.rs ``` ---snip-- - use crate::List::{Cons, Nil}; fn main() { @@ -208,22 +206,39 @@ is one more `Cons` value that holds `3` and a `List` value, which is finally `Nil`, the non-recursive variant that signals the end of the list. If we try to compile the code in Listing 15-3, we get the error shown in -Listing 15-4. +Listing 15-4: + +output.txt ``` +$ cargo run + Compiling cons-list v0.1.0 (file:///projects/cons-list) error[E0072]: recursive type `List` has infinite size --> src/main.rs:1:1 | 1 | enum List { - | ^^^^^^^^^ recursive type has infinite size + | ^^^^^^^^^ 2 | Cons(i32, List), | ---- recursive without indirection | -help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` -representable +help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle | 2 | Cons(i32, Box), | ++++ + + +error[E0391]: cycle detected when computing when `List` needs drop + --> src/main.rs:1:1 + | +1 | enum List { + | ^^^^^^^^^ + | + = note: ...which immediately requires computing when `List` needs drop again + = note: cycle used when computing whether `List` needs drop + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + +Some errors have detailed explanations: E0072, E0391. +For more information about an error, try `rustc --explain E0072`. +error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors ``` Listing 15-4: The error we get when attempting to define a recursive enum @@ -231,7 +246,7 @@ Listing 15-4: The error we get when attempting to define a recursive enum The error shows this type “has infinite size.” The reason is that we’ve defined `List` with a variant that is recursive: it holds another value of itself directly. As a result, Rust can’t figure out how much space it needs to store a -`List` value. Let’s break down why we get this error. First we’ll look at how +`List` value. Let’s break down why we get this error. First, we’ll look at how Rust decides how much space it needs to store a value of a non-recursive type. #### Computing the Size of a Non-Recursive Type @@ -264,22 +279,28 @@ type needs, the compiler looks at the variants, starting with the `Cons` variant. The `Cons` variant holds a value of type `i32` and a value of type `List`, and this process continues infinitely, as shown in Figure 15-1. -Figure 15-1: An infinite `List` consisting of infinite `Cons` variants +An infinite Cons list + +Figure 15-1: An infinite `List` consisting of infinite +`Cons` variants #### Using Box to Get a Recursive Type with a Known Size Because Rust can’t figure out how much space to allocate for recursively defined types, the compiler gives an error with this helpful suggestion: + + ``` -help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` -representable +help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle | 2 | Cons(i32, Box), | ++++ + ``` -In this suggestion, *indirection* means that instead of storing a value +In this suggestion, “indirection” means that instead of storing a value directly, we should change the data structure to store the value indirectly by storing a pointer to the value instead. @@ -293,9 +314,9 @@ this implementation is now more like placing the items next to one another rather than inside one another. We can change the definition of the `List` enum in Listing 15-2 and the usage -of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile. +of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile: -Filename: src/main.rs +src/main.rs ``` enum List { @@ -306,38 +327,31 @@ enum List { use crate::List::{Cons, Nil}; fn main() { - let list = Cons( - 1, - Box::new(Cons( - 2, - Box::new(Cons( - 3, - Box::new(Nil) - )) - )) - ); + let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); } ``` -Listing 15-5: Definition of `List` that uses `Box` in order to have a known -size +Listing 15-5: Definition of `List` that uses `Box` in order to have a known size -The `Cons` variant needs the size of an `i32` plus the space to store the box’s -pointer data. The `Nil` variant stores no values, so it needs less space than -the `Cons` variant. We now know that any `List` value will take up the size of -an `i32` plus the size of a box’s pointer data. By using a box, we’ve broken -the infinite, recursive chain, so the compiler can figure out the size it needs -to store a `List` value. Figure 15-2 shows what the `Cons` variant looks like -now. +The `Cons` variant needs the size of an `i32` plus the space to store the +box’s pointer data. The `Nil` variant stores no values, so it needs less space +than the `Cons` variant. We now know that any `List` value will take up the +size of an `i32` plus the size of a box’s pointer data. By using a box, we’ve +broken the infinite, recursive chain, so the compiler can figure out the size +it needs to store a `List` value. Figure 15-2 shows what the `Cons` variant +looks like now. -Figure 15-2: A `List` that is not infinitely sized, because `Cons` holds a `Box` +A finite Cons list + +Figure 15-2: A `List` that is not infinitely sized +because `Cons` holds a `Box` Boxes provide only the indirection and heap allocation; they don’t have any other special capabilities, like those we’ll see with the other smart pointer types. They also don’t have the performance overhead that these special capabilities incur, so they can be useful in cases like the cons list where the indirection is the only feature we need. We’ll look at more use cases for boxes -in Chapter 17. +in Chapter 18, too. The `Box` type is a smart pointer because it implements the `Deref` trait, which allows `Box` values to be treated like references. When a `Box` @@ -347,7 +361,7 @@ even more important to the functionality provided by the other smart pointer types we’ll discuss in the rest of this chapter. Let’s explore these two traits in more detail. -## Treating Smart Pointers Like Regular References with Deref +## Treating Smart Pointers Like Regular References with the Deref Trait Implementing the `Deref` trait allows you to customize the behavior of the *dereference operator* `*` (not to be confused with the multiplication or glob @@ -359,57 +373,69 @@ Let’s first look at how the dereference operator works with regular references Then we’ll try to define a custom type that behaves like `Box`, and see why the dereference operator doesn’t work like a reference on our newly defined type. We’ll explore how implementing the `Deref` trait makes it possible for -smart pointers to work in ways similar to references. Then we’ll look at Rust’s -*deref coercion* feature and how it lets us work with either references or -smart pointers. +smart pointers to work in ways similar to references. Then we’ll look at +Rust’s *deref coercion* feature and how it lets us work with either references +or smart pointers. > Note: There’s one big difference between the `MyBox` type we’re about to -build and the real `Box`: our version will not store its data on the heap. -We are focusing this example on `Deref`, so where the data is actually stored -is less important than the pointer-like behavior. +> build and the real `Box`: our version will not store its data on the heap. +> We are focusing this example on `Deref`, so where the data is actually stored +> is less important than the pointer-like behavior. + + + + ### Following the Pointer to the Value A regular reference is a type of pointer, and one way to think of a pointer is as an arrow to a value stored somewhere else. In Listing 15-6, we create a reference to an `i32` value and then use the dereference operator to follow the -reference to the value. +reference to the value: -Filename: src/main.rs +src/main.rs ``` fn main() { - 1 let x = 5; - 2 let y = &x; + let x = 5; + let y = &x; - 3 assert_eq!(5, x); - 4 assert_eq!(5, *y); + assert_eq!(5, x); + assert_eq!(5, *y); } ``` -Listing 15-6: Using the dereference operator to follow a reference to an `i32` -value +Listing 15-6: Using the dereference operator to follow a reference to an `i32` value -The variable `x` holds an `i32` value `5` [1]. We set `y` equal to a reference -to `x` [2]. We can assert that `x` is equal to `5` [3]. However, if we want to -make an assertion about the value in `y`, we have to use `*y` to follow the -reference to the value it’s pointing to (hence *dereference*) so the compiler -can compare the actual value [4]. Once we dereference `y`, we have access to -the integer value `y` is pointing to that we can compare with `5`. +The variable `x` holds an `i32` value `5`. We set `y` equal to a reference to +`x`. We can assert that `x` is equal to `5`. However, if we want to make an +assertion about the value in `y`, we have to use `*y` to follow the reference +to the value it’s pointing to (hence *dereference*) so the compiler can compare +the actual value. Once we dereference `y`, we have access to the integer value +`y` is pointing to that we can compare with `5`. If we tried to write `assert_eq!(5, y);` instead, we would get this compilation error: ``` +$ cargo run + Compiling deref-example v0.1.0 (file:///projects/deref-example) error[E0277]: can't compare `{integer}` with `&{integer}` --> src/main.rs:6:5 | 6 | assert_eq!(5, y); - | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == -&{integer}` + | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}` | - = help: the trait `PartialEq<&{integer}>` is not implemented -for `{integer}` + = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}` + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider dereferencing here + --> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/core/src/macros/mod.rs:46:35 + | +46| if !(*left_val == **right_val) { + | + + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `deref-example` (bin "deref-example") due to 1 previous error ``` Comparing a number and a reference to a number isn’t allowed because they’re @@ -421,29 +447,29 @@ to the value it’s pointing to. We can rewrite the code in Listing 15-6 to use a `Box` instead of a reference; the dereference operator used on the `Box` in Listing 15-7 functions in the same way as the dereference operator used on the reference in -Listing 15-6. +Listing 15-6: -Filename: src/main.rs +src/main.rs ``` fn main() { let x = 5; - 1 let y = Box::new(x); + let y = Box::new(x); assert_eq!(5, x); - 2 assert_eq!(5, *y); + assert_eq!(5, *y); } ``` Listing 15-7: Using the dereference operator on a `Box` The main difference between Listing 15-7 and Listing 15-6 is that here we set -`y` to be an instance of a box pointing to a copied value of `x` rather than a -reference pointing to the value of `x` [1]. In the last assertion [2], we can -use the dereference operator to follow the box’s pointer in the same way that -we did when `y` was a reference. Next, we’ll explore what is special about -`Box` that enables us to use the dereference operator by defining our own -box type. +`y` to be an instance of a `Box` pointing to a copied value of `x` rather +than a reference pointing to the value of `x`. In the last assertion, we can +use the dereference operator to follow the pointer of the `Box` in the same +way that we did when `y` was a reference. Next, we’ll explore what is special +about `Box` that enables us to use the dereference operator by defining our +own type. ### Defining Our Own Smart Pointer @@ -456,32 +482,31 @@ The `Box` type is ultimately defined as a tuple struct with one element, so Listing 15-8 defines a `MyBox` type in the same way. We’ll also define a `new` function to match the `new` function defined on `Box`. -Filename: src/main.rs +src/main.rs ``` - 1 struct MyBox(T); +struct MyBox(T); impl MyBox { - 2 fn new(x: T) -> MyBox { - 3 MyBox(x) + fn new(x: T) -> MyBox { + MyBox(x) } } ``` Listing 15-8: Defining a `MyBox` type -We define a struct named `MyBox` and declare a generic parameter `T` [1] -because we want our type to hold values of any type. The `MyBox` type is a -tuple struct with one element of type `T`. The `MyBox::new` function takes one -parameter of type `T` [2] and returns a `MyBox` instance that holds the value -passed in [3]. +We define a struct named `MyBox` and declare a generic parameter `T`, because +we want our type to hold values of any type. The `MyBox` type is a tuple struct +with one element of type `T`. The `MyBox::new` function takes one parameter of +type `T` and returns a `MyBox` instance that holds the value passed in. Let’s try adding the `main` function in Listing 15-7 to Listing 15-8 and changing it to use the `MyBox` type we’ve defined instead of `Box`. The code in Listing 15-9 won’t compile because Rust doesn’t know how to dereference `MyBox`. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -493,63 +518,71 @@ fn main() { } ``` -Listing 15-9: Attempting to use `MyBox` in the same way we used references -and `Box` +Listing 15-9: Attempting to use `MyBox` in the same way we used references and `Box` -Here’s the resultant compilation error: +Here’s the resulting compilation error: ``` +$ cargo run + Compiling deref-example v0.1.0 (file:///projects/deref-example) error[E0614]: type `MyBox<{integer}>` cannot be dereferenced --> src/main.rs:14:19 | 14 | assert_eq!(5, *y); | ^^ + +For more information about this error, try `rustc --explain E0614`. +error: could not compile `deref-example` (bin "deref-example") due to 1 previous error ``` Our `MyBox` type can’t be dereferenced because we haven’t implemented that ability on our type. To enable dereferencing with the `*` operator, we implement the `Deref` trait. + + + + ### Implementing the Deref Trait -As discussed in “Implementing a Trait on a Type” on page XX, to implement a -trait we need to provide implementations for the trait’s required methods. The -`Deref` trait, provided by the standard library, requires us to implement one -method named `deref` that borrows `self` and returns a reference to the inner -data. Listing 15-10 contains an implementation of `Deref` to add to the -definition of `MyBox```. +As discussed in “Implementing a Trait on a Type” in +Chapter 10, to implement a trait, we need to provide implementations for the +trait’s required methods. The `Deref` trait, provided by the standard library, +requires us to implement one method named `deref` that borrows `self` and +returns a reference to the inner data. Listing 15-10 contains an implementation +of `Deref` to add to the definition of `MyBox`: -Filename: src/main.rs +src/main.rs ``` use std::ops::Deref; impl Deref for MyBox { - 1 type Target = T; + type Target = T; fn deref(&self) -> &Self::Target { - 2 &self.0 + &self.0 } } ``` Listing 15-10: Implementing `Deref` on `MyBox` -The `type Target = T;` syntax [1] defines an associated type for the `Deref` +The `type Target = T;` syntax defines an associated type for the `Deref` trait to use. Associated types are a slightly different way of declaring a generic parameter, but you don’t need to worry about them for now; we’ll cover -them in more detail in Chapter 19. +them in more detail in Chapter 20. We fill in the body of the `deref` method with `&self.0` so `deref` returns a -reference to the value we want to access with the `*` operator [2]; recall from -“Using Tuple Structs Without Named Fields to Create Different Types” on page XX -that `.0` accesses the first value in a tuple struct. The `main` function in -Listing 15-9 that calls `*` on the `MyBox` value now compiles, and the -assertions pass! +reference to the value we want to access with the `*` operator; recall from the +“Using Tuple Structs without Named Fields to Create Different +Types” section of Chapter 5 that `.0` accesses +the first value in a tuple struct. The `main` function in Listing 15-9 that +calls `*` on the `MyBox` value now compiles, and the assertions pass! Without the `Deref` trait, the compiler can only dereference `&` references. The `deref` method gives the compiler the ability to take a value of any type -that implements `Deref` and call the `deref` method to get a `&` reference that +that implements `Deref` and call the `deref` method to get an `&` reference that it knows how to dereference. When we entered `*y` in Listing 15-9, behind the scenes Rust actually ran this @@ -567,7 +600,7 @@ identically whether we have a regular reference or a type that implements The reason the `deref` method returns a reference to a value, and that the plain dereference outside the parentheses in `*(y.deref())` is still necessary, -has to do with the ownership system. If the `deref` method returned the value +is to do with the ownership system. If the `deref` method returned the value directly instead of a reference to the value, the value would be moved out of `self`. We don’t want to take ownership of the inner value inside `MyBox` in this case or in most cases where we use the dereference operator. @@ -598,9 +631,9 @@ can work for either references or smart pointers. To see deref coercion in action, let’s use the `MyBox` type we defined in Listing 15-8 as well as the implementation of `Deref` that we added in Listing 15-10. Listing 15-11 shows the definition of a function that has a string slice -parameter. +parameter: -Filename: src/main.rs +src/main.rs ``` fn hello(name: &str) { @@ -611,10 +644,10 @@ fn hello(name: &str) { Listing 15-11: A `hello` function that has the parameter `name` of type `&str` We can call the `hello` function with a string slice as an argument, such as -`hello("Rust");`, for example. Deref coercion makes it possible to call `hello` -with a reference to a value of type `MyBox`, as shown in Listing 15-12. +`hello("Rust");` for example. Deref coercion makes it possible to call `hello` +with a reference to a value of type `MyBox`, as shown in Listing 15-12: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -623,8 +656,7 @@ fn main() { } ``` -Listing 15-12: Calling `hello` with a reference to a `MyBox` value, -which works because of deref coercion +Listing 15-12: Calling `hello` with a reference to a `MyBox` value, which works because of deref coercion Here we’re calling the `hello` function with the argument `&m`, which is a reference to a `MyBox` value. Because we implemented the `Deref` trait @@ -638,7 +670,7 @@ If Rust didn’t implement deref coercion, we would have to write the code in Listing 15-13 instead of the code in Listing 15-12 to call `hello` with a value of type `&MyBox`. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -647,8 +679,7 @@ fn main() { } ``` -Listing 15-13: The code we would have to write if Rust didn’t have deref -coercion +Listing 15-13: The code we would have to write if Rust didn’t have deref coercion The `(*m)` dereferences the `MyBox` into a `String`. Then the `&` and `[..]` take a string slice of the `String` that is equal to the whole string to @@ -671,14 +702,14 @@ operator on mutable references. Rust does deref coercion when it finds types and trait implementations in three cases: -* From `&T` to `&U` when `T: Deref` -* From `&mut T` to `&mut U` when `T: DerefMut` -* From `&mut T` to `&U` when `T: Deref` +1. From `&T` to `&U` when `T: Deref` +1. From `&mut T` to `&mut U` when `T: DerefMut` +1. From `&mut T` to `&U` when `T: Deref` -The first two cases are the same except that the second implements mutability. -The first case states that if you have a `&T`, and `T` implements `Deref` to -some type `U`, you can get a `&U` transparently. The second case states that -the same deref coercion happens for mutable references. +The first two cases are the same as each other except that the second +implements mutability. The first case states that if you have a `&T`, and `T` +implements `Deref` to some type `U`, you can get a `&U` transparently. The +second case states that the same deref coercion happens for mutable references. The third case is trickier: Rust will also coerce a mutable reference to an immutable one. But the reverse is *not* possible: immutable references will @@ -701,12 +732,12 @@ be used to release resources like files or network connections. We’re introducing `Drop` in the context of smart pointers because the functionality of the `Drop` trait is almost always used when implementing a -smart pointer. For example, when a `Box` is dropped it will deallocate the +smart pointer. For example, when a `Box` is dropped, it will deallocate the space on the heap that the box points to. In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance of those types. Examples -include file handles, sockets, and locks. If they forget, the system might +include file handles, sockets, or locks. If they forget, the system might become overloaded and crash. In Rust, you can specify that a particular bit of code be run whenever a value goes out of scope, and the compiler will insert this code automatically. As a result, you don’t need to be careful about @@ -720,54 +751,54 @@ let’s implement `drop` with `println!` statements for now. Listing 15-14 shows a `CustomSmartPointer` struct whose only custom functionality is that it will print `Dropping CustomSmartPointer!` when the -instance goes out of scope, to show when Rust runs the `drop` method. +instance goes out of scope, to show when Rust runs the `drop` function. -Filename: src/main.rs +src/main.rs ``` struct CustomSmartPointer { data: String, } -1 impl Drop for CustomSmartPointer { +impl Drop for CustomSmartPointer { fn drop(&mut self) { - 2 println!( - "Dropping CustomSmartPointer with data `{}`!", - self.data - ); + println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { - 3 let c = CustomSmartPointer { + let c = CustomSmartPointer { data: String::from("my stuff"), }; - 4 let d = CustomSmartPointer { + let d = CustomSmartPointer { data: String::from("other stuff"), }; - 5 println!("CustomSmartPointers created."); -6 } + println!("CustomSmartPointers created."); +} ``` -Listing 15-14: A `CustomSmartPointer` struct that implements the `Drop` trait -where we would put our cleanup code +Listing 15-14: A `CustomSmartPointer` struct that implements the `Drop` trait where we would put our cleanup code The `Drop` trait is included in the prelude, so we don’t need to bring it into -scope. We implement the `Drop` trait on `CustomSmartPointer` [1] and provide an -implementation for the `drop` method that calls `println!` [2]. The body of the -`drop` method is where you would place any logic that you wanted to run when an -instance of your type goes out of scope. We’re printing some text here to +scope. We implement the `Drop` trait on `CustomSmartPointer` and provide an +implementation for the `drop` method that calls `println!`. The body of the +`drop` function is where you would place any logic that you wanted to run when +an instance of your type goes out of scope. We’re printing some text here to demonstrate visually when Rust will call `drop`. -In `main`, we create two instances of `CustomSmartPointer` at [3] and [4] and -then print `CustomSmartPointers created` [5]. At the end of `main` [6], our -instances of `CustomSmartPointer` will go out of scope, and Rust will call the -code we put in the `drop` method [2], printing our final message. Note that we -didn’t need to call the `drop` method explicitly. +In `main`, we create two instances of `CustomSmartPointer` and then print +`CustomSmartPointers created`. At the end of `main`, our instances of +`CustomSmartPointer` will go out of scope, and Rust will call the code we put +in the `drop` method, printing our final message. Note that we didn’t need to +call the `drop` method explicitly. When we run this program, we’ll see the following output: ``` +$ cargo run + Compiling drop-example v0.1.0 (file:///projects/drop-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s + Running `target/debug/drop-example` CustomSmartPointers created. Dropping CustomSmartPointer with data `other stuff`! Dropping CustomSmartPointer with data `my stuff`! @@ -780,21 +811,23 @@ give you a visual guide to how the `drop` method works; usually you would specify the cleanup code that your type needs to run rather than a print message. +### Dropping a Value Early with std::mem::drop + Unfortunately, it’s not straightforward to disable the automatic `drop` functionality. Disabling `drop` isn’t usually necessary; the whole point of the `Drop` trait is that it’s taken care of automatically. Occasionally, however, you might want to clean up a value early. One example is when using smart pointers that manage locks: you might want to force the `drop` method that releases the lock so that other code in the same scope can acquire the lock. -Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead, +Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead you have to call the `std::mem::drop` function provided by the standard library if you want to force a value to be dropped before the end of its scope. If we try to call the `Drop` trait’s `drop` method manually by modifying the `main` function from Listing 15-14, as shown in Listing 15-15, we’ll get a -compiler error. +compiler error: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -803,31 +836,35 @@ fn main() { }; println!("CustomSmartPointer created."); c.drop(); - println!( - "CustomSmartPointer dropped before the end of main." - ); + println!("CustomSmartPointer dropped before the end of main."); } ``` -Listing 15-15: Attempting to call the `drop` method from the `Drop` trait -manually to clean up early +Listing 15-15: Attempting to call the `drop` method from the `Drop` trait manually to clean up early When we try to compile this code, we’ll get this error: ``` +$ cargo run + Compiling drop-example v0.1.0 (file:///projects/drop-example) error[E0040]: explicit use of destructor method --> src/main.rs:16:7 | 16 | c.drop(); - | --^^^^-- - | | | - | | explicit destructor calls not allowed - | help: consider using `drop` function: `drop(c)` + | ^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + | +16 | drop(c); + | +++++ ~ + +For more information about this error, try `rustc --explain E0040`. +error: could not compile `drop-example` (bin "drop-example") due to 1 previous error ``` This error message states that we’re not allowed to explicitly call `drop`. The error message uses the term *destructor*, which is the general programming term -for a function that cleans up an instance. A *destructor* is analogous to a +for a function that cleans up an instance. A destructor is analogous to a *constructor*, which creates an instance. The `drop` function in Rust is one particular destructor. @@ -841,11 +878,11 @@ scope, and we can’t call the `drop` method explicitly. So, if we need to force a value to be cleaned up early, we use the `std::mem::drop` function. The `std::mem::drop` function is different from the `drop` method in the `Drop` -trait. We call it by passing as an argument the value we want to force-drop. +trait. We call it by passing as an argument the value we want to force drop. The function is in the prelude, so we can modify `main` in Listing 15-15 to -call the `drop` function, as shown in Listing 15-16. +call the `drop` function, as shown in Listing 15-16: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -854,26 +891,26 @@ fn main() { }; println!("CustomSmartPointer created."); drop(c); - println!( - "CustomSmartPointer dropped before the end of main." - ); + println!("CustomSmartPointer dropped before the end of main."); } ``` -Listing 15-16: Calling `std::mem::drop` to explicitly drop a value before it -goes out of scope +Listing 15-16: Calling `std::mem::drop` to explicitly drop a value before it goes out of scope Running this code will print the following: ``` +$ cargo run + Compiling drop-example v0.1.0 (file:///projects/drop-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s + Running `target/debug/drop-example` CustomSmartPointer created. Dropping CustomSmartPointer with data `some data`! CustomSmartPointer dropped before the end of main. ``` -The text `Dropping CustomSmartPointer with data `some data`!` is printed -between the `CustomSmartPointer created.` and `CustomSmartPointer dropped -before the end of main.` text, showing that the `drop` method code is called to +The text ``Dropping CustomSmartPointer with data `some data`!`` is printed +between the `CustomSmartPointer created.` and `CustomSmartPointer dropped before the end of main.` text, showing that the `drop` method code is called to drop `c` at that point. You can use code specified in a `Drop` trait implementation in many ways to @@ -909,7 +946,7 @@ Imagine `Rc` as a TV in a family room. When one person enters to watch TV, they turn it on. Others can come into the room and watch the TV. When the last person leaves the room, they turn off the TV because it’s no longer being used. If someone turns off the TV while others are still watching it, there would be -an uproar from the remaining TV watchers! +uproar from the remaining TV watchers! We use the `Rc` type when we want to allocate some data on the heap for multiple parts of our program to read and we can’t determine at compile time @@ -925,19 +962,22 @@ multithreaded programs. Let’s return to our cons list example in Listing 15-5. Recall that we defined it using `Box`. This time, we’ll create two lists that both share ownership -of a third list. Conceptually, this looks similar to Figure 15-3. +of a third list. Conceptually, this looks similar to Figure 15-3: + +Two lists that share ownership of a third list -Figure 15-3: Two lists, `b` and `c`, sharing ownership of a third list, `a` +Figure 15-3: Two lists, `b` and `c`, sharing ownership of +a third list, `a` -We’ll create list `a` that contains `5` and then `10`. Then we’ll make two more -lists: `b` that starts with `3` and `c` that starts with `4`. Both `b` and `c` -lists will then continue on to the first `a` list containing `5` and `10`. In -other words, both lists will share the first list containing `5` and `10`. +We’ll create list `a` that contains 5 and then 10. Then we’ll make two more +lists: `b` that starts with 3 and `c` that starts with 4. Both `b` and `c` +lists will then continue on to the first `a` list containing 5 and 10. In other +words, both lists will share the first list containing 5 and 10. Trying to implement this scenario using our definition of `List` with `Box` -won’t work, as shown in Listing 15-17. +won’t work, as shown in Listing 15-17: -Filename: src/main.rs +src/main.rs ``` enum List { @@ -949,32 +989,35 @@ use crate::List::{Cons, Nil}; fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); - 1 let b = Cons(3, Box::new(a)); - 2 let c = Cons(4, Box::new(a)); + let b = Cons(3, Box::new(a)); + let c = Cons(4, Box::new(a)); } ``` -Listing 15-17: Demonstrating that we’re not allowed to have two lists using -`Box` that try to share ownership of a third list +Listing 15-17: Demonstrating we’re not allowed to have two lists using `Box` that try to share ownership of a third list When we compile this code, we get this error: ``` +$ cargo run + Compiling cons-list v0.1.0 (file:///projects/cons-list) error[E0382]: use of moved value: `a` --> src/main.rs:11:30 | 9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); - | - move occurs because `a` has type `List`, which -does not implement the `Copy` trait + | - move occurs because `a` has type `List`, which does not implement the `Copy` trait 10 | let b = Cons(3, Box::new(a)); | - value moved here 11 | let c = Cons(4, Box::new(a)); | ^ value used here after move + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `cons-list` (bin "cons-list") due to 1 previous error ``` -The `Cons` variants own the data they hold, so when we create the `b` list [1], -`a` is moved into `b` and `b` owns `a`. Then, when we try to use `a` again when -creating `c` [2], we’re not allowed to because `a` has been moved. +The `Cons` variants own the data they hold, so when we create the `b` list, `a` +is moved into `b` and `b` owns `a`. Then, when we try to use `a` again when +creating `c`, we’re not allowed to because `a` has been moved. We could change the definition of `Cons` to hold references instead, but then we would have to specify lifetime parameters. By specifying lifetime @@ -993,7 +1036,7 @@ we call `Rc::clone`, the reference count to the data within the `Rc` will increase, and the data won’t be cleaned up unless there are zero references to it. -Filename: src/main.rs +src/main.rs ``` enum List { @@ -1002,22 +1045,22 @@ enum List { } use crate::List::{Cons, Nil}; -1 use std::rc::Rc; +use std::rc::Rc; fn main() { - 2 let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); - 3 let b = Cons(3, Rc::clone(&a)); - 4 let c = Cons(4, Rc::clone(&a)); + let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); + let b = Cons(3, Rc::clone(&a)); + let c = Cons(4, Rc::clone(&a)); } ``` Listing 15-18: A definition of `List` that uses `Rc` -We need to add a `use` statement to bring `Rc` into scope [1] because it’s -not in the prelude. In `main`, we create the list holding `5` and `10` and -store it in a new `Rc` in `a` [2]. Then, when we create `b` [3] and `c` -[4], we call the `Rc::clone` function and pass a reference to the `Rc` in -`a` as an argument. +We need to add a `use` statement to bring `Rc` into scope because it’s not +in the prelude. In `main`, we create the list holding 5 and 10 and store it in +a new `Rc` in `a`. Then when we create `b` and `c`, we call the +`Rc::clone` function and pass a reference to the `Rc` in `a` as an +argument. We could have called `a.clone()` rather than `Rc::clone(&a)`, but Rust’s convention is to use `Rc::clone` in this case. The implementation of @@ -1038,33 +1081,19 @@ counts changing as we create and drop references to the `Rc` in `a`. In Listing 15-19, we’ll change `main` so it has an inner scope around list `c`; then we can see how the reference count changes when `c` goes out of scope. -Filename: src/main.rs +src/main.rs ``` ---snip-- - fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); - println!( - "count after creating a = {}", - Rc::strong_count(&a) - ); + println!("count after creating a = {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); - println!( - "count after creating b = {}", - Rc::strong_count(&a) - ); + println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); - println!( - "count after creating c = {}", - Rc::strong_count(&a) - ); + println!("count after creating c = {}", Rc::strong_count(&a)); } - println!( - "count after c goes out of scope = {}", - Rc::strong_count(&a) - ); + println!("count after c goes out of scope = {}", Rc::strong_count(&a)); } ``` @@ -1074,11 +1103,16 @@ At each point in the program where the reference count changes, we print the reference count, which we get by calling the `Rc::strong_count` function. This function is named `strong_count` rather than `count` because the `Rc` type also has a `weak_count`; we’ll see what `weak_count` is used for in “Preventing -Reference Cycles Using Weak” on page XX. +Reference Cycles: Turning an `Rc` into a +`Weak`”. This code prints the following: ``` +$ cargo run + Compiling cons-list v0.1.0 (file:///projects/cons-list) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s + Running `target/debug/cons-list` count after creating a = 1 count after creating b = 2 count after creating c = 3 @@ -1115,7 +1149,7 @@ action is disallowed by the borrowing rules. To mutate data, the pattern uses `unsafe` code inside a data structure to bend Rust’s usual rules that govern mutation and borrowing. Unsafe code indicates to the compiler that we’re checking the rules manually instead of relying on the compiler to check them -for us; we will discuss unsafe code more in Chapter 19. +for us; we will discuss unsafe code more in Chapter 20. We can use types that use the interior mutability pattern only when we can ensure that the borrowing rules will be followed at runtime, even though the @@ -1128,11 +1162,11 @@ interior mutability pattern. ### Enforcing Borrowing Rules at Runtime with RefCell Unlike `Rc`, the `RefCell` type represents single ownership over the data -it holds. So what makes `RefCell` different from a type like `Box`? +it holds. So, what makes `RefCell` different from a type like `Box`? Recall the borrowing rules you learned in Chapter 4: -* At any given time, you can have *either* one mutable reference or any number -of immutable references (but not both). +* At any given time, you can have *either* (but not both) one mutable reference + or any number of immutable references. * References must always be valid. With references and `Box`, the borrowing rules’ invariants are enforced at @@ -1170,13 +1204,13 @@ multithreaded program in Chapter 16. Here is a recap of the reasons to choose `Box`, `Rc`, or `RefCell`: * `Rc` enables multiple owners of the same data; `Box` and `RefCell` -have single owners. + have single owners. * `Box` allows immutable or mutable borrows checked at compile time; `Rc` -allows only immutable borrows checked at compile time; `RefCell` allows -immutable or mutable borrows checked at runtime. + allows only immutable borrows checked at compile time; `RefCell` allows + immutable or mutable borrows checked at runtime. * Because `RefCell` allows mutable borrows checked at runtime, you can -mutate the value inside the `RefCell` even when the `RefCell` is -immutable. + mutate the value inside the `RefCell` even when the `RefCell` is + immutable. Mutating the value inside an immutable value is the *interior mutability* pattern. Let’s look at a situation in which interior mutability is useful and @@ -1187,8 +1221,6 @@ examine how it’s possible. A consequence of the borrowing rules is that when you have an immutable value, you can’t borrow it mutably. For example, this code won’t compile: -Filename: src/main.rs - ``` fn main() { let x = 5; @@ -1199,14 +1231,21 @@ fn main() { If you tried to compile this code, you’d get the following error: ``` -error[E0596]: cannot borrow `x` as mutable, as it is not declared -as mutable +$ cargo run + Compiling borrowing v0.1.0 (file:///projects/borrowing) +error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable --> src/main.rs:3:13 | -2 | let x = 5; - | - help: consider changing this to be mutable: `mut x` 3 | let y = &mut x; | ^^^^^^ cannot borrow as mutable + | +help: consider changing this to be mutable + | +2 | let mut x = 5; + | +++ + +For more information about this error, try `rustc --explain E0596`. +error: could not compile `borrowing` (bin "borrowing") due to 1 previous error ``` However, there are situations in which it would be useful for a value to mutate @@ -1224,13 +1263,13 @@ an immutable value and see why that is useful. #### A Use Case for Interior Mutability: Mock Objects Sometimes during testing a programmer will use a type in place of another type, -in order to observe particular behavior and assert that it’s implemented -correctly. This placeholder type is called a *test double*. Think of it in the -sense of a stunt double in filmmaking, where a person steps in and substitutes -for an actor to do a particularly tricky scene. Test doubles stand in for other -types when we’re running tests. *Mock objects* are specific types of test -doubles that record what happens during a test so you can assert that the -correct actions took place. +in order to observe particular behavior and assert it’s implemented correctly. +This placeholder type is called a *test double*. Think of it in the sense of a +“stunt double” in filmmaking, where a person steps in and substitutes for an +actor to do a particular tricky scene. Test doubles stand in for other types +when we’re running tests. *Mock objects* are specific types of test doubles +that record what happens during a test so you can assert that the correct +actions took place. Rust doesn’t have objects in the same sense as other languages have objects, and Rust doesn’t have mock object functionality built into the standard library @@ -1246,15 +1285,15 @@ Our library will only provide the functionality of tracking how close to the maximum a value is and what the messages should be at what times. Applications that use our library will be expected to provide the mechanism for sending the messages: the application could put a message in the application, send an -email, send a text message, or do something else. The library doesn’t need to -know that detail. All it needs is something that implements a trait we’ll -provide called `Messenger`. Listing 15-20 shows the library code. +email, send a text message, or something else. The library doesn’t need to know +that detail. All it needs is something that implements a trait we’ll provide +called `Messenger`. Listing 15-20 shows the library code: -Filename: src/lib.rs +src/lib.rs ``` pub trait Messenger { - 1 fn send(&self, msg: &str); + fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: Messenger> { @@ -1267,10 +1306,7 @@ impl<'a, T> LimitTracker<'a, T> where T: Messenger, { - pub fn new( - messenger: &'a T, - max: usize - ) -> LimitTracker<'a, T> { + pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, @@ -1278,80 +1314,73 @@ where } } - 2 pub fn set_value(&mut self, value: usize) { + pub fn set_value(&mut self, value: usize) { self.value = value; - let percentage_of_max = - self.value as f64 / self.max as f64; + let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 1.0 { - self.messenger - .send("Error: You are over your quota!"); + self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger - .send("Urgent: You're at 90% of your quota!"); + .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger - .send("Warning: You're at 75% of your quota!"); + .send("Warning: You've used up over 75% of your quota!"); } } } ``` -Listing 15-20: A library to keep track of how close a value is to a maximum -value and warn when the value is at certain levels +Listing 15-20: A library to keep track of how close a value is to a maximum value and warn when the value is at certain levels One important part of this code is that the `Messenger` trait has one method called `send` that takes an immutable reference to `self` and the text of the -message [1]. This trait is the interface our mock object needs to implement so -that the mock can be used in the same way a real object is. The other important -part is that we want to test the behavior of the `set_value` method on the -`LimitTracker` [2]. We can change what we pass in for the `value` parameter, -but `set_value` doesn’t return anything for us to make assertions on. We want -to be able to say that if we create a `LimitTracker` with something that -implements the `Messenger` trait and a particular value for `max`, when we pass -different numbers for `value` the messenger is told to send the appropriate -messages. +message. This trait is the interface our mock object needs to implement so that +the mock can be used in the same way a real object is. The other important part +is that we want to test the behavior of the `set_value` method on the +`LimitTracker`. We can change what we pass in for the `value` parameter, but +`set_value` doesn’t return anything for us to make assertions on. We want to be +able to say that if we create a `LimitTracker` with something that implements +the `Messenger` trait and a particular value for `max`, when we pass different +numbers for `value`, the messenger is told to send the appropriate messages. We need a mock object that, instead of sending an email or text message when we call `send`, will only keep track of the messages it’s told to send. We can create a new instance of the mock object, create a `LimitTracker` that uses the mock object, call the `set_value` method on `LimitTracker`, and then check that the mock object has the messages we expect. Listing 15-21 shows an attempt to -implement a mock object to do just that, but the borrow checker won’t allow it. +implement a mock object to do just that, but the borrow checker won’t allow it: -Filename: src/lib.rs +src/lib.rs ``` #[cfg(test)] mod tests { use super::*; - 1 struct MockMessenger { - 2 sent_messages: Vec, + struct MockMessenger { + sent_messages: Vec, } impl MockMessenger { - 3 fn new() -> MockMessenger { + fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } } - 4 impl Messenger for MockMessenger { + impl Messenger for MockMessenger { fn send(&self, message: &str) { - 5 self.sent_messages.push(String::from(message)); + self.sent_messages.push(String::from(message)); } } #[test] - 6 fn it_sends_an_over_75_percent_warning_message() { + fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); - let mut limit_tracker = LimitTracker::new( - &mock_messenger, - 100 - ); + let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); @@ -1360,55 +1389,63 @@ mod tests { } ``` -Listing 15-21: An attempt to implement a `MockMessenger` that isn’t allowed by -the borrow checker +Listing 15-21: An attempt to implement a `MockMessenger` that isn’t allowed by the borrow checker -This test code defines a `MockMessenger` struct [1] that has a `sent_messages` -field with a `Vec` of `String` values [2] to keep track of the messages it’s -told to send. We also define an associated function `new` [3] to make it -convenient to create new `MockMessenger` values that start with an empty list -of messages. We then implement the `Messenger` trait for `MockMessenger` [4] so -we can give a `MockMessenger` to a `LimitTracker`. In the definition of the -`send` method [5], we take the message passed in as a parameter and store it in -the `MockMessenger` list of `sent_messages`. +This test code defines a `MockMessenger` struct that has a `sent_messages` +field with a `Vec` of `String` values to keep track of the messages it’s told +to send. We also define an associated function `new` to make it convenient to +create new `MockMessenger` values that start with an empty list of messages. We +then implement the `Messenger` trait for `MockMessenger` so we can give a +`MockMessenger` to a `LimitTracker`. In the definition of the `send` method, we +take the message passed in as a parameter and store it in the `MockMessenger` +list of `sent_messages`. In the test, we’re testing what happens when the `LimitTracker` is told to set -`value` to something that is more than 75 percent of the `max` value [6]. First -we create a new `MockMessenger`, which will start with an empty list of -messages. Then we create a new `LimitTracker` and give it a reference to the -new `MockMessenger` and a `max` value of `100`. We call the `set_value` method -on the `LimitTracker` with a value of `80`, which is more than 75 percent of -100. Then we assert that the list of messages that the `MockMessenger` is -keeping track of should now have one message in it. +`value` to something that is more than 75 percent of the `max` value. First, we +create a new `MockMessenger`, which will start with an empty list of messages. +Then we create a new `LimitTracker` and give it a reference to the new +`MockMessenger` and a `max` value of 100. We call the `set_value` method on the +`LimitTracker` with a value of 80, which is more than 75 percent of 100. Then +we assert that the list of messages that the `MockMessenger` is keeping track +of should now have one message in it. However, there’s one problem with this test, as shown here: ``` -error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a -`&` reference +$ cargo test + Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) +error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/lib.rs:58:13 | -2 | fn send(&self, msg: &str); - | ----- help: consider changing that to be a mutable reference: -`&mut self` -... 58 | self.sent_messages.push(String::from(message)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a -`&` reference, so the data it refers to cannot be borrowed as mutable + | ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable + | +help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition + | +2 ~ fn send(&mut self, msg: &str); +3 | } +... +56 | impl Messenger for MockMessenger { +57 ~ fn send(&mut self, message: &str) { + | + +For more information about this error, try `rustc --explain E0596`. +error: could not compile `limit-tracker` (lib test) due to 1 previous error ``` -We can’t modify the `MockMessenger` to keep track of the messages because the +We can’t modify the `MockMessenger` to keep track of the messages, because the `send` method takes an immutable reference to `self`. We also can’t take the -suggestion from the error text to use `&mut self` instead because then the -signature of `send` wouldn’t match the signature in the `Messenger` trait -definition (feel free to try it and see what error message you get). +suggestion from the error text to use `&mut self` in both the `impl` method and +the `trait` definition. We do not want to change the `Messenger` trait solely +for the sake of testing. Instead, we need to find a way to make our test code +work correctly with our existing design. This is a situation in which interior mutability can help! We’ll store the -`sent_messages` within a `RefCell`, and then the `send` method will be able -to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 shows -what that looks like. +`sent_messages` within a `RefCell`, and then the `send` method will be +able to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 +shows what that looks like: -Filename: src/lib.rs +src/lib.rs ``` #[cfg(test)] @@ -1417,54 +1454,48 @@ mod tests { use std::cell::RefCell; struct MockMessenger { - 1 sent_messages: RefCell>, + sent_messages: RefCell>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { - 2 sent_messages: RefCell::new(vec![]), + sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { - self.sent_messages - 3 .borrow_mut() - .push(String::from(message)); + self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { - --snip-- + // --snip-- - assert_eq!( - 4 mock_messenger.sent_messages.borrow().len(), - 1 - ); + assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } } ``` -Listing 15-22: Using `RefCell` to mutate an inner value while the outer -value is considered immutable +Listing 15-22: Using `RefCell` to mutate an inner value while the outer value is considered immutable -The `sent_messages` field is now of type `RefCell>` [1] instead of +The `sent_messages` field is now of type `RefCell>` instead of `Vec`. In the `new` function, we create a new `RefCell>` -instance around the empty vector [2]. +instance around the empty vector. For the implementation of the `send` method, the first parameter is still an immutable borrow of `self`, which matches the trait definition. We call -`borrow_mut` on the `RefCell>` in `self.sent_messages` [3] to get a +`borrow_mut` on the `RefCell>` in `self.sent_messages` to get a mutable reference to the value inside the `RefCell>`, which is the vector. Then we can call `push` on the mutable reference to the vector to keep track of the messages sent during the test. The last change we have to make is in the assertion: to see how many items are in the inner vector, we call `borrow` on the `RefCell>` to get an -immutable reference to the vector [4]. +immutable reference to the vector. Now that you’ve seen how to use `RefCell`, let’s dig into how it works! @@ -1480,7 +1511,7 @@ can treat them like regular references. The `RefCell` keeps track of how many `Ref` and `RefMut` smart pointers are currently active. Every time we call `borrow`, the `RefCell` increases its count of how many immutable borrows are active. When a `Ref` -value goes out of scope, the count of immutable borrows goes down by 1. Just +value goes out of scope, the count of immutable borrows goes down by one. Just like the compile-time borrowing rules, `RefCell` lets us have many immutable borrows or one mutable borrow at any point in time. @@ -1491,22 +1522,21 @@ Listing 15-22. We’re deliberately trying to create two mutable borrows active for the same scope to illustrate that `RefCell` prevents us from doing this at runtime. -Filename: src/lib.rs +src/lib.rs ``` -impl Messenger for MockMessenger { - fn send(&self, message: &str) { - let mut one_borrow = self.sent_messages.borrow_mut(); - let mut two_borrow = self.sent_messages.borrow_mut(); + impl Messenger for MockMessenger { + fn send(&self, message: &str) { + let mut one_borrow = self.sent_messages.borrow_mut(); + let mut two_borrow = self.sent_messages.borrow_mut(); - one_borrow.push(String::from(message)); - two_borrow.push(String::from(message)); + one_borrow.push(String::from(message)); + two_borrow.push(String::from(message)); + } } -} ``` -Listing 15-23: Creating two mutable references in the same scope to see that -`RefCell` will panic +Listing 15-23: Creating two mutable references in the same scope to see that `RefCell` will panic We create a variable `one_borrow` for the `RefMut` smart pointer returned from `borrow_mut`. Then we create another mutable borrow in the same way in the @@ -1515,13 +1545,31 @@ which isn’t allowed. When we run the tests for our library, the code in Listin 15-23 will compile without any errors, but the test will fail: ``` +$ cargo test + Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s + Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde) + +running 1 test +test tests::it_sends_an_over_75_percent_warning_message ... FAILED + +failures: + ---- tests::it_sends_an_over_75_percent_warning_message stdout ---- -thread 'main' panicked at 'already borrowed: BorrowMutError', src/lib.rs:60:53 +thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53: +already borrowed: BorrowMutError note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + tests::it_sends_an_over_75_percent_warning_message + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +error: test failed, to rerun pass `--lib` ``` -Notice that the code panicked with the message `already borrowed: -BorrowMutError`. This is how `RefCell` handles violations of the borrowing +Notice that the code panicked with the message `already borrowed: BorrowMutError`. This is how `RefCell` handles violations of the borrowing rules at runtime. Choosing to catch borrowing errors at runtime rather than compile time, as @@ -1535,7 +1583,7 @@ in a context where only immutable values are allowed. You can use `RefCell` despite its trade-offs to get more functionality than regular references provide. -### Allowing Multiple Owners of Mutable Data with Rc and RefCell +### Having Multiple Owners of Mutable Data by Combining Rc and RefCell A common way to use `RefCell` is in combination with `Rc`. Recall that `Rc` lets you have multiple owners of some data, but it only gives immutable @@ -1545,12 +1593,12 @@ get a value that can have multiple owners *and* that you can mutate! For example, recall the cons list example in Listing 15-18 where we used `Rc` to allow multiple lists to share ownership of another list. Because `Rc` holds only immutable values, we can’t change any of the values in the -list once we’ve created them. Let’s add in `RefCell` for its ability to +list once we’ve created them. Let’s add in `RefCell` to gain the ability to change the values in the lists. Listing 15-24 shows that by using a `RefCell` in the `Cons` definition, we can modify the value stored in all -the lists. +the lists: -Filename: src/main.rs +src/main.rs ``` #[derive(Debug)] @@ -1564,44 +1612,49 @@ use std::cell::RefCell; use std::rc::Rc; fn main() { - 1 let value = Rc::new(RefCell::new(5)); + let value = Rc::new(RefCell::new(5)); - 2 let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); + let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a)); - 3 *value.borrow_mut() += 10; + *value.borrow_mut() += 10; - println!("a after = {:?}", a); - println!("b after = {:?}", b); - println!("c after = {:?}", c); + println!("a after = {a:?}"); + println!("b after = {b:?}"); + println!("c after = {c:?}"); } ``` Listing 15-24: Using `Rc>` to create a `List` that we can mutate We create a value that is an instance of `Rc>` and store it in a -variable named `value` [1] so we can access it directly later. Then we create a -`List` in `a` with a `Cons` variant that holds `value` [2]. We need to clone +variable named `value` so we can access it directly later. Then we create a +`List` in `a` with a `Cons` variant that holds `value`. We need to clone `value` so both `a` and `value` have ownership of the inner `5` value rather than transferring ownership from `value` to `a` or having `a` borrow from `value`. -We wrap the list `a` in an `Rc` so when we create lists `b` and `c`, they -can both refer to `a`, which is what we did in Listing 15-18. +We wrap the list `a` in an `Rc` so that when we create lists `b` and `c`, +they can both refer to `a`, which is what we did in Listing 15-18. After we’ve created the lists in `a`, `b`, and `c`, we want to add 10 to the -value in `value` [3]. We do this by calling `borrow_mut` on `value`, which uses -the automatic dereferencing feature we discussed in “Where’s the -> Operator?” -on page XX to dereference the `Rc` to the inner `RefCell` value. The -`borrow_mut` method returns a `RefMut` smart pointer, and we use the -dereference operator on it and change the inner value. +value in `value`. We do this by calling `borrow_mut` on `value`, which uses the +automatic dereferencing feature we discussed in Chapter 5 (see “Where’s the +`->` Operator?”) to dereference the +`Rc` to the inner `RefCell` value. The `borrow_mut` method returns a +`RefMut` smart pointer, and we use the dereference operator on it and change +the inner value. When we print `a`, `b`, and `c`, we can see that they all have the modified -value of `15` rather than `5`: +value of 15 rather than 5: ``` +$ cargo run + Compiling cons-list v0.1.0 (file:///projects/cons-list) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s + Running `target/debug/cons-list` a after = Cons(RefCell { value: 15 }, Nil) b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil)) c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil)) @@ -1613,7 +1666,7 @@ access to its interior mutability so we can modify our data when we need to. The runtime checks of the borrowing rules protect us from data races, and it’s sometimes worth trading a bit of speed for this flexibility in our data structures. Note that `RefCell` does not work for multithreaded code! -`Mutex` is the thread-safe version of `RefCell`, and we’ll discuss +`Mutex` is the thread-safe version of `RefCell` and we’ll discuss `Mutex` in Chapter 16. ## Reference Cycles Can Leak Memory @@ -1631,9 +1684,9 @@ will never be dropped. Let’s look at how a reference cycle might happen and how to prevent it, starting with the definition of the `List` enum and a `tail` method in Listing -15-25. +15-25: -Filename: src/main.rs +src/main.rs ``` use crate::List::{Cons, Nil}; @@ -1642,29 +1695,30 @@ use std::rc::Rc; #[derive(Debug)] enum List { - 1 Cons(i32, RefCell>), + Cons(i32, RefCell>), Nil, } impl List { - 2 fn tail(&self) -> Option<&RefCell>> { + fn tail(&self) -> Option<&RefCell>> { match self { Cons(_, item) => Some(item), Nil => None, } } } + +fn main() {} ``` -Listing 15-25: A cons list definition that holds a `RefCell` so we can -modify what a `Cons` variant is referring to +Listing 15-25: A cons list definition that holds a `RefCell` so we can modify what a `Cons` variant is referring to We’re using another variation of the `List` definition from Listing 15-5. The -second element in the `Cons` variant is now `RefCell>` [1], meaning -that instead of having the ability to modify the `i32` value as we did in -Listing 15-24, we want to modify the `List` value a `Cons` variant is pointing -to. We’re also adding a `tail` method [2] to make it convenient for us to -access the second item if we have a `Cons` variant. +second element in the `Cons` variant is now `RefCell>`, meaning that +instead of having the ability to modify the `i32` value as we did in Listing +15-24, we want to modify the `List` value a `Cons` variant is pointing to. +We’re also adding a `tail` method to make it convenient for us to access the +second item if we have a `Cons` variant. In Listing 15-26, we’re adding a `main` function that uses the definitions in Listing 15-25. This code creates a list in `a` and a list in `b` that points to @@ -1672,61 +1726,55 @@ the list in `a`. Then it modifies the list in `a` to point to `b`, creating a reference cycle. There are `println!` statements along the way to show what the reference counts are at various points in this process. -Filename: src/main.rs +src/main.rs ``` fn main() { - 1 let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); + let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a initial rc count = {}", Rc::strong_count(&a)); println!("a next item = {:?}", a.tail()); - 2 let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); + let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); - println!( - "a rc count after b creation = {}", - Rc::strong_count(&a) - ); + println!("a rc count after b creation = {}", Rc::strong_count(&a)); println!("b initial rc count = {}", Rc::strong_count(&b)); println!("b next item = {:?}", b.tail()); - 3 if let Some(link) = a.tail() { - 4 *link.borrow_mut() = Rc::clone(&b); + if let Some(link) = a.tail() { + *link.borrow_mut() = Rc::clone(&b); } - println!( - "b rc count after changing a = {}", - Rc::strong_count(&b) - ); - println!( - "a rc count after changing a = {}", - Rc::strong_count(&a) - ); + println!("b rc count after changing a = {}", Rc::strong_count(&b)); + println!("a rc count after changing a = {}", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; - // it will overflow the stack + // it will overflow the stack. // println!("a next item = {:?}", a.tail()); } ``` -Listing 15-26: Creating a reference cycle of two `List` values pointing to each -other +Listing 15-26: Creating a reference cycle of two `List` values pointing to each other We create an `Rc` instance holding a `List` value in the variable `a` -with an initial list of `5, Nil` [1]. We then create an `Rc` instance -holding another `List` value in the variable `b` that contains the value `10` -and points to the list in `a` [2]. +with an initial list of `5, Nil`. We then create an `Rc` instance holding +another `List` value in the variable `b` that contains the value 10 and points +to the list in `a`. We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We do that by using the `tail` method to get a reference to the `RefCell>` -in `a`, which we put in the variable `link` [3]. Then we use the `borrow_mut` +in `a`, which we put in the variable `link`. Then we use the `borrow_mut` method on the `RefCell>` to change the value inside from an `Rc` -that holds a `Nil` value to the `Rc` in `b` [4]. +that holds a `Nil` value to the `Rc` in `b`. When we run this code, keeping the last `println!` commented out for the moment, we’ll get this output: ``` +$ cargo run + Compiling cons-list v0.1.0 (file:///projects/cons-list) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s + Running `target/debug/cons-list` a initial rc count = 1 a next item = Some(RefCell { value: Nil }) a rc count after b creation = 2 @@ -1736,28 +1784,31 @@ b rc count after changing a = 2 a rc count after changing a = 2 ``` -The reference count of the `Rc` instances in both `a` and `b` is 2 after +The reference count of the `Rc` instances in both `a` and `b` are 2 after we change the list in `a` to point to `b`. At the end of `main`, Rust drops the -variable `b`, which decreases the reference count of the `b` `Rc` -instance from 2 to 1. The memory that `Rc` has on the heap won’t be -dropped at this point because its reference count is 1, not 0. Then Rust drops -`a`, which decreases the reference count of the `a` `Rc` instance from 2 -to 1 as well. This instance’s memory can’t be dropped either, because the other +variable `b`, which decreases the reference count of the `b` `Rc` instance +from 2 to 1. The memory that `Rc` has on the heap won’t be dropped at +this point, because its reference count is 1, not 0. Then Rust drops `a`, which +decreases the reference count of the `a` `Rc` instance from 2 to 1 as +well. This instance’s memory can’t be dropped either, because the other `Rc` instance still refers to it. The memory allocated to the list will -remain uncollected forever. To visualize this reference cycle, we’ve created a +remain uncollected forever. To visualize this reference cycle, we’ve created the diagram in Figure 15-4. -Figure 15-4: A reference cycle of lists `a` and `b` pointing to each other +Reference cycle of lists + +Figure 15-4: A reference cycle of lists `a` and `b` +pointing to each other If you uncomment the last `println!` and run the program, Rust will try to -print this cycle with `a` pointing to `b` pointing to `a` and so forth until it -overflows the stack. - -Compared to a real-world program, the consequences of creating a reference -cycle in this example aren’t very dire: right after we create the reference -cycle, the program ends. However, if a more complex program allocated lots of -memory in a cycle and held onto it for a long time, the program would use more -memory than it needed and might overwhelm the system, causing it to run out of +print this cycle with `a` pointing to `b` pointing to `a`, and so forth, until +it overflows the stack. + +Compared to a real-world program, the consequences of creating a reference cycle +in this example aren’t very dire: right after we create the reference cycle, +the program ends. However, if a more complex program allocated lots of memory +in a cycle and held onto it for a long time, the program would use more memory +than it needed and might overwhelm the system, causing it to run out of available memory. Creating reference cycles is not easily done, but it’s not impossible either. @@ -1778,14 +1829,14 @@ Let’s look at an example using graphs made up of parent nodes and child nodes to see when non-ownership relationships are an appropriate way to prevent reference cycles. -### Preventing Reference Cycles Using Weak +### Preventing Reference Cycles: Turning an Rc into a Weak So far, we’ve demonstrated that calling `Rc::clone` increases the `strong_count` of an `Rc` instance, and an `Rc` instance is only cleaned up if its `strong_count` is 0. You can also create a *weak reference* to the value within an `Rc` instance by calling `Rc::downgrade` and passing a -reference to the `Rc`. Strong references are how you can share ownership of -an `Rc` instance. Weak references don’t express an ownership relationship, +reference to the `Rc`. *Strong references* are how you can share ownership of +an `Rc` instance. *Weak references* don’t express an ownership relationship, and their count doesn’t affect when an `Rc` instance is cleaned up. They won’t cause a reference cycle because any cycle involving some weak references will be broken once the strong reference count of values involved is 0. @@ -1798,7 +1849,7 @@ Instead of increasing the `strong_count` in the `Rc` instance by 1, calling `Rc` instance to be cleaned up. Because the value that `Weak` references might have been dropped, to do -anything with the value that a `Weak` is pointing to you must make sure the +anything with the value that a `Weak` is pointing to, you must make sure the value still exists. Do this by calling the `upgrade` method on a `Weak` instance, which will return an `Option>`. You’ll get a result of `Some` if the `Rc` value has not been dropped yet and a result of `None` if the @@ -1810,7 +1861,7 @@ As an example, rather than using a list whose items know only about the next item, we’ll create a tree whose items know about their children items *and* their parent items. -#### Creating a Tree Data Structure: A Node with Child Nodes +#### Creating a Tree Data Structure: a Node with Child Nodes To start, we’ll build a tree with nodes that know about their child nodes. We’ll create a struct named `Node` that holds its own `i32` value as well as @@ -1836,10 +1887,10 @@ modify which nodes are children of another node, so we have a `RefCell` in `children` around the `Vec>`. Next, we’ll use our struct definition and create one `Node` instance named -`leaf` with the value `3` and no children, and another instance named `branch` -with the value `5` and `leaf` as one of its children, as shown in Listing 15-27. +`leaf` with the value 3 and no children, and another instance named `branch` +with the value 5 and `leaf` as one of its children, as shown in Listing 15-27: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1855,8 +1906,7 @@ fn main() { } ``` -Listing 15-27: Creating a `leaf` node with no children and a `branch` node with -`leaf` as one of its children +Listing 15-27: Creating a `leaf` node with no children and a `branch` node with `leaf` as one of its children We clone the `Rc` in `leaf` and store that in `branch`, meaning the `Node` in `leaf` now has two owners: `leaf` and `branch`. We can get from @@ -1869,7 +1919,7 @@ parent. We’ll do that next. To make the child node aware of its parent, we need to add a `parent` field to our `Node` struct definition. The trouble is in deciding what the type of -`parent` should be. We know it can’t contain an `Rc` because that would +`parent` should be. We know it can’t contain an `Rc`, because that would create a reference cycle with `leaf.parent` pointing to `branch` and `branch.children` pointing to `leaf`, which would cause their `strong_count` values to never be 0. @@ -1879,7 +1929,7 @@ children: if a parent node is dropped, its child nodes should be dropped as well. However, a child should not own its parent: if we drop a child node, the parent should still exist. This is a case for weak references! -So, instead of `Rc`, we’ll make the type of `parent` use `Weak`, +So instead of `Rc`, we’ll make the type of `parent` use `Weak`, specifically a `RefCell>`. Now our `Node` struct definition looks like this: @@ -1897,67 +1947,61 @@ struct Node { } ``` -A node will be able to refer to its parent node but doesn’t own its parent. In -Listing 15-28, we update `main` to use this new definition so the `leaf` node -will have a way to refer to its parent, `branch`. +A node will be able to refer to its parent node but doesn’t own its parent. +In Listing 15-28, we update `main` to use this new definition so the `leaf` +node will have a way to refer to its parent, `branch`: -Filename: src/main.rs +src/main.rs ``` fn main() { let leaf = Rc::new(Node { value: 3, - 1 parent: RefCell::new(Weak::new()), + parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); - 2 println!( - "leaf parent = {:?}", - leaf.parent.borrow().upgrade() - ); + println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, - 3 parent: RefCell::new(Weak::new()), + parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); - 4 *leaf.parent.borrow_mut() = Rc::downgrade(&branch); + *leaf.parent.borrow_mut() = Rc::downgrade(&branch); - 5 println!( - "leaf parent = {:?}", - leaf.parent.borrow().upgrade() - ); + println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); } ``` -Listing 15-28: A `leaf` node with a weak reference to its parent node, `branch` +Listing 15-28: A `leaf` node with a weak reference to its parent node `branch` Creating the `leaf` node looks similar to Listing 15-27 with the exception of the `parent` field: `leaf` starts out without a parent, so we create a new, -empty `Weak` reference instance [1]. +empty `Weak` reference instance. At this point, when we try to get a reference to the parent of `leaf` by using the `upgrade` method, we get a `None` value. We see this in the output from the -first `println!` statement [2]: +first `println!` statement: ``` leaf parent = None ``` When we create the `branch` node, it will also have a new `Weak` -reference in the `parent` field [3] because `branch` doesn’t have a parent -node. We still have `leaf` as one of the children of `branch`. Once we have the +reference in the `parent` field, because `branch` doesn’t have a parent node. +We still have `leaf` as one of the children of `branch`. Once we have the `Node` instance in `branch`, we can modify `leaf` to give it a `Weak` -reference to its parent [4]. We use the `borrow_mut` method on the +reference to its parent. We use the `borrow_mut` method on the `RefCell>` in the `parent` field of `leaf`, and then we use the `Rc::downgrade` function to create a `Weak` reference to `branch` from the `Rc` in `branch`. -When we print the parent of `leaf` again [5], this time we’ll get a `Some` -variant holding `branch`: now `leaf` can access its parent! When we print -`leaf`, we also avoid the cycle that eventually ended in a stack overflow like -we had in Listing 15-26; the `Weak` references are printed as `(Weak)`: +When we print the parent of `leaf` again, this time we’ll get a `Some` variant +holding `branch`: now `leaf` can access its parent! When we print `leaf`, we +also avoid the cycle that eventually ended in a stack overflow like we had in +Listing 15-26; the `Weak` references are printed as `(Weak)`: ``` leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, @@ -1975,9 +2019,9 @@ Let’s look at how the `strong_count` and `weak_count` values of the `Rc` instances change by creating a new inner scope and moving the creation of `branch` into that scope. By doing so, we can see what happens when `branch` is created and then dropped when it goes out of scope. The modifications are shown -in Listing 15-29. +in Listing 15-29: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1987,13 +2031,13 @@ fn main() { children: RefCell::new(vec![]), }); - 1 println!( + println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); - 2 { + { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), @@ -2002,24 +2046,21 @@ fn main() { *leaf.parent.borrow_mut() = Rc::downgrade(&branch); - 3 println!( + println!( "branch strong = {}, weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch), ); - 4 println!( + println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); - 5 } + } - 6 println!( - "leaf parent = {:?}", - leaf.parent.borrow().upgrade() - ); - 7 println!( + println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); + println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), @@ -2027,27 +2068,26 @@ fn main() { } ``` -Listing 15-29: Creating `branch` in an inner scope and examining strong and -weak reference counts +Listing 15-29: Creating `branch` in an inner scope and examining strong and weak reference counts After `leaf` is created, its `Rc` has a strong count of 1 and a weak -count of 0 [1]. In the inner scope [2], we create `branch` and associate it -with `leaf`, at which point when we print the counts [3], the `Rc` in -`branch` will have a strong count of 1 and a weak count of 1 (for `leaf.parent` -pointing to `branch` with a `Weak`). When we print the counts in `leaf` -[4], we’ll see it will have a strong count of 2 because `branch` now has a -clone of the `Rc` of `leaf` stored in `branch.children`, but will still -have a weak count of 0. - -When the inner scope ends [5], `branch` goes out of scope and the strong count -of the `Rc` decreases to 0, so its `Node` is dropped. The weak count of 1 +count of 0. In the inner scope, we create `branch` and associate it with +`leaf`, at which point, when we print the counts, the `Rc` in `branch` +will have a strong count of 1 and a weak count of 1 (for `leaf.parent` pointing +to `branch` with a `Weak`). When we print the counts in `leaf`, we’ll see +it will have a strong count of 2, because `branch` now has a clone of the +`Rc` of `leaf` stored in `branch.children`, but will still have a weak +count of 0. + +When the inner scope ends, `branch` goes out of scope and the strong count of +the `Rc` decreases to 0, so its `Node` is dropped. The weak count of 1 from `leaf.parent` has no bearing on whether or not `Node` is dropped, so we don’t get any memory leaks! If we try to access the parent of `leaf` after the end of the scope, we’ll get -`None` again [6]. At the end of the program [7], the `Rc` in `leaf` has a -strong count of 1 and a weak count of 0 because the variable `leaf` is now the -only reference to the `Rc` again. +`None` again. At the end of the program, the `Rc` in `leaf` has a strong +count of 1 and a weak count of 0, because the variable `leaf` is now the only +reference to the `Rc` again. All of the logic that manages the counts and value dropping is built into `Rc` and `Weak` and their implementations of the `Drop` trait. By @@ -2072,9 +2112,8 @@ functionality of smart pointers. We explored reference cycles that can cause memory leaks and how to prevent them using `Weak`. If this chapter has piqued your interest and you want to implement your own -smart pointers, check out “The Rustonomicon” at -*https://doc.rust-lang.org/stable/nomicon* for more useful information. +smart pointers, check out “The Rustonomicon” at *../nomicon/index.html* for more useful +information. Next, we’ll talk about concurrency in Rust. You’ll even learn about a few new smart pointers. - diff --git a/nostarch/chapter16.md b/nostarch/chapter16.md index 7c5389e4f4..96a85608a6 100644 --- a/nostarch/chapter16.md +++ b/nostarch/chapter16.md @@ -9,11 +9,11 @@ directory, so all fixes need to be made in `/src/`. # Fearless Concurrency Handling concurrent programming safely and efficiently is another of Rust’s -major goals. *Concurrent programming*, where different parts of a program -execute independently, and *parallel programming*, where different parts of a -program execute at the same time, are becoming increasingly important as more +major goals. *Concurrent programming*, in which different parts of a program +execute independently, and *parallel programming*, in which different parts of +a program execute at the same time, are becoming increasingly important as more computers take advantage of their multiple processors. Historically, -programming in these contexts has been difficult and error prone: Rust hopes to +programming in these contexts has been difficult and error prone. Rust hopes to change that. Initially, the Rust team thought that ensuring memory safety and preventing @@ -31,16 +31,16 @@ shipped to production. We’ve nicknamed this aspect of Rust *fearless* subtle bugs and is easy to refactor without introducing new bugs. > Note: For simplicity’s sake, we’ll refer to many of the problems as -*concurrent* rather than being more precise by saying *concurrent and/or -parallel*. If this book were about concurrency and/or parallelism, we’d be more -specific. For this chapter, please mentally substitute *concurrent and/or -parallel* whenever we use *concurrent*. +> *concurrent* rather than being more precise by saying *concurrent and/or +> parallel*. If this book were about concurrency and/or parallelism, we’d be +> more specific. For this chapter, please mentally substitute *concurrent +> and/or parallel* whenever we use *concurrent*. Many languages are dogmatic about the solutions they offer for handling concurrent problems. For example, Erlang has elegant functionality for message-passing concurrency but has only obscure ways to share state between threads. Supporting only a subset of possible solutions is a reasonable -strategy for higher-level languages because a higher-level language promises +strategy for higher-level languages, because a higher-level language promises benefits from giving up some control to gain abstractions. However, lower-level languages are expected to provide the solution with the best performance in any given situation and have fewer abstractions over the hardware. Therefore, Rust @@ -52,9 +52,9 @@ Here are the topics we’ll cover in this chapter: * How to create threads to run multiple pieces of code at the same time * *Message-passing* concurrency, where channels send messages between threads * *Shared-state* concurrency, where multiple threads have access to some piece -of data + of data * The `Sync` and `Send` traits, which extend Rust’s concurrency guarantees to -user-defined types as well as types provided by the standard library + user-defined types as well as types provided by the standard library ## Using Threads to Run Code Simultaneously @@ -62,7 +62,7 @@ In most current operating systems, an executed program’s code is run in a *process*, and the operating system will manage multiple processes at once. Within a program, you can also have independent parts that run simultaneously. The features that run these independent parts are called *threads*. For -example, a web server could have multiple threads so that it could respond to +example, a web server could have multiple threads so that it can respond to more than one request at the same time. Splitting the computation in your program into multiple threads to run multiple @@ -71,12 +71,12 @@ Because threads can run simultaneously, there’s no inherent guarantee about th order in which parts of your code on different threads will run. This can lead to problems, such as: -* Race conditions, where threads are accessing data or resources in an -inconsistent order -* Deadlocks, where two threads are waiting for each other, preventing both -threads from continuing +* Race conditions, in which threads are accessing data or resources in an + inconsistent order +* Deadlocks, in which two threads are waiting for each other, preventing both + threads from continuing * Bugs that happen only in certain situations and are hard to reproduce and fix -reliably + reliably Rust attempts to mitigate the negative effects of using threads, but programming in a multithreaded context still takes careful thought and requires @@ -84,20 +84,21 @@ a code structure that is different from that in programs running in a single thread. Programming languages implement threads in a few different ways, and many -operating systems provide an API the language can call for creating new -threads. The Rust standard library uses a *1:1* model of thread implementation, -whereby a program uses one operating system thread per one language thread. -There are crates that implement other models of threading that make different -trade-offs to the 1:1 model. +operating systems provide an API the language can call for creating new threads. +The Rust standard library uses a *1:1* model of thread implementation, whereby a +program uses one operating system thread per one language thread. There are +crates that implement other models of threading that make different tradeoffs to +the 1:1 model. (Rust’s async system, which we will see in the next chapter, +provides another approach to concurrency as well.) ### Creating a New Thread with spawn To create a new thread, we call the `thread::spawn` function and pass it a closure (we talked about closures in Chapter 13) containing the code we want to run in the new thread. The example in Listing 16-1 prints some text from a main -thread and other text from a new thread. +thread and other text from a new thread: -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -118,14 +119,17 @@ fn main() { } ``` -Listing 16-1: Creating a new thread to print one thing while the main thread -prints something else +Listing 16-1: Creating a new thread to print one thing while the main thread prints something else Note that when the main thread of a Rust program completes, all spawned threads are shut down, whether or not they have finished running. The output from this program might be a little different every time, but it will look similar to the following: + + ``` hi number 1 from the main thread! hi number 1 from the spawned thread! @@ -143,7 +147,7 @@ duration, allowing a different thread to run. The threads will probably take turns, but that isn’t guaranteed: it depends on how your operating system schedules the threads. In this run, the main thread printed first, even though the print statement from the spawned thread appears first in the code. And even -though we told the spawned thread to print until `i` is 9, it only got to 5 +though we told the spawned thread to print until `i` is `9`, it only got to `5` before the main thread shut down. If you run this code and only see output from the main thread, or don’t see any @@ -157,15 +161,14 @@ the time due to the main thread ending, but because there is no guarantee on the order in which threads run, we also can’t guarantee that the spawned thread will get to run at all! -We can fix the problem of the spawned thread not running or of it ending -prematurely by saving the return value of `thread::spawn` in a variable. The -return type of `thread::spawn` is `JoinHandle`. A `JoinHandle` is an -owned value that, when we call the `join` method on it, will wait for its -thread to finish. Listing 16-2 shows how to use the `JoinHandle` of the -thread we created in Listing 16-1 and call `join` to make sure the spawned -thread finishes before `main` exits. +We can fix the problem of the spawned thread not running or ending prematurely +by saving the return value of `thread::spawn` in a variable. The return type of +`thread::spawn` is `JoinHandle`. A `JoinHandle` is an owned value that, when we +call the `join` method on it, will wait for its thread to finish. Listing 16-2 +shows how to use the `JoinHandle` of the thread we created in Listing 16-1 and +how to call `join` to make sure the spawned thread finishes before `main` exits: -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -188,8 +191,7 @@ fn main() { } ``` -Listing 16-2: Saving a `JoinHandle` from `thread::spawn` to guarantee the -thread is run to completion +Listing 16-2: Saving a `JoinHandle` from `thread::spawn` to guarantee the thread is run to completion Calling `join` on the handle blocks the thread currently running until the thread represented by the handle terminates. *Blocking* a thread means that @@ -197,6 +199,10 @@ thread is prevented from performing work or exiting. Because we’ve put the cal to `join` after the main thread’s `for` loop, running Listing 16-2 should produce output similar to this: + + ``` hi number 1 from the main thread! hi number 2 from the main thread! @@ -219,7 +225,7 @@ call to `handle.join()` and does not end until the spawned thread is finished. But let’s see what happens when we instead move `handle.join()` before the `for` loop in `main`, like this: -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -242,9 +248,15 @@ fn main() { } ``` + + The main thread will wait for the spawned thread to finish and then run its `for` loop, so the output won’t be interleaved anymore, as shown here: + + ``` hi number 1 from the spawned thread! hi number 2 from the spawned thread! @@ -269,18 +281,18 @@ threads run at the same time. We’ll often use the `move` keyword with closures passed to `thread::spawn` because the closure will then take ownership of the values it uses from the environment, thus transferring ownership of those values from one thread to -another. In “Capturing the Environment with Closures” on page XX, we discussed -`move` in the context of closures. Now we’ll concentrate more on the -interaction between `move` and `thread::spawn`. +another. In “Capturing References or Moving Ownership” +in Chapter 13, we discussed `move` in the context of closures. Now, we’ll +concentrate more on the interaction between `move` and `thread::spawn`. Notice in Listing 16-1 that the closure we pass to `thread::spawn` takes no arguments: we’re not using any data from the main thread in the spawned thread’s code. To use data from the main thread in the spawned thread, the spawned thread’s closure must capture the values it needs. Listing 16-3 shows an attempt to create a vector in the main thread and use it in the spawned -thread. However, this won’t work yet, as you’ll see in a moment. +thread. However, this won’t yet work, as you’ll see in a moment. -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -289,15 +301,14 @@ fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { - println!("Here's a vector: {:?}", v); + println!("Here's a vector: {v:?}"); }); handle.join().unwrap(); } ``` -Listing 16-3: Attempting to use a vector created by the main thread in another -thread +Listing 16-3: Attempting to use a vector created by the main thread in another thread The closure uses `v`, so it will capture `v` and make it part of the closure’s environment. Because `thread::spawn` runs this closure in a new thread, we @@ -305,39 +316,42 @@ should be able to access `v` inside that new thread. But when we compile this example, we get the following error: ``` -error[E0373]: closure may outlive the current function, but it borrows `v`, -which is owned by the current function +$ cargo run + Compiling threads v0.1.0 (file:///projects/threads) +error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:6:32 | 6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v` -7 | println!("Here's a vector: {:?}", v); - | - `v` is borrowed here +7 | println!("Here's a vector: {v:?}"); + | - `v` is borrowed here | note: function requires argument type to outlive `'static` --> src/main.rs:6:18 | 6 | let handle = thread::spawn(|| { | __________________^ -7 | | println!("Here's a vector: {:?}", v); +7 | | println!("Here's a vector: {v:?}"); 8 | | }); | |______^ -help: to force the closure to take ownership of `v` (and any other referenced -variables), use the `move` keyword +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword | 6 | let handle = thread::spawn(move || { | ++++ + +For more information about this error, try `rustc --explain E0373`. +error: could not compile `threads` (bin "threads") due to 1 previous error ``` Rust *infers* how to capture `v`, and because `println!` only needs a reference to `v`, the closure tries to borrow `v`. However, there’s a problem: Rust can’t -tell how long the spawned thread will run, so it doesn’t know whether the -reference to `v` will always be valid. +tell how long the spawned thread will run, so it doesn’t know if the reference +to `v` will always be valid. Listing 16-4 provides a scenario that’s more likely to have a reference to `v` -that won’t be valid. +that won’t be valid: -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -346,7 +360,7 @@ fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { - println!("Here's a vector: {:?}", v); + println!("Here's a vector: {v:?}"); }); drop(v); // oh no! @@ -355,22 +369,24 @@ fn main() { } ``` -Listing 16-4: A thread with a closure that attempts to capture a reference to -`v` from a main thread that drops `v` +Listing 16-4: A thread with a closure that attempts to capture a reference to `v` from a main thread that drops `v` -If Rust allowed us to run this code, there’s a possibility that the spawned -thread would be immediately put in the background without running at all. The -spawned thread has a reference to `v` inside, but the main thread immediately -drops `v`, using the `drop` function we discussed in Chapter 15. Then, when the +If Rust allowed us to run this code, there’s a possibility the spawned thread +would be immediately put in the background without running at all. The spawned +thread has a reference to `v` inside, but the main thread immediately drops +`v`, using the `drop` function we discussed in Chapter 15. Then, when the spawned thread starts to execute, `v` is no longer valid, so a reference to it is also invalid. Oh no! To fix the compiler error in Listing 16-3, we can use the error message’s advice: + + ``` -help: to force the closure to take ownership of `v` (and any other referenced -variables), use the `move` keyword +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword | 6 | let handle = thread::spawn(move || { | ++++ @@ -379,9 +395,9 @@ variables), use the `move` keyword By adding the `move` keyword before the closure, we force the closure to take ownership of the values it’s using rather than allowing Rust to infer that it should borrow the values. The modification to Listing 16-3 shown in Listing -16-5 will compile and run as we intend. +16-5 will compile and run as we intend: -Filename: src/main.rs +src/main.rs ``` use std::thread; @@ -390,15 +406,14 @@ fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { - println!("Here's a vector: {:?}", v); + println!("Here's a vector: {v:?}"); }); handle.join().unwrap(); } ``` -Listing 16-5: Using the `move` keyword to force a closure to take ownership of -the values it uses +Listing 16-5: Using the `move` keyword to force a closure to take ownership of the values it uses We might be tempted to try the same thing to fix the code in Listing 16-4 where the main thread called `drop` by using a `move` closure. However, this fix will @@ -408,46 +423,48 @@ closure’s environment, and we could no longer call `drop` on it in the main thread. We would get this compiler error instead: ``` +$ cargo run + Compiling threads v0.1.0 (file:///projects/threads) error[E0382]: use of moved value: `v` --> src/main.rs:10:10 | 4 | let v = vec![1, 2, 3]; - | - move occurs because `v` has type `Vec`, which does not -implement the `Copy` trait + | - move occurs because `v` has type `Vec`, which does not implement the `Copy` trait 5 | 6 | let handle = thread::spawn(move || { | ------- value moved into closure here -7 | println!("Here's a vector: {:?}", v); - | - variable moved due to use in -closure +7 | println!("Here's a vector: {v:?}"); + | - variable moved due to use in closure ... 10 | drop(v); // oh no! | ^ value used here after move + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `threads` (bin "threads") due to 1 previous error ``` Rust’s ownership rules have saved us again! We got an error from the code in Listing 16-3 because Rust was being conservative and only borrowing `v` for the thread, which meant the main thread could theoretically invalidate the spawned thread’s reference. By telling Rust to move ownership of `v` to the spawned -thread, we’re guaranteeing Rust that the main thread won’t use `v` anymore. If -we change Listing 16-4 in the same way, we’re then violating the ownership +thread, we’re guaranteeing to Rust that the main thread won’t use `v` anymore. +If we change Listing 16-4 in the same way, we’re then violating the ownership rules when we try to use `v` in the main thread. The `move` keyword overrides Rust’s conservative default of borrowing; it doesn’t let us violate the ownership rules. -Now that we’ve covered what threads are and the methods supplied by the thread -API, let’s look at some situations in which we can use threads. +With a basic understanding of threads and the thread API, let’s look at what we +can *do* with threads. ## Using Message Passing to Transfer Data Between Threads One increasingly popular approach to ensuring safe concurrency is *message passing*, where threads or actors communicate by sending each other messages -containing data. Here’s the idea in a slogan from the Go language documentation -at *https://golang.org/doc/effective_go.html#concurrency*: “Do not communicate -by sharing memory; instead, share memory by communicating.” +containing data. Here’s the idea in a slogan from the Go language documentation at *https://golang.org/doc/effective_go.html#concurrency*: +“Do not communicate by sharing memory; instead, share memory by communicating.” To accomplish message-sending concurrency, Rust’s standard library provides an -implementation of *channels*. A channel is a general programming concept by +implementation of channels. A *channel* is a general programming concept by which data is sent from one thread to another. You can imagine a channel in programming as being like a directional channel of @@ -455,7 +472,7 @@ water, such as a stream or a river. If you put something like a rubber duck into a river, it will travel downstream to the end of the waterway. A channel has two halves: a transmitter and a receiver. The transmitter half is -the upstream location where you put the rubber duck into the river, and the +the upstream location where you put rubber ducks into the river, and the receiver half is where the rubber duck ends up downstream. One part of your code calls methods on the transmitter with the data you want to send, and another part checks the receiving end for arriving messages. A channel is said @@ -465,9 +482,9 @@ Here, we’ll work up to a program that has one thread to generate values and send them down a channel, and another thread that will receive the values and print them out. We’ll be sending simple values between threads using a channel to illustrate the feature. Once you’re familiar with the technique, you could -use channels for any threads that need to communicate with each other, such as -a chat system or a system where many threads perform parts of a calculation and -send the parts to one thread that aggregates the results. +use channels for any threads that need to communicate between each other, such +as a chat system or a system where many threads perform parts of a calculation +and send the parts to one thread that aggregates the results. First, in Listing 16-6, we’ll create a channel but not do anything with it. Note that this won’t compile yet because Rust can’t tell what type of values we @@ -483,7 +500,8 @@ fn main() { } ``` -Listing 16-6: Creating a channel and assigning the two halves to `tx` and `rx` +Listing 16-6: Creating a channel and assigning the two +halves to `tx` and `rx` We create a new channel using the `mpsc::channel` function; `mpsc` stands for *multiple producer, single consumer*. In short, the way Rust’s standard library @@ -495,21 +513,21 @@ producer for now, but we’ll add multiple producers when we get this example working. The `mpsc::channel` function returns a tuple, the first element of which is the -sending end—the transmitter—and the second element of which is the receiving -end—the receiver. The abbreviations `tx` and `rx` are traditionally used in -many fields for *transmitter* and *receiver*, respectively, so we name our -variables as such to indicate each end. We’re using a `let` statement with a -pattern that destructures the tuples; we’ll discuss the use of patterns in -`let` statements and destructuring in Chapter 18. For now, know that using a -`let` statement in this way is a convenient approach to extract the pieces of -the tuple returned by `mpsc::channel`. +sending end—the transmitter—and the second element is the receiving end—the +receiver. The abbreviations `tx` and `rx` are traditionally used in many fields +for *transmitter* and *receiver* respectively, so we name our variables as such +to indicate each end. We’re using a `let` statement with a pattern that +destructures the tuples; we’ll discuss the use of patterns in `let` statements +and destructuring in Chapter 19. For now, know that using a `let` statement +this way is a convenient approach to extract the pieces of the tuple returned +by `mpsc::channel`. Let’s move the transmitting end into a spawned thread and have it send one -string so the spawned thread is communicating with the main thread, as shown in -Listing 16-7. This is like putting a rubber duck in the river upstream or -sending a chat message from one thread to another. +string so that the spawned thread is communicating with the main thread, as +shown in Listing 16-7. This is like putting a rubber duck in the river upstream +or sending a chat message from one thread to another. -Filename: src/main.rs +src/main.rs ``` use std::sync::mpsc; @@ -525,10 +543,10 @@ fn main() { } ``` -Listing 16-7: Moving `tx` to a spawned thread and sending `"hi"` +Listing 16-7: Moving `tx` to a spawned thread and sending “hi” Again, we’re using `thread::spawn` to create a new thread and then using `move` -to move `tx` into the closure so the spawned thread owns `tx`. The spawned +to move `tx` into the closure so that the spawned thread owns `tx`. The spawned thread needs to own the transmitter to be able to send messages through the channel. @@ -543,7 +561,7 @@ In Listing 16-8, we’ll get the value from the receiver in the main thread. Thi is like retrieving the rubber duck from the water at the end of the river or receiving a chat message. -Filename: src/main.rs +src/main.rs ``` use std::sync::mpsc; @@ -562,7 +580,7 @@ fn main() { } ``` -Listing 16-8: Receiving the value `"hi"` in the main thread and printing it +Listing 16-8: Receiving the value “hi” in the main thread and printing it The receiver has two useful methods: `recv` and `try_recv`. We’re using `recv`, short for *receive*, which will block the main thread’s execution and wait @@ -585,6 +603,10 @@ thread is appropriate. When we run the code in Listing 16-8, we’ll see the value printed from the main thread: + + ``` Got: hi ``` @@ -599,9 +621,9 @@ advantage of thinking about ownership throughout your Rust programs. Let’s do an experiment to show how channels and ownership work together to prevent problems: we’ll try to use a `val` value in the spawned thread *after* we’ve sent it down the channel. Try compiling the code in Listing 16-9 to see why -this code isn’t allowed. +this code isn’t allowed: -Filename: src/main.rs +src/main.rs ``` use std::sync::mpsc; @@ -631,20 +653,26 @@ unexpected results due to inconsistent or nonexistent data. However, Rust gives us an error if we try to compile the code in Listing 16-9: ``` +$ cargo run + Compiling message-passing v0.1.0 (file:///projects/message-passing) error[E0382]: borrow of moved value: `val` - --> src/main.rs:10:31 + --> src/main.rs:10:26 | 8 | let val = String::from("hi"); - | --- move occurs because `val` has type `String`, which does -not implement the `Copy` trait + | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait 9 | tx.send(val).unwrap(); | --- value moved here 10 | println!("val is {val}"); - | ^^^ value borrowed here after move + | ^^^^^ value borrowed here after move + | + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `message-passing` (bin "message-passing") due to 1 previous error ``` -Our concurrency mistake has caused a compile-time error. The `send` function -takes ownership of its parameter, and when the value is moved the receiver +Our concurrency mistake has caused a compile time error. The `send` function +takes ownership of its parameter, and when the value is moved, the receiver takes ownership of it. This stops us from accidentally using the value again after sending it; the ownership system checks that everything is okay. @@ -656,7 +684,7 @@ two separate threads were talking to each other over the channel. In Listing running concurrently: the spawned thread will now send multiple messages and pause for a second between each message. -Filename: src/main.rs +src/main.rs ``` use std::sync::mpsc; @@ -686,19 +714,23 @@ fn main() { } ``` -Listing 16-10: Sending multiple messages and pausing between each one +Listing 16-10: Sending multiple messages and pausing between each This time, the spawned thread has a vector of strings that we want to send to the main thread. We iterate over them, sending each individually, and pause between each by calling the `thread::sleep` function with a `Duration` value of -one second. +1 second. In the main thread, we’re not calling the `recv` function explicitly anymore: instead, we’re treating `rx` as an iterator. For each value received, we’re printing it. When the channel is closed, iteration will end. When running the code in Listing 16-10, you should see the following output -with a one-second pause in between each line: +with a 1-second pause in between each line: + + ``` Got: hi @@ -713,52 +745,52 @@ the spawned thread. ### Creating Multiple Producers by Cloning the Transmitter -Earlier we mentioned that `mpsc` was an acronym for *multiple producer, single -consumer*. Let’s put `mpsc` to use and expand the code in Listing 16-10 to -create multiple threads that all send values to the same receiver. We can do so -by cloning the transmitter, as shown in Listing 16-11. +Earlier we mentioned that `mpsc` was an acronym for *multiple producer, +single consumer*. Let’s put `mpsc` to use and expand the code in Listing 16-10 +to create multiple threads that all send values to the same receiver. We can do +so by cloning the transmitter, as shown in Listing 16-11: -Filename: src/main.rs +src/main.rs ``` ---snip-- - -let (tx, rx) = mpsc::channel(); - -let tx1 = tx.clone(); -thread::spawn(move || { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("thread"), - ]; - - for val in vals { - tx1.send(val).unwrap(); - thread::sleep(Duration::from_secs(1)); - } -}); + // --snip-- -thread::spawn(move || { - let vals = vec![ - String::from("more"), - String::from("messages"), - String::from("for"), - String::from("you"), - ]; + let (tx, rx) = mpsc::channel(); - for val in vals { - tx.send(val).unwrap(); - thread::sleep(Duration::from_secs(1)); - } -}); + let tx1 = tx.clone(); + thread::spawn(move || { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("thread"), + ]; -for received in rx { - println!("Got: {received}"); -} + for val in vals { + tx1.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + thread::spawn(move || { + let vals = vec![ + String::from("more"), + String::from("messages"), + String::from("for"), + String::from("you"), + ]; + + for val in vals { + tx.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + for received in rx { + println!("Got: {received}"); + } ---snip-- + // --snip-- ``` Listing 16-11: Sending multiple messages from multiple producers @@ -770,6 +802,10 @@ This gives us two threads, each sending different messages to the one receiver. When you run the code, your output should look something like this: + + ``` Got: hi Got: more @@ -791,17 +827,17 @@ concurrency. ## Shared-State Concurrency -Message passing is a fine way to handle concurrency, but it’s not the only way. -Another method would be for multiple threads to access the same shared data. -Consider this part of the slogan from the Go language documentation again: “Do -not communicate by sharing memory.” +Message passing is a fine way of handling concurrency, but it’s not the only +one. Another method would be for multiple threads to access the same shared +data. Consider this part of the slogan from the Go language documentation +again: “do not communicate by sharing memory.” What would communicating by sharing memory look like? In addition, why would message-passing enthusiasts caution not to use memory sharing? -In a way, channels in any programming language are similar to single ownership +In a way, channels in any programming language are similar to single ownership, because once you transfer a value down a channel, you should no longer use that -value. Shared-memory concurrency is like multiple ownership: multiple threads +value. Shared memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time. As you saw in Chapter 15, where smart pointers made multiple ownership possible, multiple ownership can add complexity because these different owners need managing. Rust’s type system @@ -811,7 +847,7 @@ for shared memory. ### Using Mutexes to Allow Access to Data from One Thread at a Time -*Mutex* is an abbreviation for *mutual exclusion*, as in a mutex allows only +*Mutex* is an abbreviation for *mutual exclusion*, as in, a mutex allows only one thread to access some data at any given time. To access the data in a mutex, a thread must first signal that it wants access by asking to acquire the mutex’s *lock*. The lock is a data structure that is part of the mutex that @@ -823,7 +859,7 @@ remember two rules: 1. You must attempt to acquire the lock before using the data. 1. When you’re done with the data that the mutex guards, you must unlock the -data so other threads can acquire the lock. + data so other threads can acquire the lock. For a real-world metaphor for a mutex, imagine a panel discussion at a conference with only one microphone. Before a panelist can speak, they have to @@ -841,32 +877,31 @@ system and ownership rules, you can’t get locking and unlocking wrong. #### The API of Mutex As an example of how to use a mutex, let’s start by using a mutex in a -single-threaded context, as shown in Listing 16-12. +single-threaded context, as shown in Listing 16-12: -Filename: src/main.rs +src/main.rs ``` use std::sync::Mutex; fn main() { - 1 let m = Mutex::new(5); + let m = Mutex::new(5); { - 2 let mut num = m.lock().unwrap(); - 3 *num = 6; - 4 } + let mut num = m.lock().unwrap(); + *num = 6; + } - 5 println!("m = {:?}", m); + println!("m = {m:?}"); } ``` -Listing 16-12: Exploring the API of `Mutex` in a single-threaded context for -simplicity +Listing 16-12: Exploring the API of `Mutex` in a single-threaded context for simplicity -As with many types, we create a `Mutex` using the associated function `new` -[1]. To access the data inside the mutex, we use the `lock` method to acquire -the lock [2]. This call will block the current thread so it can’t do any work -until it’s our turn to have the lock. +As with many types, we create a `Mutex` using the associated function `new`. +To access the data inside the mutex, we use the `lock` method to acquire the +lock. This call will block the current thread so it can’t do any work until +it’s our turn to have the lock. The call to `lock` would fail if another thread holding the lock panicked. In that case, no one would ever be able to get the lock, so we’ve chosen to @@ -884,94 +919,108 @@ to `lock` *returns* a smart pointer called `MutexGuard`, wrapped in a `LockResult` that we handled with the call to `unwrap`. The `MutexGuard` smart pointer implements `Deref` to point at our inner data; the smart pointer also has a `Drop` implementation that releases the lock automatically when a -`MutexGuard` goes out of scope, which happens at the end of the inner scope -[4]. As a result, we don’t risk forgetting to release the lock and blocking the -mutex from being used by other threads because the lock release happens +`MutexGuard` goes out of scope, which happens at the end of the inner scope. As +a result, we don’t risk forgetting to release the lock and blocking the mutex +from being used by other threads, because the lock release happens automatically. After dropping the lock, we can print the mutex value and see that we were able -to change the inner `i32` to `6` [5]. +to change the inner `i32` to 6. #### Sharing a Mutex Between Multiple Threads -Now let’s try to share a value between multiple threads using `Mutex`. We’ll -spin up 10 threads and have them each increment a counter value by 1, so the -counter goes from 0 to 10. The example in Listing 16-13 will have a compiler -error, and we’ll use that error to learn more about using `Mutex` and how -Rust helps us use it correctly. +Now, let’s try to share a value between multiple threads using `Mutex`. +We’ll spin up 10 threads and have them each increment a counter value by 1, so +the counter goes from 0 to 10. The next example in Listing 16-13 will have +a compiler error, and we’ll use that error to learn more about using +`Mutex` and how Rust helps us use it correctly. -Filename: src/main.rs +src/main.rs ``` use std::sync::Mutex; use std::thread; fn main() { - 1 let counter = Mutex::new(0); + let counter = Mutex::new(0); let mut handles = vec![]; - 2 for _ in 0..10 { - 3 let handle = thread::spawn(move || { - 4 let mut num = counter.lock().unwrap(); + for _ in 0..10 { + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); - 5 *num += 1; + *num += 1; }); - 6 handles.push(handle); + handles.push(handle); } for handle in handles { - 7 handle.join().unwrap(); + handle.join().unwrap(); } - 8 println!("Result: {}", *counter.lock().unwrap()); + println!("Result: {}", *counter.lock().unwrap()); } ``` -Listing 16-13: Ten threads, each incrementing a counter guarded by a `Mutex` +Listing 16-13: Ten threads each increment a counter guarded by a `Mutex` -We create a `counter` variable to hold an `i32` inside a `Mutex` [1], as we -did in Listing 16-12. Next, we create 10 threads by iterating over a range of -numbers [2]. We use `thread::spawn` and give all the threads the same closure: -one that moves the counter into the thread [3], acquires a lock on the -`Mutex` by calling the `lock` method [4], and then adds 1 to the value in -the mutex [5]. When a thread finishes running its closure, `num` will go out of -scope and release the lock so another thread can acquire it. +We create a `counter` variable to hold an `i32` inside a `Mutex`, as we did +in Listing 16-12. Next, we create 10 threads by iterating over a range of +numbers. We use `thread::spawn` and give all the threads the same closure: one +that moves the counter into the thread, acquires a lock on the `Mutex` by +calling the `lock` method, and then adds 1 to the value in the mutex. When a +thread finishes running its closure, `num` will go out of scope and release the +lock so another thread can acquire it. -In the main thread, we collect all the join handles [6]. Then, as we did in -Listing 16-2, we call `join` on each handle to make sure all the threads finish -[7]. At that point, the main thread will acquire the lock and print the result -of this program [8]. +In the main thread, we collect all the join handles. Then, as we did in Listing +16-2, we call `join` on each handle to make sure all the threads finish. At +that point, the main thread will acquire the lock and print the result of this +program. We hinted that this example wouldn’t compile. Now let’s find out why! ``` -error[E0382]: use of moved value: `counter` - --> src/main.rs:9:36 +$ cargo run + Compiling shared-state v0.1.0 (file:///projects/shared-state) +error[E0382]: borrow of moved value: `counter` + --> src/main.rs:21:29 | 5 | let counter = Mutex::new(0); - | ------- move occurs because `counter` has type `Mutex`, which -does not implement the `Copy` trait + | ------- move occurs because `counter` has type `Mutex`, which does not implement the `Copy` trait ... +8 | for _ in 0..10 { + | -------------- inside of this loop 9 | let handle = thread::spawn(move || { - | ^^^^^^^ value moved into closure here, -in previous iteration of loop -10 | let mut num = counter.lock().unwrap(); - | ------- use occurs due to use in closure + | ------- value moved into closure here, in previous iteration of loop +... +21 | println!("Result: {}", *counter.lock().unwrap()); + | ^^^^^^^ value borrowed here after move + | +help: consider moving the expression out of the loop so it is only moved once + | +8 ~ let mut value = counter.lock(); +9 ~ for _ in 0..10 { +10 | let handle = thread::spawn(move || { +11 ~ let mut num = value.unwrap(); + | + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `shared-state` (bin "shared-state") due to 1 previous error ``` The error message states that the `counter` value was moved in the previous -iteration of the loop. Rust is telling us that we can’t move the ownership of -lock `counter` into multiple threads. Let’s fix the compiler error with the +iteration of the loop. Rust is telling us that we can’t move the ownership +of `counter` into multiple threads. Let’s fix the compiler error with a multiple-ownership method we discussed in Chapter 15. #### Multiple Ownership with Multiple Threads -In Chapter 15, we gave a value to multiple owners by using the smart pointer +In Chapter 15, we gave a value multiple owners by using the smart pointer `Rc` to create a reference counted value. Let’s do the same here and see what happens. We’ll wrap the `Mutex` in `Rc` in Listing 16-14 and clone the `Rc` before moving ownership to the thread. -Filename: src/main.rs +src/main.rs ``` use std::rc::Rc; @@ -1000,38 +1049,51 @@ fn main() { } ``` -Listing 16-14: Attempting to use `Rc` to allow multiple threads to own the -`Mutex` +Listing 16-14: Attempting to use `Rc` to allow multiple threads to own the `Mutex` Once again, we compile and get… different errors! The compiler is teaching us a lot. ``` -error[E0277]: `Rc>` cannot be sent between threads safely 1 - --> src/main.rs:11:22 +$ cargo run + Compiling shared-state v0.1.0 (file:///projects/shared-state) +error[E0277]: `Rc>` cannot be sent between threads safely + --> src/main.rs:11:36 | 11 | let handle = thread::spawn(move || { - | ______________________^^^^^^^^^^^^^_- + | ------------- ^------ + | | | + | ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}` | | | - | | `Rc>` cannot be sent between threads -safely + | | required by a bound introduced by this call 12 | | let mut num = counter.lock().unwrap(); 13 | | 14 | | *num += 1; 15 | | }); - | |_________- within this `[closure@src/main.rs:11:36: 15:10]` + | |_________^ `Rc>` cannot be sent between threads safely + | + = help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc>`, which is required by `{closure@src/main.rs:11:36: 11:43}: Send` +note: required because it's used within this closure + --> src/main.rs:11:36 | -= help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not -implemented for `Rc>` 2 - = note: required because it appears within the type -`[closure@src/main.rs:11:36: 15:10]` +11 | let handle = thread::spawn(move || { + | ^^^^^^^ note: required by a bound in `spawn` + --> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/std/src/thread/mod.rs:675:8 + | +672 | pub fn spawn(f: F) -> JoinHandle + | ----- required by a bound in this function +... +675 | F: Send + 'static, + | ^^^^ required by this bound in `spawn` + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `shared-state` (bin "shared-state") due to 1 previous error ``` Wow, that error message is very wordy! Here’s the important part to focus on: -``Rc>` cannot be sent between threads safely` [1]. The compiler is -also telling us the reason why: `the trait `Send` is not implemented for -`Rc>`` [2]. We’ll talk about `Send` in the next section: it’s one of +`` `Rc>` cannot be sent between threads safely ``. The compiler is +also telling us the reason why: `` the trait `Send` is not implemented for `Rc>` ``. We’ll talk about `Send` in the next section: it’s one of the traits that ensures the types we use with threads are meant for use in concurrent situations. @@ -1041,18 +1103,18 @@ subtracts from the count when each clone is dropped. But it doesn’t use any concurrency primitives to make sure that changes to the count can’t be interrupted by another thread. This could lead to wrong counts—subtle bugs that could in turn lead to memory leaks or a value being dropped before we’re done -with it. What we need is a type exactly like `Rc` but one that makes changes -to the reference count in a thread-safe way. +with it. What we need is a type that is exactly like `Rc` but one that makes +changes to the reference count in a thread-safe way. #### Atomic Reference Counting with Arc Fortunately, `Arc` *is* a type like `Rc` that is safe to use in concurrent situations. The *a* stands for *atomic*, meaning it’s an *atomically -reference counted* type. Atomics are an additional kind of concurrency +reference-counted* type. Atomics are an additional kind of concurrency primitive that we won’t cover in detail here: see the standard library -documentation for `std::sync::atomic` for more details. At this point, you just -need to know that atomics work like primitive types but are safe to share -across threads. +documentation for `std::sync::atomic` for more +details. At this point, you just need to know that atomics work like primitive +types but are safe to share across threads. You might then wonder why all primitive types aren’t atomic and why standard library types aren’t implemented to use `Arc` by default. The reason is that @@ -1063,9 +1125,9 @@ guarantees atomics provide. Let’s return to our example: `Arc` and `Rc` have the same API, so we fix our program by changing the `use` line, the call to `new`, and the call to -`clone`. The code in Listing 16-15 will finally compile and run. +`clone`. The code in Listing 16-15 will finally compile and run: -Filename: src/main.rs +src/main.rs ``` use std::sync::{Arc, Mutex}; @@ -1093,11 +1155,14 @@ fn main() { } ``` -Listing 16-15: Using an `Arc` to wrap the `Mutex` to be able to share -ownership across multiple threads +Listing 16-15: Using an `Arc` to wrap the `Mutex` to be able to share ownership across multiple threads This code will print the following: + + ``` Result: 10 ``` @@ -1111,20 +1176,20 @@ thread update the final result with its part. Note that if you are doing simple numerical operations, there are types simpler than `Mutex` types provided by the `std::sync::atomic` module of the -standard library. These types provide safe, concurrent, atomic access to -primitive types. We chose to use `Mutex` with a primitive type for this -example so we could concentrate on how `Mutex` works. +standard library. These types provide safe, concurrent, +atomic access to primitive types. We chose to use `Mutex` with a primitive +type for this example so we could concentrate on how `Mutex` works. ### Similarities Between RefCell/Rc and Mutex/Arc -You might have noticed that `counter` is immutable but we could get a mutable +You might have noticed that `counter` is immutable, but we could get a mutable reference to the value inside it; this means `Mutex` provides interior mutability, as the `Cell` family does. In the same way we used `RefCell` in Chapter 15 to allow us to mutate contents inside an `Rc`, we use `Mutex` to mutate contents inside an `Arc`. Another detail to note is that Rust can’t protect you from all kinds of logic -errors when you use `Mutex`. Recall in Chapter 15 that using `Rc` came +errors when you use `Mutex`. Recall from Chapter 15 that using `Rc` came with the risk of creating reference cycles, where two `Rc` values refer to each other, causing memory leaks. Similarly, `Mutex` comes with the risk of creating *deadlocks*. These occur when an operation needs to lock two resources @@ -1138,7 +1203,7 @@ useful information. We’ll round out this chapter by talking about the `Send` and `Sync` traits and how we can use them with custom types. -## Extensible Concurrency with the Send and Sync Traits +## Extensible Concurrency with the Sync and Send Traits Interestingly, the Rust language has *very* few concurrency features. Almost every concurrency feature we’ve talked about so far in this chapter has been @@ -1147,7 +1212,7 @@ concurrency are not limited to the language or the standard library; you can write your own concurrency features or use those written by others. However, two concurrency concepts are embedded in the language: the -`std::marker` traits `Send` and `Sync` . +`std::marker` traits `Sync` and `Send`. ### Allowing Transference of Ownership Between Threads with Send @@ -1162,13 +1227,12 @@ performance penalty. Therefore, Rust’s type system and trait bounds ensure that you can never accidentally send an `Rc` value across threads unsafely. When we tried to do -this in Listing 16-14, we got the error `the trait `Send` is not implemented -for `Rc>``. When we switched to `Arc`, which is `Send`, the code +this in Listing 16-14, we got the error `the trait Send is not implemented for Rc>`. When we switched to `Arc`, which is `Send`, the code compiled. Any type composed entirely of `Send` types is automatically marked as `Send` as well. Almost all primitive types are `Send`, aside from raw pointers, which -we’ll discuss in Chapter 19. +we’ll discuss in Chapter 20. ### Allowing Access from Multiple Threads with Sync @@ -1183,7 +1247,8 @@ The smart pointer `Rc` is also not `Sync` for the same reasons that it’s no family of related `Cell` types are not `Sync`. The implementation of borrow checking that `RefCell` does at runtime is not thread-safe. The smart pointer `Mutex` is `Sync` and can be used to share access with multiple -threads, as you saw in “Sharing a Mutex Between Multiple Threads” on page XX. +threads as you saw in “Sharing a `Mutex` Between Multiple +Threads”. ### Implementing Send and Sync Manually Is Unsafe @@ -1193,17 +1258,18 @@ marker traits, they don’t even have any methods to implement. They’re just useful for enforcing invariants related to concurrency. Manually implementing these traits involves implementing unsafe Rust code. -We’ll talk about using unsafe Rust code in Chapter 19; for now, the important +We’ll talk about using unsafe Rust code in Chapter 20; for now, the important information is that building new concurrent types not made up of `Send` and `Sync` parts requires careful thought to uphold the safety guarantees. “The -Rustonomicon” at *https://doc.rust-lang.org/stable/nomicon* has more -information about these guarantees and how to uphold them. +Rustonomicon” at *../nomicon/index.html* has more information about these guarantees and how to +uphold them. ## Summary -This isn’t the last you’ll see of concurrency in this book: the project in -Chapter 20 will use the concepts in this chapter in a more realistic situation -than the smaller examples discussed here. +This isn’t the last you’ll see of concurrency in this book: the whole next +chapter focuses on async programming, and the project in Chapter 21 will use the +concepts in this chapter in a more realistic situation than the smaller examples +discussed here. As mentioned earlier, because very little of how Rust handles concurrency is part of the language, many concurrency solutions are implemented as crates. @@ -1219,8 +1285,3 @@ Once you get your code to compile, you can rest assured that it will happily run on multiple threads without the kinds of hard-to-track-down bugs common in other languages. Concurrent programming is no longer a concept to be afraid of: go forth and make your programs concurrent, fearlessly! - -Next, we’ll talk about idiomatic ways to model problems and structure solutions -as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idioms -relate to those you might be familiar with from object-oriented programming. - diff --git a/nostarch/chapter17.md b/nostarch/chapter17.md index 225b0a8a42..488036ac76 100644 --- a/nostarch/chapter17.md +++ b/nostarch/chapter17.md @@ -1,115 +1,137 @@ + + [TOC] -## Async and Await - -Many operations we ask the computer to do can take a while to finish. For -example, if you used a video editor to create a video of a family celebration, -exporting it could take anywhere from minutes to hours. Similarly, downloading a -video shared by someone in your family might take a long time. It would be nice -if we could do something else while we are waiting for those long-running -processes to complete. - -The video export will use as much CPU and GPU power as it can. If you only had -one CPU core, and your operating system never paused that export until it -completed, you couldn’t do anything else on your computer while it was running. -That would be a pretty frustrating experience, though. Instead, your computer’s -operating system can—and does!—invisibly interrupt the export often enough to -let you get other work done along the way. - -The file download is different. It does not take up very much CPU time. Instead, -the CPU needs to wait on data to arrive from the network. While you can start -reading the data once some of it is present, it might take a while for the rest -to show up. Even once the data is all present, a video can be quite large, so it -might take some time to load it all. Maybe it only takes a second or two—but -that’s a very long time for a modern processor, which can do billions of -operations every second. It would be nice to be able to put the CPU to use for -other work while waiting for the network call to finish—so, again, your -operating system will invisibly interrupt your program so other things can -happen while the network operation is still ongoing. - -> Note: The video export is the kind of operation which is often described as -> “CPU-bound” or “compute-bound”. It’s limited by the speed of the computer’s -> ability to process data within the *CPU* or *GPU*, and how much of that speed -> it can use. The video download is the kind of operation which is often -> described as “IO-bound,” because it’s limited by the speed of the computer’s -> *input and output*. It can only go as fast as the data can be sent across the -> network. +# Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams + +Many operations we ask the computer to do can take a while to finish. It would +be nice if we could do something else while we are waiting for those +long-running processes to complete. Modern computers offer two techniques for +working on more than one operation at a time: parallelism and concurrency. Once +we start writing programs that involve parallel or concurrent operations, +though, we quickly encounter new challenges inherent to *asynchronous +programming*, where operations may not finish sequentially in the order they +were started. This chapter builds on Chapter 16’s use of threads for parallelism +and concurrency by introducing an alternative approach to asynchronous +programming: Rust’s Futures, Streams, the `async` and `await` syntax that +supports them, and the tools for managing and coordinating between asynchronous +operations. + +Let’s consider an example. Say you’re exporting a video you’ve created of a +family celebration, an operation that could take anywhere from minutes to hours. +The video export will use as much CPU and GPU power as it can. If you had only +one CPU core and your operating system didn’t pause that export until it +completed—that is, if it executed the export *synchronously*—you couldn’t do +anything else on your computer while that task was running. That would be a +pretty frustrating experience. Fortunately, your computer’s operating system +can, and does, invisibly interrupt the export often enough to let you get other +work done simultaneously. + +Now say you’re downloading a video shared by someone else, which can also take a +while but does not take up as much CPU time. In this case, the CPU has to wait +for data to arrive from the network. While you can start reading the data once +it starts to arrive, it might take some time for all of it to show up. Even once +the data is all present, if the video is quite large, it could take at least a +second or two to load it all. That might not sound like much, but it’s a very +long time for a modern processor, which can perform billions of operations every +second. Again, your operating system will invisibly interrupt your program to +allow the CPU to perform other work while waiting for the network call to +finish. + +The video export is an example of a *CPU-bound* or *compute-bound* operation. +It’s limited by the computer’s potential data processing speed within the CPU or +GPU, and how much of that speed it can dedicate to the operation. The video +download is an example of an *IO-bound* operation, because it’s limited by the +speed of the computer’s *input and output*; it can only go as fast as the data +can be sent across the network. In both of these examples, the operating system’s invisible interrupts provide a -form of concurrency. That concurrency only happens at the level of a whole +form of concurrency. That concurrency happens only at the level of the entire program, though: the operating system interrupts one program to let other programs get work done. In many cases, because we understand our programs at a -much more granular level than the operating system does, we can spot lots of -opportunities for concurrency that the operating system cannot see. +much more granular level than the operating system does, we can spot +opportunities for concurrency that the operating system can’t see. For example, if we’re building a tool to manage file downloads, we should be -able to write our program in such a way that starting one download does not lock -up the UI, and users should be able to start multiple downloads at the same -time. Many operating system APIs for interacting with the network are -*blocking*, though. That is, these APIs block the program’s progress until the -data that they are processing is completely ready. - -> Note: This is how *most* function calls work, if you think about it! However, -> we normally reserve the term “blocking” for function calls which interact with +able to write our program so that starting one download won’t lock up the UI, +and users should be able to start multiple downloads at the same time. Many +operating system APIs for interacting with the network are *blocking*, though; +that is, they block the program’s progress until the data they’re processing is +completely ready. + +> Note: This is how *most* function calls work, if you think about it. However, +> the term *blocking* is usually reserved for function calls that interact with > files, the network, or other resources on the computer, because those are the -> places where an individual program would benefit from the operation being +> cases where an individual program would benefit from the operation being > *non*-blocking. We could avoid blocking our main thread by spawning a dedicated thread to -download each file. However, we would eventually find that the overhead of those -threads was a problem. It would also be nicer if the call were not blocking in -the first place. Last but not least, it would be better if we could write in the -same direct style we use in blocking code. Something similar to this: +download each file. However, the overhead of those threads would eventually +become a problem. It would be preferable if the call didn’t block in the first +place. It would also be better if we could write in the same direct style we use +in blocking code, similar to this: ``` let data = fetch_data_from(url).await; println!("{data}"); ``` -That is exactly what Rust’s async abstraction gives us. Before we see how this -works in practice, though, we need to take a short detour into the differences -between parallelism and concurrency. +That is exactly what Rust’s *async* (short for *asynchronous*) abstraction gives +us. In this chapter, you’ll learn all about async as we cover the following +topics: + +* How to use Rust’s `async` and `await` syntax +* How to use the async model to solve some of the same challenges we looked at + in Chapter 16 +* How multithreading and async provide complementary solutions, that you can + combine in many cases + +Before we see how async works in practice, though, we need to take a short +detour to discuss the differences between parallelism and concurrency. ### Parallelism and Concurrency -In the “Fearless Concurrency” chapter on page XX, we treated parallelism and -concurrency as mostly interchangeable. Now we need to distinguish between them -more precisely, because the differences will show up as we start working. +We’ve treated parallelism and concurrency as mostly interchangeable so far. Now +we need to distinguish between them more precisely, because the differences will +show up as we start working. -Consider the different ways a team could split up work on a software project. We -could assign a single individual multiple tasks, or we could assign one task per -team member, or we could do a mix of both approaches. +Consider the different ways a team could split up work on a software project. +You could assign a single member multiple tasks, assign each member one task, or +use a mix of the two approaches. When an individual works on several different tasks before any of them is complete, this is *concurrency*. Maybe you have two different projects checked out on your computer, and when you get bored or stuck on one project, you switch to the other. You’re just one person, so you can’t make progress on both tasks -at the exact same time—but you can multi-task, making progress on multiple -tasks by switching between them. +at the exact same time, but you can multi-task, making progress on one at a time +by switching between them (see Figure 17-1). -Concurrent work flow +A diagram with boxes labeled Task A and Task B, with diamonds in them representing subtasks. There are arrows pointing from A1 to B1, B1 to A2, A2 to B2, B2 to A3, A3 to A4, and A4 to B3. The arrows between the subtasks cross the boxes between Task A and Task B. -Figure 17-1: A concurrent workflow, switching between Task A and Task B. +Figure 17-1: A concurrent workflow, switching between Task A and Task B -When you agree to split up a group of tasks between the people on the team, with -each person taking one task and working on it alone, this is *parallelism*. Each -person on the team can make progress at the exact same time. +When the team splits up a group of tasks by having each member take one task and +work on it alone, this is *parallelism*. Each person on the team can make +progress at the exact same time (see Figure 17-2). -Concurrent work flow +A diagram with boxes labeled Task A and Task B, with diamonds in them representing subtasks. There are arrows pointing from A1 to A2, A2 to A3, A3 to A4, B1 to B2, and B2 to B3. No arrows cross between the boxes for Task A and Task B. -Figure 17-2: A parallel workflow, where work happens on Task A and Task B -independently. +Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently -With both of these situations, you might have to coordinate between different -tasks. Maybe you *thought* the task that one person was working on was totally -independent from everyone else’s work, but it actually needs something finished -by another person on the team. Some of the work could be done in parallel, but -some of it was actually *serial*: it could only happen in a series, one thing -after the other, as in Figure 17-3. +In both of these workflows, you might have to coordinate between different +tasks. Maybe you *thought* the task assigned to one person was totally +independent from everyone else’s work, but it actually requires another person +on the team to finish their task first. Some of the work could be done in +parallel, but some of it was actually *serial*: it could only happen in a +series, one task after the other, as in Figure 17-3. -Concurrent work flow +A diagram with boxes labeled Task A and Task B, with diamonds in them representing subtasks. There are arrows pointing from A1 to A2, A2 to a pair of thick vertical lines like a “pause” symbol, from that symbol to A3, B1 to B2, B2 to B3, which is below that symbol, B3 to A3, and B3 to B4. -Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until task A3 is blocked on the results of task B3. +Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until Task A3 is blocked on the results of Task B3. Likewise, you might realize that one of your own tasks depends on another of your tasks. Now your concurrent work has also become serial. @@ -121,91 +143,81 @@ coworker are no longer able to work in parallel, and you’re also no longer abl to work concurrently on your own tasks. The same basic dynamics come into play with software and hardware. On a machine -with a single CPU core, the CPU can only do one operation at a time, but it can -still work concurrently. Using tools such as threads, processes, and async, the -computer can pause one activity and switch to others before eventually cycling -back to that first activity again. On a machine with multiple CPU cores, it can -also do work in parallel. One core can be doing one thing while another core -does something completely unrelated, and those actually happen at the same -time. +with a single CPU core, the CPU can perform only one operation at a time, but it +can still work concurrently. Using tools such as threads, processes, and async, +the computer can pause one activity and switch to others before eventually +cycling back to that first activity again. On a machine with multiple CPU cores, +it can also do work in parallel. One core can be performing one task while +another core performs a completely unrelated one, and those operations actually +happen at the same time. When working with async in Rust, we’re always dealing with concurrency. Depending on the hardware, the operating system, and the async runtime we are -using—more on async runtimes shortly!—that concurrency may also use parallelism +using (more on async runtimes shortly), that concurrency may also use parallelism under the hood. -Now, let’s dive into how async programming in Rust actually works! In the rest -of this chapter, we will: - -* see how to use Rust’s `async` and `await` syntax -* explore how to use the async model to solve some of the same challenges we - looked at in Chapter 16 -* look at how multithreading and async provide complementary solutions, which - you can even use together in many cases +Now, let’s dive into how async programming in Rust actually works. ## Futures and the Async Syntax The key elements of asynchronous programming in Rust are *futures* and Rust’s `async` and `await` keywords. -A *future* is a value which may not be ready now, but will become ready at some +A *future* is a value that may not be ready now but will become ready at some point in the future. (This same concept shows up in many languages, sometimes -under other names such as “task” or “promise”.) Rust provides a `Future` trait -as a building block so different async operations can be implemented with -different data structures, but with a common interface. In Rust, we say that -types which implement the `Future` trait are futures. Each type which -implements `Future` holds its own information about the progress that has been -made and what “ready” means. - -The `async` keyword can be applied to blocks and functions to specify that they +under other names such as *task* or *promise*.) Rust provides a `Future` trait +as a building block so that different async operations can be implemented with +different data structures but with a common interface. In Rust, futures are +types that implement the `Future` trait. Each future holds its own information +about the progress that has been made and what “ready” means. + +You can apply the `async` keyword to blocks and functions to specify that they can be interrupted and resumed. Within an async block or async function, you can -use the `await` keyword to wait for a future to become ready, called *awaiting a -future*. Each place you await a future within an async block or function is a -place that async block or function may get paused and resumed. The process of -checking with a future to see if its value is available yet is called *polling*. - -Some other languages also use `async` and `await` keywords for async -programming. If you’re familiar with those languages, you may notice some -significant differences in how Rust does things, including how it handles the -syntax. That’s for good reason, as we’ll see! - -Most of the time when writing async Rust, we use the `async` and `await` -keywords. Rust compiles them into equivalent code using the `Future` trait, much -as it compiles `for` loops into equivalent code using the `Iterator` trait. -Because Rust provides the `Future` trait, though, you can also implement it for -your own data types when you need to. Many of the functions we’ll see -throughout this chapter return types with their own implementations of `Future`. -We’ll return to the definition of the trait at the end of the chapter and dig -into more of how it works, but this is enough detail to keep us moving forward. - -That may all feel a bit abstract. Let’s write our first async program: a little -web scraper. We’ll pass in two URLs from the command line, fetch both of them -concurrently, and return the result of whichever one finishes first. This -example will have a fair bit of new syntax, but don’t worry. We’ll explain +use the `await` keyword to *await a future* (that is, wait for it to become +ready). Any point where you await a future within an async block or function is +a potential spot for that async block or function to pause and resume. The +process of checking with a future to see if its value is available yet is called +*polling*. + +Some other languages, such as C# and JavaScript, also use `async` and `await` +keywords for async programming. If you’re familiar with those languages, you may +notice some significant differences in how Rust does things, including how it +handles the syntax. That’s for good reason, as we’ll see! + +When writing async Rust, we use the `async` and `await` keywords most of the +time. Rust compiles them into equivalent code using the `Future` trait, much as +it compiles `for` loops into equivalent code using the `Iterator` trait. Because +Rust provides the `Future` trait, though, you can also implement it for your own +data types when you need to. Many of the functions we’ll see throughout this +chapter return types with their own implementations of `Future`. We’ll return to +the definition of the trait at the end of the chapter and dig into more of how +it works, but this is enough detail to keep us moving forward. + +This may all feel a bit abstract, so let’s write our first async program: a +little web scraper. We’ll pass in two URLs from the command line, fetch both of +them concurrently, and return the result of whichever one finishes first. This +example will have a fair bit of new syntax, but don’t worry—we’ll explain everything you need to know as we go. -### Our First Async Program +## Our First Async Program -To keep this chapter focused on learning async, rather than juggling parts of -the ecosystem, we have created the `trpl` crate (`trpl` is short for “The Rust +To keep the focus of this chapter on learning async rather than juggling parts +of the ecosystem, we’ve created the `trpl` crate (`trpl` is short for “The Rust Programming Language”). It re-exports all the types, traits, and functions -you’ll need, primarily from the `futures` and `tokio` crates, available on -*https://crates.io*. - -* The `futures` crate is an official home for Rust experimentation for async - code, and is actually where the `Future` type was originally designed. - -* Tokio is the most widely used async runtime in Rust today, especially (but - not only!) for web applications. There are other great runtimes out there, - and they may be more suitable for your purposes. We use Tokio under the hood - for `trpl` because it’s well-tested and widely used. - -In some cases, `trpl` also renames or wraps the original APIs to let us stay +you’ll need, primarily from the `futures` and +`tokio` crates. The `futures` crate is an official home +for Rust experimentation for async code, and it’s actually where the `Future` +trait was originally designed. Tokio is the most widely used async runtime in +Rust today, especially for web applications. There are other great runtimes out +there, and they may be more suitable for your purposes. We use the `tokio` crate +under the hood for `trpl` because it’s well tested and widely used. + +In some cases, `trpl` also renames or wraps the original APIs to keep you focused on the details relevant to this chapter. If you want to understand what -the crate does, we encourage you to check out its source code at -*https://github.com/rust-lang/book/tree/main/packages/trpl*. -You’ll be able to see what crate each re-export comes from, and we’ve left -extensive comments explaining what the crate does. +the crate does, we encourage you to check out its source +code. You’ll be able to see what crate each +re-export comes from, and we’ve left extensive comments explaining what the +crate does. Create a new binary project named `hello-async` and add the `trpl` crate as a dependency: @@ -217,14 +229,16 @@ $ cargo add trpl ``` Now we can use the various pieces provided by `trpl` to write our first async -program. We’ll build a little command line tool which fetches two web pages, +program. We’ll build a little command line tool that fetches two web pages, pulls the `` element from each, and prints out the title of whichever -finishes that whole process first. +page finishes that whole process first. + +### Defining the page_title Function Let’s start by writing a function that takes one page URL as a parameter, makes -a request to it, and returns the text of the title element: +a request to it, and returns the text of the title element (see Listing 17-1). -Filename: src/main.rs +src/main.rs ``` use trpl::Html; @@ -240,55 +254,55 @@ async fn page_title(url: &str) -> Option<String> { Listing 17-1: Defining an async function to get the title element from an HTML page -In Listing 17-1, we define a function named `page_title`, and we mark it with -the `async` keyword. Then we use the `trpl::get` function to fetch whatever URL -is passed in, and, and we await the response by using the `await` keyword. Then -we get the text of the response by calling its `text` method and once again -awaiting it with the `await` keyword. Both of these steps are asynchronous. For -`get`, we need to wait for the server to send back the first part of its -response, which will include HTTP headers, cookies, and so on. That part of the -response can be delivered separately from the body of the request. Especially if -the body is very large, it can take some time for it all to arrive. Thus, we -have to wait for the *entirety* of the response to arrive, so the `text` method -is also async. +First, we define a function named `page_title` and mark it with the `async` +keyword. Then we use the `trpl::get` function to fetch whatever URL is passed in +and add the `await` keyword to await the response. To get the text of the +response, we call its `text` method, and once again await it with the `await` +keyword. Both of these steps are asynchronous. For the `get` function, we have +to wait for the server to send back the first part of its response, which will +include HTTP headers, cookies, and so on, and can be delivered separately from +the response body. Especially if the body is very large, it can take some time +for it all to arrive. Because we have to wait for the *entirety* of the response +to arrive, the `text` method is also async. We have to explicitly await both of these futures, because futures in Rust are -*lazy*: they don’t do anything until you ask them to with `await`. (In fact, -Rust will show a compiler warning if you don’t use a future.) This should -remind you of our discussion of iterators back in the “Processing a Series of -Items with Iterators” section of Chapter 13 on page XX. Iterators do nothing -unless you call their `next` method—whether directly, or using `for` loops or -methods such as `map` which use `next` under the hood. With futures, the same -basic idea applies: they do nothing unless you explicitly ask them to. This -laziness allows Rust to avoid running async code until it’s actually needed. - -> Note: This is different from the behavior we saw when using `thread::spawn` in -> the “Creating a New Thread with `spawn`” section of Chapter 16 on page XX, -> where the closure we passed to another thread started running immediately. -> It’s also different from how many other languages approach async! But it’s -> important for Rust. We’ll see why that is later. - -Once we have `response_text`, we can then parse it into an instance of the -`Html` type using `Html::parse`. Instead of a raw string, we now have a data -type we can use to work with the HTML as a richer data structure. In particular, -we can use the `select_first` method to find the first instance of a given CSS -selector. By passing the string `"title"`, we’ll get the first `<title>` -element in the document, if there is one. Because there may not be any matching -element, `select_first` returns an `Option<ElementRef>`. Finally, we use the +*lazy*: they don’t do anything until you ask them to with the `await` keyword. +(In fact, Rust will show a compiler warning if you don’t use a future.) This +might remind you of Chapter 13’s discussion of iterators in the section +Processing a Series of Items With Iterators. +Iterators do nothing unless you call their `next` method—whether directly or by +using `for` loops or methods such as `map` that use `next` under the hood. +Likewise, futures do nothing unless you explicitly ask them to. This laziness +allows Rust to avoid running async code until it’s actually needed. + +> Note: This is different from the behavior we saw in the previous chapter when +> using `thread::spawn` in Creating a New Thread with +> spawn at *ch16-01-threads.html#creating-a-new-thread-with-spawn*<!--ignore-->, where the closure we passed to another +> thread started running immediately. It’s also different from how many other +> languages approach async. But it’s important for Rust, and we’ll see why +> later. + +Once we have `response_text`, we can parse it into an instance of the `Html` +type using `Html::parse`. Instead of a raw string, we now have a data type we +can use to work with the HTML as a richer data structure. In particular, we can +use the `select_first` method to find the first instance of a given CSS +selector. By passing the string `"title"`, we’ll get the first `<title>` element +in the document, if there is one. Because there may not be any matching element, +`select_first` returns an `Option<ElementRef>`. Finally, we use the `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 a `String`. When all is said and done, we have an `Option<String>`. -Notice that Rust’s `await` keyword goes after the expression you’re awaiting, -not before it. That is, it’s a *postfix keyword*. This may be different from -what you might be used to if you have used async in other languages. Rust chose -this because it makes chains of methods much nicer to work with. As a result, we -can change the body of `page_url_for` to chain the `trpl::get` and `text` -function calls together with `await` between them, as shown in Listing 17-2: +Notice that Rust’s `await` keyword goes *after* the expression you’re awaiting, +not before it. That is, it’s a *postfix* keyword. This may differ from what +you’re used to if you’ve used `async` in other languages, but in Rust it makes +chains of methods much nicer to work with. As a result, we can change the body +of `page_url_for` to chain the `trpl::get` and `text` function calls together +with `await` between them, as shown in Listing 17-2. -Filename: src/main.rs +src/main.rs ``` let response_text = trpl::get(url).await.text().await; @@ -301,15 +315,15 @@ some code in `main` to call it, let’s talk a little more about what we’ve written and what it means. When Rust sees a block marked with the `async` keyword, it compiles it into a -unique, anonymous data type which implements the `Future` trait. When Rust sees -a function marked with `async`, it compiles it into a non-async function whose -body is an async block. An async function’s return type is the type of the of -the anonymous data type the compiler creates for that async block. +unique, anonymous data type that implements the `Future` trait. When Rust sees a +function marked with `async`, it compiles it into a non-async function whose +body is an async block. An async function’s return type is the type of the +anonymous data type the compiler creates for that async block. -Thus, writing `async fn` is equivalent to writing a function which returns a -*future* of the return type. When the compiler sees a function definition such -as the `async fn page_title` in Listing 17-1, it’s equivalent to a non-async -function defined like this: +Thus, writing `async fn` is equivalent to writing a function that returns a +*future* of the return type. To the compiler, a function definition such as the +`async fn page_title` in Listing 17-1 is equivalent to a non-async function +defined like this: ``` use std::future::Future; @@ -327,37 +341,40 @@ fn page_title(url: &str) -> impl Future<Output = Option<String>> + '_ { Let’s walk through each part of the transformed version: -* It uses the `impl Trait` syntax we discussed back in the “Traits as - Parameters” section in Chapter 10 on page XX. -* The returned trait is a `Future`, with an associated type of `Output`. Notice - that the `Output` type is `Option<String>`, which is the same as the the - original return type from the `async fn` version of `page_title`. +* It uses the `impl Trait` syntax we discussed back in Chapter 10 in the + “Traits as Parameters” section. +* The returned trait is a `Future` with an associated type of `Output`. Notice + that the `Output` type is `Option<String>`, which is the same as the original + return type from the `async fn` version of `page_title`. * All of the code called in the body of the original function is wrapped in an `async move` block. Remember that blocks are expressions. This whole block is the expression returned from the function. -* This async block produces a value with the type `Option<String>`, as described - above. That value matches the `Output` type in the return type. This is just - like other blocks you have seen. +* This async block produces a value with the type `Option<String>`, as just + described. That value matches the `Output` type in the return type. This + is just like other blocks you have seen. * The new function body is an `async move` block because of how it uses the - `url` parameter. (We’ll talk about `async` vs. `async move` much more later + `url` parameter. (We’ll talk much more about `async` versus `async move` later in the chapter.) * The new version of the function has a kind of lifetime we haven’t seen before - in the output type: `'_`. Because the function returns a `Future` which refers - to a reference—in this case, the reference from the `url` parameter—we need to - tell Rust that we mean for that reference to be included. We don’t have to - name the lifetime here, because Rust is smart enough to know there is only one - reference which could be involved, but we *do* have to be explicit that the - resulting `Future` is bound by that lifetime. + in the output type: `'_`. Because the function returns a future that refers to + a reference—in this case, the reference from the `url` parameter—we need to + tell Rust that we want that reference to be included. We don’t have to name + the lifetime here, because Rust is smart enough to know there’s only one + reference that could be involved, but we *do* have to be explicit that the + resulting future is bound by that lifetime. + +Now we can call `page_title` in `main`. + +## Determining a Single Page’s Title -Now we can call `page_title` in `main`. To start, we’ll just get the title for -a single page. In Listing 17-3, we follow the same pattern we used for getting -command line arguments back in the “Accepting Command Line Arguments” section -of Chapter 12 on page XX. Then we pass the first URL `page_title`, and await -the result. Because the value produced by the future is an `Option<String>`, we -use a `match` expression to print different messages to account for whether the -page had a `<title>`. +To start, we’ll just get the title for a single page. In Listing 17-3, we follow +the same pattern we used in Chapter 12 to get command line arguments in the +Accepting Command Line Arguments section. Then we +pass the first URL `page_title` and await the result. Because the value +produced by the future is an `Option<String>`, we use a `match` expression to +print different messages to account for whether the page had a `<title>`. -Filename: src/main.rs +src/main.rs ``` async fn main() { @@ -370,13 +387,18 @@ async fn main() { } ``` -Listing 17-3: Calling the `page_title` function from `main` with a -user-supplied argument +Listing 17-3: Calling the `page_title` function from `main` with a user-supplied argument -Unfortunately, this doesn’t compile. The only place we can use the `await` +Unfortunately, this code doesn’t compile. The only place we can use the `await` keyword is in async functions or blocks, and Rust won’t let us mark the special `main` function as `async`. +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-03 +cargo build +copy just the compiler error +--> + ``` error[E0752]: `main` function is not allowed to be `async` --> src/main.rs:6:1 @@ -386,35 +408,36 @@ error[E0752]: `main` function is not allowed to be `async` ``` The reason `main` can’t be marked `async` is that async code needs a *runtime*: -a Rust crate which manages the details of executing asynchronous code. A +a Rust crate that manages the details of executing asynchronous code. A program’s `main` function can *initialize* a runtime, but it’s not a runtime -*itself*. (We’ll see more about why this is a bit later.) Every Rust program -that executes async code has at least one place where it sets up a runtime and -executes the futures. - -Most languages which support async bundle a runtime with the language. Rust does -not. Instead, there are many different async runtimes available, each of which -makes different tradeoffs suitable to the use case they target. For example, a -high-throughput web server with many CPU cores and a large amount of RAM has -very different different needs than a microcontroller with a single core, a -small amount of RAM, and no ability to do heap allocations. The crates which -provide those runtimes also often supply async versions of common functionality -such as file or network I/O. - -Here, and throughout the rest of this chapter, we’ll use the `run` function -from the `trpl` crate, which takes a future as an argument and runs it to -completion. Behind the scenes, calling `run` sets up a runtime to use to run the -future passed in. Once the future completes, `run` returns whatever value the -future produced. - -We could pass the future returned by `page_title` directly to `run`. Once it -completed, we would be able to match on the resulting `Option<String>`, the way +*itself*. (We’ll see more about why this is the case in a bit.) Every Rust +program that executes async code has at least one place where it sets up a +runtime and executes the futures. + +Most languages that support async bundle a runtime, but Rust does not. Instead, +there are many different async runtimes available, each of which makes different +tradeoffs suitable to the use case it targets. For example, a high-throughput +web server with many CPU cores and a large amount of RAM has very different +needs than a microcontroller with a single core, a small amount of RAM, and no +heap allocation ability. The crates that provide those runtimes also often +supply async versions of common functionality such as file or network I/O. + +Here, and throughout the rest of this chapter, we’ll use the `run` function from +the `trpl` crate, which takes a future as an argument and runs it to completion. +Behind the scenes, calling `run` sets up a runtime that’s used to run the future +passed in. Once the future completes, `run` returns whatever value the future +produced. + +We could pass the future returned by `page_title` directly to `run`, and once it +completed, we could match on the resulting `Option<String>`, as we tried to do in Listing 17-3. However, for most of the examples in the chapter -(and most async code in the real world!), we’ll be doing more than just one +(and most async code in the real world), we’ll be doing more than just one async function call, so instead we’ll pass an `async` block and explicitly -await the result of calling `page_title`, as in Listing 17-4. +await the result of the `page_title` call, as in Listing 17-4. + +src/main.rs -Filename: src/main.rs +<!-- should_panic,noplayground because mdbook test does not pass args --> ``` fn main() { @@ -432,24 +455,33 @@ fn main() { Listing 17-4: Awaiting an async block with `trpl::run` -When we run this, we get the behavior we might have expected initially: +When we run this code, we get the behavior we expected initially: + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-04 +cargo build # skip all the build noise +cargo run https://www.rust-lang.org +# copy the output here +--> ``` -$ cargo run "http://www.rust-lang.org" -The title for http://www.rust-lang.org was +$ cargo run -- https://www.rust-lang.org + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s + Running `target/debug/async_await 'https://www.rust-lang.org'` +The title for https://www.rust-lang.org was Rust Programming Language ``` -Phew: we finally have some working async code! This now compiles, and we can run -it. Before we add code to race two sites against each other, let’s briefly turn -our attention back to how futures work. +Phew—we finally have some working async code! But before we add the code to race +the two sites against each other, let’s briefly turn our attention back to how +futures work. Each *await point*—that is, every place where the code uses the `await` -keyword—represents a place where control gets handed back to the runtime. To +keyword—represents a place where control is handed back to the runtime. To make that work, Rust needs to keep track of the state involved in the async -block, so that the runtime can kick off some other work and then come back when -it’s ready to try advancing this one again. This is an invisible state machine, -as if you wrote an enum in this way to save the current state at each `await` +block so that the runtime can kick off some other work and then come back when +it’s ready to try advancing the first one again. This is an invisible state machine, +as if you’d written an enum like this to save the current state at each await point: ``` @@ -461,35 +493,39 @@ enum PageTitleFuture<'a> { ``` Writing the code to transition between each state by hand would be tedious and -error-prone, especially when adding more functionality and more states to the -code later. Instead, the Rust compiler creates and manages the state machine -data structures for async code automatically. If you’re wondering: yep, the -normal borrowing and ownership rules around data structures all apply. Happily, -the compiler also handles checking those for us, and has good error messages. -We’ll work through a few of those later in the chapter! - -Ultimately, something has to execute that state machine. That something is a -runtime. (This is why you may sometimes come across references to *executors* +error-prone, however, especially when you need to add more functionality and +more states to the code later. Fortunately, the Rust compiler creates and +manages the state machine data structures for async code automatically. The +normal borrowing and ownership rules around data structures all still apply, and +happily, the compiler also handles checking those for us and provides useful +error messages. We’ll work through a few of those later in the chapter. + +Ultimately, something has to execute this state machine, and that something is a +runtime. (This is why you may come across references to *executors* when looking into runtimes: an executor is the part of a runtime responsible for executing the async code.) -Now we can understand why the compiler stopped us from making `main` itself an -async function back in Listing 17-3. If `main` were an async function, something -else would need to manage the state machine for whatever future `main` returned, -but `main` is the starting point for the program! Instead, we call the -`trpl::run` function in `main`, which sets up a runtime and runs the future -returned by the `async` block until it returns `Ready`. +Now you can see why the compiler stopped us from making `main` itself an async +function back in Listing 17-3. If `main` were an async function, something else +would need to manage the state machine for whatever future `main` returned, but +`main` is the starting point for the program! Instead, we called the `trpl::run` +function in `main` to set up a runtime and run the future returned by the +`async` block until it returns `Ready`. + +> Note: Some runtimes provide macros so you *can* write an async `main` +> function. Those macros rewrite `async fn main() { ... }` to be a normal `fn main`, which does the same thing we did by hand in Listing 17-5: call a +> function that runs a future to completion the way `trpl::run` does. -> Note: some runtimes provide macros to make it so you *can* write an async -> main function. Those macros rewrite `async fn main() { ... }` to be a normal -> `fn main` which does the same thing we did by hand in Listing 17-5: call a -> function which runs a future to completion the way `trpl::run` does. +Now let’s put these pieces together and see how we can write concurrent code. -Let’s put these pieces together and see how we can write concurrent code, by -calling `page_title` with two different URLs passed in from the command line -and racing them. +### Racing Our Two URLs Against Each Other -Filename: src/main.rs +In Listing 17-5, we call `page_title` with two different URLs passed in from the +command line and race them. + +src/main.rs + +<!-- should_panic,noplayground because mdbook does not pass args --> ``` use trpl::{Either, Html}; @@ -524,26 +560,25 @@ async fn page_title(url: &str) -> (&str, Option<String>) { } ``` -Listing 17-5: Calling `page_title` for two URLs to see which returns first +Listing 17-5: -In Listing 17-5, we begin by calling `page_title` for each of the user-supplied -URLs. We save the futures produced by calling `page_title` as `title_fut_1` and -`title_fut_2`. Remember, these don’t do anything yet, because futures are lazy, -and we haven’t yet awaited them. Then we pass the futures to `trpl::race`, -which returns a value to indicate which of the futures passed to it finishes -first. +We begin by calling `page_title` for each of the user-supplied URLs. We save the +resulting futures as `title_fut_1` and `title_fut_2`. Remember, these don’t do +anything yet, because futures are lazy and we haven’t yet awaited them. Then we +pass the futures to `trpl::race`, which returns a value to indicate which of the +futures passed to it finishes first. > Note: Under the hood, `race` is built on a more general function, `select`, > which you will encounter more often in real-world Rust code. A `select` -> function can do a lot of things that `trpl::race` function can’t, but it also -> has some additional complexity that we can skip over for now. +> function can do a lot of things that the `trpl::race` function can’t, but it +> also has some additional complexity that we can skip over for now. Either future can legitimately “win,” so it doesn’t make sense to return a `Result`. Instead, `race` returns a type we haven’t seen before, -`trpl::Either`. The `Either` type is somewhat similar to a `Result`, in that it +`trpl::Either`. The `Either` type is somewhat similar to a `Result` in that it has two cases. Unlike `Result`, though, there is no notion of success or failure baked into `Either`. Instead, it uses `Left` and `Right` to indicate -“one or the other”. +“one or the other”: ``` enum Either<A, B> { @@ -552,46 +587,56 @@ enum Either<A, B> { } ``` -The `race` function returns `Left` if the first argument finishes first, with -that future’s output, and `Right` with the second future argument’s output if -*that* one finishes first. This matches the order the arguments appear when -calling the function: the first argument is to the left of the second argument. +The `race` function returns `Left` with that future’s output if the first +argument wins, and `Right` with the second future argument’s output if *that* +one wins. This matches the order the arguments appear in when calling the +function: the first argument is to the left of the second argument. We also update `page_title` to return the same URL passed in. That way, if -the page which returns first does not have a `<title>` we can resolve, we can +the page that returns first does not have a `<title>` we can resolve, we can still print a meaningful message. With that information available, we wrap up by updating our `println!` output to indicate both which URL finished first and -what the `<title>` was for the web page at that URL, if any. +what, if any, the `<title>` is for the web page at that URL. You have built a small working web scraper now! Pick a couple URLs and run the -command line tool. You may discover that some sites are reliably faster than -others, while in other cases which site “wins” varies from run to run. More -importantly, you’ve learned the basics of working with futures, so we can now -dig into even more of the things we can do with async. +command line tool. You may discover that some sites are consistently faster than +others, while in other cases the faster site varies from run to run. More +importantly, you’ve learned the basics of working with futures, so now we can +dig deeper into what we can do with async. + +<!-- TODO: map source link version to version of Rust? --> + +## Applying Concurrency with Async -## Concurrency With Async +<!-- Old headings. Do not remove or links may break. --> + +<a id="concurrency-with-async"></a> In this section, we’ll apply async to some of the same concurrency challenges -we tackled with threads in Chapter 16. Because we already talked about a lot of +we tackled with threads in chapter 16. Because we already talked about a lot of the key ideas there, in this section we’ll focus on what’s different between threads and futures. In many cases, the APIs for working with concurrency using async are very -similar to those for using threads. In other cases, they end up being shaped -quite differently. Even when the APIs *look* similar between threads and async, -they often have different behavior—and they nearly always have different -performance characteristics. +similar to those for using threads. In other cases, they end up being quite +different. Even when the APIs *look* similar between threads and async, they +often have different behavior—and they nearly always have different performance +characteristics. + +<!-- Old headings. Do not remove or links may break. --> -### Counting +<a id="counting"></a> -The first task we tackled in the “Creating a New Thread with spawn” section of -Chapter 16 on page XX was counting up on two separate threads. Let’s do the -same using async. The `trpl` crate supplies a `spawn_task` function which looks -very similar to the `thread::spawn` API, and a `sleep` function which is an -async version of the `thread::sleep` API. We can use these together to -implement the same counting example as with threads, in Listing 17-6. +### Creating a New Task with spawn_task -Filename: src/main.rs +The first operation we tackled in Creating a New Thread with +Spawn was counting up on two separate threads. +Let’s do the same using async. The `trpl` crate supplies a `spawn_task` function +that looks very similar to the `thread::spawn` API, and a `sleep` function +that is an async version of the `thread::sleep` API. We can use these together +to implement the counting example, as shown in Listing 17-6. + +src/main.rs ``` use std::time::Duration; @@ -613,23 +658,27 @@ fn main() { } ``` -Listing 17-6: Using `spawn_task` to count with two +Listing 17-6: Creating a new task to print one thing while the main task prints something else -As our starting point, we set up our `main` function with `trpl::run`, so -that our top-level function can be async. +As our starting point, we set up our `main` function with `trpl::run` so that +our top-level function can be async. > Note: From this point forward in the chapter, every example will include this > exact same wrapping code with `trpl::run` in `main`, so we’ll often skip it > just as we do with `main`. Don’t forget to include it in your code! -Then we write two loops within that block, each with a `trpl::sleep` call in it, +Then we write two loops within that block, each containing a `trpl::sleep` call, which waits for half a second (500 milliseconds) before sending the next message. We put one loop in the body of a `trpl::spawn_task` and the other in a top-level `for` loop. We also add an `await` after the `sleep` calls. -This does something similar to the thread-based implementation—including the +This code behaves similarly to the thread-based implementation—including the fact that you may see the messages appear in a different order in your own -terminal when you run it. +terminal when you run it: + +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> ``` hi number 1 from the second task! @@ -643,36 +692,40 @@ hi number 4 from the second task! hi number 5 from the first task! ``` -This version stops as soon as the for loop in the body of the main async block -finishes, because the task spawned by `spawn_task` is shut down when the main -function ends. If you want to run all the way to the completion of the task, you +This version stops as soon as the `for` loop in the body of the main async block +finishes, because the task spawned by `spawn_task` is shut down when the `main` +function ends. If you want it to run all the way to the task’s completion, you will need to use a join handle to wait for the first task to complete. With threads, we used the `join` method to “block” until the thread was done running. In Listing 17-7, we can use `await` to do the same thing, because the task handle itself is a future. Its `Output` type is a `Result`, so we also unwrap it after awaiting it. -Filename: src/main.rs +src/main.rs ``` -let handle = trpl::spawn_task(async { - for i in 1..10 { - println!("hi number {i} from the first task!"); - trpl::sleep(Duration::from_millis(500)).await; - } -}); + let handle = trpl::spawn_task(async { + for i in 1..10 { + println!("hi number {i} from the first task!"); + trpl::sleep(Duration::from_millis(500)).await; + } + }); -for i in 1..5 { - println!("hi number {i} from the second task!"); - trpl::sleep(Duration::from_millis(500)).await; -} + for i in 1..5 { + println!("hi number {i} from the second task!"); + trpl::sleep(Duration::from_millis(500)).await; + } -handle.await.unwrap(); + handle.await.unwrap(); ``` Listing 17-7: Using `await` with a join handle to run a task to completion -This updated version runs till *both* loops finish. +This updated version runs until *both* loops finish. + +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> ``` hi number 1 from the second task! @@ -700,40 +753,44 @@ async blocks compile to anonymous futures, we can put each loop in an async block and have the runtime run them both to completion using the `trpl::join` function. -In the “Waiting for All Threads to Finish Using `join` Handles” section of -Chapter 16 on page XX, we showed how to use the `join` method on the -`JoinHandle` type returned when you call `std::thread::spawn`. The `trpl::join` -function is similar, but for futures. When you give it two futures, it produces -a single new future whose output is a tuple with the output of each of the -futures you passed in once *both* complete. Thus, in Listing 17-8, we use -`trpl::join` to wait for both `fut1` and `fut2` to finish. We do *not* await -`fut1` and `fut2`, but instead the new future produced by `trpl::join`. We -ignore the output, because it’s just a tuple with two unit values in it. +In the section Waiting for All Threads to Finishing Using `join` +Handles, we showed how to use the `join` method on +the `JoinHandle` type returned when you call `std::thread::spawn`. The +`trpl::join` function is similar, but for futures. When you give it two futures, +it produces a single new future whose output is a tuple containing the output of +each future you passed in once they *both* complete. Thus, in Listing 17-8, we +use `trpl::join` to wait for both `fut1` and `fut2` to finish. We do *not* await +`fut1` and `fut2` but instead the new future produced by `trpl::join`. We ignore +the output, because it’s just a tuple containing two unit values. -Filename: src/main.rs +src/main.rs ``` -let fut1 = async { - for i in 1..10 { - println!("hi number {i} from the first task!"); - trpl::sleep(Duration::from_millis(500)).await; - } -}; + let fut1 = async { + for i in 1..10 { + println!("hi number {i} from the first task!"); + trpl::sleep(Duration::from_millis(500)).await; + } + }; -let fut2 = async { - for i in 1..5 { - println!("hi number {i} from the second task!"); - trpl::sleep(Duration::from_millis(500)).await; - } -}; + let fut2 = async { + for i in 1..5 { + println!("hi number {i} from the second task!"); + trpl::sleep(Duration::from_millis(500)).await; + } + }; -trpl::join(fut1, fut2).await; + trpl::join(fut1, fut2).await; ``` Listing 17-8: Using `trpl::join` to await two anonymous futures When we run this, we see both futures run to completion: +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> + ``` hi number 1 from the first task! hi number 1 from the second task! @@ -750,7 +807,7 @@ hi number 8 from the first task! hi number 9 from the first task! ``` -Here, you’ll see the exact same order every time, which is very different from +Now, you’ll see the exact same order every time, which is very different from what we saw with threads. That is because the `trpl::join` function is *fair*, meaning it checks each future equally often, alternating between them, and never lets one race ahead if the other is ready. With threads, the operating system @@ -759,11 +816,10 @@ runtime decides which task to check. (In practice, the details get complicated because an async runtime might use operating system threads under the hood as part of how it manages concurrency, so guaranteeing fairness can be more work for a runtime—but it’s still possible!) Runtimes don’t have to guarantee -fairness for any given operation, and runtimes often offer different APIs to let -you choose whether you want fairness or not. +fairness for any given operation, and they often offer different APIs to let you +choose whether or not you want fairness. -Try some of these different variations on awaiting the futures and see what they -do: +Try some of these variations on awaiting the futures and see what they do: * Remove the async block from around either or both of the loops. * Await each async block immediately after defining it. @@ -773,40 +829,42 @@ do: For an extra challenge, see if you can figure out what the output will be in each case *before* running the code! -### Message Passing +<!-- Old headings. Do not remove or links may break. --> + +<a id="message-passing"></a> + +### Counting Up on Two Tasks Using Message Passing Sharing data between futures will also be familiar: we’ll use message passing -again, but this with async versions of the types and functions. We’ll take a -slightly different path than we did in the “Using Message Passing to Transfer -Data Between Threads” section of Chapter 16 on page XX, to illustrate some of +again, but this time with async versions of the types and functions. We’ll take +a slightly different path than we did in Using Message Passing to Transfer Data +Between Threads to illustrate some of the key differences between thread-based and futures-based concurrency. In Listing 17-9, we’ll begin with just a single async block—*not* spawning a separate task as we spawned a separate thread. -Filename: src/main.rs +src/main.rs ``` -let (tx, mut rx) = trpl::channel(); + let (tx, mut rx) = trpl::channel(); -let val = String::from("hi"); -tx.send(val).unwrap(); + let val = String::from("hi"); + tx.send(val).unwrap(); -let received = rx.recv().await.unwrap(); -println!("Got: {received}"); + let received = rx.recv().await.unwrap(); + println!("Got: {received}"); ``` -Listing 17-9: Creating an async channel and assigning the two halves to `tx` -and `rx` +Listing 17-9: Creating an async channel and assigning the two halves to `tx` and `rx` Here, we use `trpl::channel`, an async version of the multiple-producer, -single-consumer channel API we used with threads back in the “Using Message -Passing to Transfer Data Between Threads” section of Chapter 16 on page XX. The -async version of the API is only a little different from the thread-based -version: it uses a mutable rather than an immutable receiver `rx`, and its -`recv` method produces a future we need to await rather than producing the -value directly. Now we can send messages from the sender to the receiver. -Notice that we don’t have to spawn a separate thread or even a task; we merely -need to await the `rx.recv` call. +single-consumer channel API we used with threads back in Chapter 16. The async +version of the API is only a little different from the thread-based version: it +uses a mutable rather than an immutable receiver `rx`, and its `recv` method +produces a future we need to await rather than producing the value directly. Now +we can send messages from the sender to the receiver. Notice that we don’t have +to spawn a separate thread or even a task; we merely need to await the `rx.recv` +call. The synchronous `Receiver::recv` method in `std::mpsc::channel` blocks until it receives a message. The `trpl::Receiver::recv` method does not, because it @@ -817,65 +875,64 @@ because the channel we’re sending it into is unbounded. > Note: Because all of this async code runs in an async block in a `trpl::run` > call, everything within it can avoid blocking. However, the code *outside* it -> will block on the `run` function returning. That is the whole point of the +> will block on the `run` function returning. That’s the whole point of the > `trpl::run` function: it lets you *choose* where to block on some set of async > code, and thus where to transition between sync and async code. In most async > runtimes, `run` is actually named `block_on` for exactly this reason. -Notice two things about this example: First, the message will arrive right away! +Notice two things about this example. First, the message will arrive right away. Second, although we use a future here, there’s no concurrency yet. Everything in the listing happens in sequence, just as it would if there were no futures involved. -Let’s address the first part by sending a series of messages, and sleep in -between them, as shown in Listing 17-10: +Let’s address the first part by sending a series of messages and sleeping in +between them, as shown in Listing 17-10. -Filename: src/main.rs +<!-- We cannot test this one because it never stops! --> + +src/main.rs ``` -let (tx, mut rx) = trpl::channel(); + let (tx, mut rx) = trpl::channel(); -let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("future"), -]; + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("future"), + ]; -for val in vals { - tx.send(val).unwrap(); - trpl::sleep(Duration::from_millis(500)).await; -} + for val in vals { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } -while let Some(value) = rx.recv().await { - println!("received '{value}'"); -} + while let Some(value) = rx.recv().await { + println!("received '{value}'"); + } ``` -Listing 17-10: Sending and receiving multiple messages over the async channel -and sleeping with an `await` between each message +Listing 17-10: Sending and receiving multiple messages over the async channel and sleeping with an `await` between each message -In addition to sending the messages, we need to receive them. In this case, we -could do that manually, by just doing `rx.recv().await` four times, because we -know how many messages are coming in. In the real world, though, we’ll -generally be waiting on some *unknown* number of messages. In that case, we need -to keep waiting until we determine that there are no more messages. +In addition to sending the messages, we need to receive them. In this case, +because we know how many messages are coming in, we could do that manually by +calling `rx.recv().await` four times. In the real world, though, we’ll generally +be waiting on some *unknown* number of messages, so we need to keep waiting +until we determine that there are no more messages. In Listing 16-10, we used a `for` loop to process all the items received from a -synchronous channel. However, Rust doesn’t yet have a way to write a `for` loop -over an *asynchronous* series of items. Instead, we need to use a new kind of -loop we haven’t seen before, the `while let` conditional loop. A `while let` -loop is the loop version of the `if let` construct we saw back in the “Concise -Control Flow with `if let`” section in Chapter 6 on page XX. The loop will -continue executing as long as the pattern it specifies continues to match the -value. - -The `rx.recv` call produces a `Future`, which we await. The runtime will pause -the `Future` until it is ready. Once a message arrives, the future will resolve -to `Some(message)`, as many times as a message arrives. When the channel closes, -regardless of whether *any* messages have arrived, the future will instead -resolve to `None` to indicate that there are no more values, and we should stop -polling—that is, stop awaiting. +synchronous channel. Rust doesn’t yet have a way to write a `for` loop over an +*asynchronous* series of items, however, so we need to use a loop we haven’t +seen before: the `while let` conditional loop. This is the loop version of the +`if let` construct we saw back in the section Concise Control Flow with `if let` and `let else`. The loop will continue executing as +long as the pattern it specifies continues to match the value. + +The `rx.recv` call produces a future, which we await. The runtime will pause the +future until it is ready. Once a message arrives, the future will resolve to +`Some(message)` as many times as a message arrives. When the channel closes, +regardless of whether *any* messages have arrived, the future will instead +resolve to `None` to indicate that there are no more values and thus we should +stop polling—that is, stop awaiting. The `while let` loop pulls all of this together. If the result of calling `rx.recv().await` is `Some(message)`, we get access to the message and we can @@ -884,16 +941,16 @@ use it in the loop body, just as we could with `if let`. If the result is again, so the runtime pauses it again until another message arrives. The code now successfully sends and receives all of the messages. Unfortunately, -there are still a couple problems. For one thing, the messages do not arrive at -half-second intervals. They arrive all at once, two seconds (2,000 milliseconds) -after we start the program. For another, this program also never exits! Instead, -it waits forever for new messages. You will need to shut it down using <span +there are still a couple of problems. For one thing, the messages do not arrive +at half-second intervals. They arrive all at once, 2 (2,000 milliseconds) after +we start the program. For another, this program also never exits! Instead, it +waits forever for new messages. You will need to shut it down using <span class="keystroke">ctrl-c</span>. -Let’s start by understanding why the messages all come in at once after the full -delay, rather than coming in with delays in between each one. Within a given -async block, the order that `await` keywords appear in the code is also the -order they happen when running the program. +Let’s start by examining why the messages come in all at once after the full +delay, rather than coming in with delays between each one. Within a given async +block, the order in which `await` keywords appear in the code is also the order +in which they’re executed when the program runs. There’s only one async block in Listing 17-10, so everything in it runs linearly. There’s still no concurrency. All the `tx.send` calls happen, @@ -901,63 +958,64 @@ interspersed with all of the `trpl::sleep` calls and their associated await points. Only then does the `while let` loop get to go through any of the `await` points on the `recv` calls. -To get the behavior we want, where the sleep delay happens between receiving -each message, we need to put the `tx` and `rx` operations in their own async -blocks. Then the runtime can execute each of them separately using `trpl::join`, -just as in the counting example. Once again, we await the result of calling -`trpl::join`, not the individual futures. If we awaited the individual futures -in sequence, we would just end up back in a sequential flow—exactly what we’re -trying *not* to do. +To get the behavior we want, where the sleep delay happens between each message, +we need to put the `tx` and `rx` operations in their own async blocks, as shown +in Listing 17-11. Then the runtime can execute each of them separately using +`trpl::join`, just as in the counting example. Once again, we await the result +of calling `trpl::join`, not the individual futures. If we awaited the +individual futures in sequence, we would just end up back in a sequential +flow—exactly what we’re trying *not* to do. -Filename: src/main.rs +<!-- We cannot test this one because it never stops! --> + +src/main.rs ``` -let tx_fut = async { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("future"), - ]; + let tx_fut = async { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("future"), + ]; - for val in vals { - tx.send(val).unwrap(); - trpl::sleep(Duration::from_millis(500)).await; - } -}; + for val in vals { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } + }; -let rx_fut = async { - while let Some(value) = rx.recv().await { - println!("received '{value}'"); - } -}; + let rx_fut = async { + while let Some(value) = rx.recv().await { + println!("received '{value}'"); + } + }; -trpl::join(tx_fut, rx_fut).await; + trpl::join(tx_fut, rx_fut).await; ``` -Listing 17-11: Separating `send` and `recv` into their own `async` blocks and -awaiting the futures for those blocks +Listing 17-11: Separating `send` and `recv` into their own `async` blocks and awaiting the futures for those blocks With the updated code in Listing 17-11, the messages get printed at -500-millisecond intervals, rather than all in a rush after two seconds. +500-millisecond intervals, rather than all in a rush after 2 seconds. The program still never exits, though, because of the way `while let` loop interacts with `trpl::join`: -* The future returned from `trpl::join` only completes once *both* futures +* The future returned from `trpl::join` completes only once *both* futures passed to it have completed. * The `tx` future completes once it finishes sleeping after sending the last message in `vals`. * The `rx` future won’t complete until the `while let` loop ends. * The `while let` loop won’t end until awaiting `rx.recv` produces `None`. -* Awaiting `rx.recv` will only return `None` once the other end of the channel +* Awaiting `rx.recv` will return `None` only once the other end of the channel is closed. -* The channel will only close if we call `rx.close` or when the sender side, +* The channel will close only if we call `rx.close` or when the sender side, `tx`, is dropped. * We don’t call `rx.close` anywhere, and `tx` won’t be dropped until the outermost async block passed to `trpl::run` ends. * The block can’t end because it is blocked on `trpl::join` completing, which - takes us back to the top of this list! + takes us back to the top of this list. We could manually close `rx` by calling `rx.close` somewhere, but that doesn’t make much sense. Stopping after handling some arbitrary number of messages would @@ -965,113 +1023,117 @@ make the program shut down, but we could miss messages. We need some other way to make sure that `tx` gets dropped *before* the end of the function. Right now, the async block where we send the messages only borrows `tx` because -sending a message doesn’t require ownership, but if we could move `tx` into -that async block, it would be dropped once that block ends. In the “Capturing -References or Moving Ownership” section of Chapter 13 on page XX, we learned -how to use the `move` keyword with closures, and in the “Using `move` Closures -with Threads” section of Chapter 16 on page XX, we saw that we often need to -move data into closures when working with threads. The same basic dynamics -apply to async blocks, so the `move` keyword works with async blocks just as it -does with closures. +sending a message doesn’t require ownership, but if we could move `tx` into that +async block, it would be dropped once that block ends. In the Chapter 13 section +Capturing References or Moving Ownership, you +learned how to use the `move` keyword with closures, and, as discussed in the +Chapter 16 section Using `move` Closures with Threads, we often need to move data into closures when working with threads. The +same basic dynamics apply to async blocks, so the `move` keyword works with +async blocks just as it does with closures. -In Listing 17-12, we change the async block for sending messages from a plain -`async` block to an `async move` block. When we run *this* version of the code, -it shuts down gracefully after the last message is sent and received. +In Listing 17-12, we change the block used to send messages from `async` to +`async move`. When we run *this* version of the code, it shuts down gracefully +after the last message is sent and received. -Filename: src/main.rs +src/main.rs ``` -let (tx, mut rx) = trpl::channel(); + let (tx, mut rx) = trpl::channel(); -let tx_fut = async move { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("future"), - ]; + let tx_fut = async move { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("future"), + ]; - for val in vals { - tx.send(val).unwrap(); - trpl::sleep(Duration::from_millis(500)).await; - } -}; + for val in vals { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } + }; -let rx_fut = async { - while let Some(value) = rx.recv().await { - eprintln!("received '{value}'"); - } -}; + let rx_fut = async { + while let Some(value) = rx.recv().await { + println!("received '{value}'"); + } + }; -trpl::join(tx_fut, rx_fut).await; + trpl::join(tx_fut, rx_fut).await; ``` -Listing 17-12: A working example of sending and receiving messages between -futures which correctly shuts down when complete +Listing 17-12: A revision of the code from Listing 17-11 that correctly shuts down when complete This async channel is also a multiple-producer channel, so we can call `clone` -on `tx` if we want to send messages from multiple futures. In Listing 17-13, we -clone `tx`, creating `tx1` outside the first async block. We move `tx1` into -that block just as we did before with `tx`. Then, later, we move the original -`tx` into a *new* async block, where we send more messages on a slightly slower -delay. We happen to put this new async block after the async block for receiving -messages, but it could go before it just as well. The key is the order of the -futures are awaited in, not the order they are created in. - -Both of the async blocks for sending messages need to be `async move` blocks, so -that both `tx` and `tx1` get dropped when those blocks finish. Otherwise we’ll -end up back in the same infinite loop we started out in. Finally, we switch from -`trpl::join` to `trpl::join3` to handle the additional future. +on `tx` if we want to send messages from multiple futures, as shown in Listing +17-13. -Filename: src/main.rs +src/main.rs ``` -let (tx, mut rx) = trpl::channel(); + let (tx, mut rx) = trpl::channel(); -let tx1 = tx.clone(); -let tx1_fut = async move { - let vals = vec![ - String::from("hi"), - String::from("from"), - String::from("the"), - String::from("future"), - ]; + let tx1 = tx.clone(); + let tx1_fut = async move { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("future"), + ]; - for val in vals { - tx1.send(val).unwrap(); - trpl::sleep(Duration::from_millis(500)).await; - } -}; - -let rx_fut = async { - while let Some(value) = rx.recv().await { - println!("received '{value}'"); - } -}; + for val in vals { + tx1.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } + }; -let tx_fut = async move { - let vals = vec![ - String::from("more"), - String::from("messages"), - String::from("for"), - String::from("you"), - ]; + let rx_fut = async { + while let Some(value) = rx.recv().await { + println!("received '{value}'"); + } + }; - for val in vals { - tx.send(val).unwrap(); - trpl::sleep(Duration::from_millis(1500)).await; - } -}; + let tx_fut = async move { + let vals = vec![ + String::from("more"), + String::from("messages"), + String::from("for"), + String::from("you"), + ]; + + for val in vals { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(1500)).await; + } + }; -trpl::join3(tx1_fut, tx_fut, rx_fut).await; + trpl::join3(tx1_fut, tx_fut, rx_fut).await; ``` Listing 17-13: Using multiple producers with async blocks -Now we see all the messages from both sending futures. Because the sending +First, we clone `tx`, creating `tx1` outside the first async block. We move +`tx1` into that block just as we did before with `tx`. Then, later, we move the +original `tx` into a *new* async block, where we send more messages on a +slightly slower delay. We happen to put this new async block after the async +block for receiving messages, but it could go before it just as well. The key is +the order in which the futures are awaited, not in which they’re created. + +Both of the async blocks for sending messages need to be `async move` blocks so +that both `tx` and `tx1` get dropped when those blocks finish. Otherwise, we’ll +end up back in the same infinite loop we started out in. Finally, we switch from +`trpl::join` to `trpl::join3` to handle the additional future. + +Now we see all the messages from both sending futures, and because the sending futures use slightly different delays after sending, the messages are also received at those different intervals. +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> + ``` received 'hi' received 'more' @@ -1086,7 +1148,7 @@ received 'you' This is a good start, but it limits us to just a handful of futures: two with `join`, or three with `join3`. Let’s see how we might work with more futures. -## Working With Any Number of Futures +## Working with Any Number of Futures When we switched from using two futures to three in the previous section, we also had to switch from using `join` to using `join3`. It would be annoying to @@ -1094,149 +1156,198 @@ have to call a different function every time we changed the number of futures we wanted to join. Happily, we have a macro form of `join` to which we can pass an arbitrary number of arguments. It also handles awaiting the futures itself. Thus, we could rewrite the code from Listing 17-13 to use `join!` instead of -`join3`, as in Listing 17-14: +`join3`, as in Listing 17-14. -Filename: src/main.rs +src/main.rs ``` -trpl::join!(tx1_fut, tx_fut, rx_fut); + trpl::join!(tx1_fut, tx_fut, rx_fut); ``` Listing 17-14: Using `join!` to wait for multiple futures -This is definitely a nice improvement over needing to swap between `join` and -`join3` and `join4` and so on! However, even this macro form only works when we -know the number of futures ahead of time. In real-world Rust, though, pushing -futures into a collection and then waiting on some or all the futures in that -collection to complete is a common pattern. +This is definitely an improvement over swapping between `join` and +`join3` and `join4` and so on! However, even this macro form only works +when we know the number of futures ahead of time. In real-world Rust, +though, pushing futures into a collection and then waiting on some or +all the futures of them to complete is a common pattern. To check all the futures in some collection, we’ll need to iterate over and -join on *all* of them. The `trpl::join_all` function accepts any type which -implements the `Iterator` trait, which we learned about back in “The Iterator -Trait and the next Method” section of Chapter 13 on page XX, so it seems like -just the ticket. Let’s try putting our futures in a vector, and replace `join!` -with `join_all`. +join on *all* of them. The `trpl::join_all` function accepts any type that +implements the `Iterator` trait, which you learned about back in The Iterator +Trait and the `next` Method Chapter 13, so +it seems like just the ticket. Let’s try putting our futures in a vector and +replacing `join!` with `join_all` as show in Listing 17-15. + ``` -let futures = vec![tx1_fut, rx_fut, tx_fut]; + let futures = vec![tx1_fut, rx_fut, tx_fut]; -trpl::join_all(futures).await; + trpl::join_all(futures).await; ``` Listing 17-15: Storing anonymous futures in a vector and calling `join_all` -Unfortunately, this doesn’t compile. Instead, we get this error: +Unfortunately, this code doesn’t compile. Instead, we get this error: + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-15/ +cargo build +copy just the compiler error +--> ``` error[E0308]: mismatched types - --> src/main.rs:43:37 + --> src/main.rs:45:37 | -8 | let tx1_fut = async move { - | _______________________- -9 | | let vals = vec![ -10 | | String::from("hi"), -11 | | String::from("from"), -... | -19 | | } -20 | | }; - | |_________- the expected `async` block -21 | -22 | let rx_fut = async { - | ______________________- -23 | | while let Some(value) = rx.recv().await { -24 | | println!("received '{value}'"); -25 | | } -26 | | }; - | |_________- the found `async` block +10 | let tx1_fut = async move { + | ---------- the expected `async` block ... -43 | let futures = vec![tx1_fut, rx_fut, tx_fut]; - | ^^^^^^ expected `async` block, found a different `async` block +24 | let rx_fut = async { + | ----- the found `async` block +... +45 | let futures = vec![tx1_fut, rx_fut, tx_fut]; + | ^^^^^^ expected `async` block, found a +different `async` block | - = note: expected `async` block `{async block@src/main.rs:8:23: 20:10}` - found `async` block `{async block@src/main.rs:22:22: 26:10}` + = note: expected `async` block `{async block@src/main.rs:10:23: 10:33}` + found `async` block `{async block@src/main.rs:24:22: 24:27}` = note: no two async blocks, even if identical, have the same type - = help: consider pinning your async block and and casting it to a trait object + = help: consider pinning your async block and casting it to a trait object ``` -This might be surprising. After all, none of them return anything, so each -block produces a `Future<Output = ()>`. However, `Future` is a trait, not a -concrete type. The concrete types are the individual data structures generated -by the compiler for async blocks. You can’t put two different hand-written -structs in a `Vec`, and the same thing applies to the different structs -generated by the compiler. +This might be surprising. After all, none of the async blocks returns anything, +so each one produces a `Future<Output = ()>`. Remember that `Future` is a trait, +though, and that the compiler creates a unique enum for each async block. You +can’t put two different hand-written structs in a `Vec`, and the same rule +applies to the different enums generated by the compiler. -To make this work, we need to use *trait objects*, just as we did in the -“Returning Errors from the run function” section in Chapter 12 on page XX. -(We’ll cover trait objects in detail in Chapter 18.) Using trait objects lets -us treat each of the anonymous futures produced by these types as the same -type, because all of them implement the `Future` trait. +To make this work, we need to use *trait objects*, just as we did in “Returning +Errors from the run function” in Chapter 12. (We’ll cover +trait objects in detail in Chapter 18.) Using trait objects lets us treat each +of the anonymous futures produced by these types as the same type, because all +of them implement the `Future` trait. -> Note: In the “Using an Enum to Store Multiple Types” section of Chapter 8 on -> page XX, we discussed another way to include multiple types in a `Vec`: using -> an enum to represent each of the different types which can appear in the +> Note: In the Chapter 8 section Using an Enum to Store Multiple +> Values, we discussed another way to include multiple +> types in a `Vec`: using an enum to represent each type that can appear in the > vector. We can’t do that here, though. For one thing, we have no way to name > the different types, because they are anonymous. For another, the reason we > reached for a vector and `join_all` in the first place was to be able to work -> with a dynamic collection of futures where we don’t know what they will all -> be until runtime. +> with a dynamic collection of futures where we only care that they have the +> same output type. -We start by wrapping each of the futures in the `vec!` in a `Box::new`, as shown -in Listing 17-16. +We start by wrapping each future in the `vec!` in a `Box::new`, as shown in +Listing 17-16. -Filename: src/main.rs +src/main.rs ``` -let futures = - vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; + let futures = + vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; -trpl::join_all(futures).await; + trpl::join_all(futures).await; ``` -Listing 17-16: Trying to use `Box::new` to align the types of the futures in a -`Vec` +Listing 17-16: Using `Box::new` to align the types of the futures in a `Vec` -Unfortunately, this still doesn’t compile. In fact, we have the same basic -error we did before, but we get one for both the second and third `Box::new` -calls, and we also get new errors referring to the `Unpin` trait. We will come -back to the `Unpin` errors in a moment. First, let’s fix the type errors on the -`Box::new` calls, by explicitly annotating the type of the `futures` variable: +Unfortunately, this code still doesn’t compile. In fact, we get the same basic +error we got before for both the second and third `Box::new` calls, as well as +new errors referring to the `Unpin` trait. We’ll come back to the `Unpin` errors +in a moment. First, let’s fix the type errors on the `Box::new` calls by +explicitly annotating the type of the `futures` variable (see Listing 17-17). -Filename: src/main.rs +src/main.rs ``` -let futures: Vec<Box<dyn Future<Output = ()>>> = - vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; + let futures: Vec<Box<dyn Future<Output = ()>>> = + vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; ``` -Listing 17-17: Fixing the rest of the type mismatch errors by using an explicit -type declaration +Listing 17-17: Fixing the rest of the type mismatch errors by using an explicit type declaration + +This type declaration is a little involved, so let’s walk through it: -The type we had to write here is a little involved, so let’s walk through it: +1. The innermost type is the future itself. We note explicitly that the output + of the future is the unit type `()` by writing `Future<Output = ()>`. +1. Then we annotate the trait with `dyn` to mark it as dynamic. +1. The entire trait reference is wrapped in a `Box`. +1. Finally, we state explicitly that `futures` is a `Vec` containing these + items. -* The innermost type is the future itself. We note explicitly that the output of - the future is the unit type `()` by writing `Future<Output = ()>`. -* Then we annotate the trait with `dyn` to mark it as dynamic. -* The entire trait reference is wrapped in a `Box`. -* Finally, we state explicitly that `futures` is a `Vec` containing these items. +That already made a big difference. Now when we run the compiler, we get only +the errors mentioning `Unpin`. Although there are three of them, their contents +are very similar. -That already made a big difference. Now when we run the compiler, we only have -the errors mentioning `Unpin`. Although there are three of them, notice that -each is very similar in its contents. +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-16 +cargo build +# copy *only* the errors +# fix the paths +--> ``` -error[E0277]: `{async block@src/main.rs:8:23: 20:10}` cannot be unpinned - --> src/main.rs:46:24 +error[E0308]: mismatched types + --> src/main.rs:46:46 + | +10 | let tx1_fut = async move { + | ---------- the expected `async` block +... +24 | let rx_fut = async { + | ----- the found `async` block +... +46 | vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; + | -------- ^^^^^^ expected `async` block, found a different `async` block + | | + | arguments to this function are incorrect + | + = note: expected `async` block `{async block@src/main.rs:10:23: 10:33}` + found `async` block `{async block@src/main.rs:24:22: 24:27}` + = note: no two async blocks, even if identical, have the same type + = help: consider pinning your async block and casting it to a trait object +note: associated function defined here + --> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/alloc/src/boxed.rs:255:12 + | +255 | pub fn new(x: T) -> Self { + | ^^^ + +error[E0308]: mismatched types + --> src/main.rs:46:64 + | +10 | let tx1_fut = async move { + | ---------- the expected `async` block +... +30 | let tx_fut = async move { + | ---------- the found `async` block +... +46 | vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; + | -------- ^^^^^^ expected `async` block, found a different `async` block + | | + | arguments to this function are incorrect + | + = note: expected `async` block `{async block@src/main.rs:10:23: 10:33}` + found `async` block `{async block@src/main.rs:30:22: 30:32}` + = note: no two async blocks, even if identical, have the same type + = help: consider pinning your async block and casting it to a trait object +note: associated function defined here + --> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/alloc/src/boxed.rs:255:12 + | +255 | pub fn new(x: T) -> Self { + | ^^^ + +error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned + --> src/main.rs:48:24 | -46 | trpl::join_all(futures).await; - | -------------- ^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:8:23: 20:10}`, which is required by `Box<{async block@src/main.rs:8:23: 20:10}>: std::future::Future` +48 | trpl::join_all(futures).await; + | -------------- ^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}`, which is required by `Box<{async block@src/main.rs:10:23: 10:33}>: Future` | | | required by a bound introduced by this call | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:8:23: 20:10}>` to implement `std::future::Future` + = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` note: required by a bound in `join_all` - --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:105:14 + --> file:///home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:105:14 | 102 | pub fn join_all<I>(iter: I) -> JoinAll<I::Item> | -------- required by a bound in this function @@ -1244,17 +1355,17 @@ note: required by a bound in `join_all` 105 | I::Item: Future, | ^^^^^^ required by this bound in `join_all` -error[E0277]: `{async block@src/main.rs:8:23: 20:10}` cannot be unpinned - --> src/main.rs:46:9 +error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned + --> src/main.rs:48:9 | -46 | trpl::join_all(futures).await; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:8:23: 20:10}`, which is required by `Box<{async block@src/main.rs:8:23: 20:10}>: std::future::Future` +48 | trpl::join_all(futures).await; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}`, which is required by `Box<{async block@src/main.rs:10:23: 10:33}>: Future` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:8:23: 20:10}>` to implement `std::future::Future` -note: required by a bound in `JoinAll` - --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 + = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` +note: required by a bound in `futures_util::future::join_all::JoinAll` + --> file:///home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 | 27 | pub struct JoinAll<F> | ------- required by a bound in this struct @@ -1262,48 +1373,49 @@ note: required by a bound in `JoinAll` 29 | F: Future, | ^^^^^^ required by this bound in `JoinAll` -error[E0277]: `{async block@src/main.rs:8:23: 20:10}` cannot be unpinned - --> src/main.rs:46:33 +error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned + --> src/main.rs:48:33 | -46 | trpl::join_all(futures).await; - | ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:8:23: 20:10}`, which is required by `Box<{async block@src/main.rs:8:23: 20:10}>: std::future::Future` +48 | trpl::join_all(futures).await; + | ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}`, which is required by `Box<{async block@src/main.rs:10:23: 10:33}>: Future` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:8:23: 20:10}>` to implement `std::future::Future` -note: required by a bound in `JoinAll` - --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 + = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` +note: required by a bound in `futures_util::future::join_all::JoinAll` + --> file:///home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 | 27 | pub struct JoinAll<F> | ------- required by a bound in this struct 28 | where 29 | F: Future, | ^^^^^^ required by this bound in `JoinAll` - -Some errors have detailed explanations: E0277, E0308. -For more information about an error, try `rustc --explain E0277`. ``` That is a *lot* to digest, so let’s pull it apart. The first part of the message tell us that the first async block (`src/main.rs:8:23: 20:10`) does not -implement the `Unpin` trait, and suggests using `pin!` or `Box::pin` to resolve +implement the `Unpin` trait and suggests using `pin!` or `Box::pin` to resolve it. Later in the chapter, we’ll dig into a few more details about `Pin` and `Unpin`. For the moment, though, we can just follow the compiler’s advice to get -unstuck! In Listing 17-18, we start by updating the type annotation for +unstuck. In Listing 17-18, we start by updating the type annotation for `futures`, with a `Pin` wrapping each `Box`. Second, we use `Box::pin` to pin the futures themselves. -Filename: src/main.rs +src/main.rs ``` -let futures: Vec<Pin<Box<dyn Future<Output = ()>>>> = - vec![Box::pin(tx1_fut), Box::pin(rx_fut), Box::pin(tx_fut)]; + let futures: Vec<Pin<Box<dyn Future<Output = ()>>>> = + vec![Box::pin(tx1_fut), Box::pin(rx_fut), Box::pin(tx_fut)]; ``` Listing 17-18: Using `Pin` and `Box::pin` to make the `Vec` type check If we compile and run this, we finally get the output we hoped for: +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> + ``` received 'hi' received 'more' @@ -1317,50 +1429,47 @@ received 'you' Phew! -There’s a bit more we can explore here. For one thing, using `Pin<Box<T>>` -comes with a small amount of extra overhead from putting these futures on the -heap with `Box`—and we’re only doing that to get the types to line up. We don’t -actually *need* the heap allocation, after all: these futures are local to this -particular function. As noted above, `Pin` is itself a wrapper type, so we can -get the benefit of having a single type in the `Vec`—the original reason we -reached for `Box`—without doing a heap allocation. We can use `Pin` directly -with each future, using the `std::pin::pin` macro. +There’s a bit more to explore here. For one thing, using `Pin<Box<T>>` adds a +small amount of overhead from putting these futures on the heap with `Box`—and +we’re only doing that to get the types to line up. We don’t actually *need* the +heap allocation, after all: these futures are local to this particular function. +As noted before, `Pin` is itself a wrapper type, so we can get the benefit of +having a single type in the `Vec`—the original reason we reached for +`Box`—without doing a heap allocation. We can use `Pin` directly with each +future, using the `std::pin::pin` macro. However, we must still be explicit about the type of the pinned reference; -otherwise Rust will still not know to interpret these as dynamic trait objects, +otherwise, Rust will still not know to interpret these as dynamic trait objects, which is what we need them to be in the `Vec`. We therefore `pin!` each future when we define it, and define `futures` as a `Vec` containing pinned mutable -references to the dynamic `Future` type, as in Listing 17-19. +references to the dynamic future type, as in Listing 17-19. -Filename: src/main.rs +src/main.rs ``` -let tx1_fut = pin!(async move { - // --snip-- -}); + let tx1_fut = pin!(async move { + // --snip-- + }); -let rx_fut = pin!(async { - // --snip-- -}); + let rx_fut = pin!(async { + // --snip-- + }); -let tx_fut = pin!(async move { - // --snip-- -}); + let tx_fut = pin!(async move { + // --snip-- + }); -let futures: Vec<Pin<&mut dyn Future<Output = ()>>> = - vec![tx1_fut, rx_fut, tx_fut]; + let futures: Vec<Pin<&mut dyn Future<Output = ()>>> = + vec![tx1_fut, rx_fut, tx_fut]; ``` -Listing 17-19: Using `Pin` directly with the `pin!` macro to avoid unnecessary -heap allocations +Listing 17-19: Using `Pin` directly with the `pin!` macro to avoid unnecessary heap allocations We got this far by ignoring the fact that we might have different `Output` types. For example, in Listing 17-20, the anonymous future for `a` implements -`Future<Output = u32>`, the anonymous future for `b` implements -`Future<Output = &str>`, and the anonymous future for `c` implements -`Future<Output = bool>`. +`Future<Output = u32>`, the anonymous future for `b` implements `Future<Output = &str>`, and the anonymous future for `c` implements `Future<Output = bool>`. -Filename: src/main.rs +src/main.rs ``` let a = async { 1u32 }; @@ -1373,19 +1482,20 @@ Filename: src/main.rs Listing 17-20: Three futures with distinct types -We can use `trpl::join!` to await them, because it allows you to pass in -multiple future types and produces a tuple of those types. We *cannot* use -`trpl::join_all`, because it requires the futures passed in all to have the same -type. Remember, that error is what got us started on this adventure with `Pin`! +We can use `trpl::join!` to await them, because it allows us to pass in multiple +future types and produces a tuple of those types. We *cannot* use +`trpl::join_all`, because it requires all of the futures passed in to have the +same type. Remember, that error is what got us started on this adventure with +`Pin`! This is a fundamental tradeoff: we can either deal with a dynamic number of futures with `join_all`, as long as they all have the same type, or we can deal with a set number of futures with the `join` functions or the `join!` macro, -even if they have different types. This is the same as working with any other -types in Rust, though. Futures are not special, even though we have some nice -syntax for working with them, and that is a good thing. +even if they have different types. This is the same scenario we’d face when +working with any other types in Rust. Futures are not special, even though we +have some nice syntax for working with them, and that’s a good thing. -### Racing futures +### Racing Futures When we “join” futures with the `join` family of functions and macros, we require *all* of them to finish before we move on. Sometimes, though, we only @@ -1393,56 +1503,57 @@ need *some* future from a set to finish before we move on—kind of similar to racing one future against another. In Listing 17-21, we once again use `trpl::race` to run two futures, `slow` and -`fast`, against each other. Each one prints a message when it starts running, -pauses for some amount of time by calling and awaiting `sleep`, and then prints -another message when it finishes. Then we pass both to `trpl::race` and wait for -one of them to finish. (The outcome here won’t be too surprising: `fast` wins!) -Unlike when we used `race` back in the “Our First Async Program” section of this -chapter on page XX, we just ignore the `Either` instance it returns here, -because all of the interesting behavior happens in the body of the async blocks. +`fast`, against each other. -Filename: src/main.rs +src/main.rs ``` -let slow = async { - println!("'slow' started."); - trpl::sleep(Duration::from_millis(100)).await; - println!("'slow' finished."); -}; + let slow = async { + println!("'slow' started."); + trpl::sleep(Duration::from_millis(100)).await; + println!("'slow' finished."); + }; -let fast = async { - println!("'fast' started."); - trpl::sleep(Duration::from_millis(50)).await; - println!("'fast' finished."); -}; + let fast = async { + println!("'fast' started."); + trpl::sleep(Duration::from_millis(50)).await; + println!("'fast' finished."); + }; -trpl::race(slow, fast).await; + trpl::race(slow, fast).await; ``` Listing 17-21: Using `race` to get the result of whichever future finishes first +Each future prints a message when it starts running, pauses for some amount of +time by calling and awaiting `sleep`, and then prints another message when it +finishes. Then we pass both `slow` and `fast` to `trpl::race` and wait for one +of them to finish. (The outcome here isn’t too surprising: `fast` wins.) Unlike +when we used `race` back in “Our First Async Program”, we just ignore the `Either` instance it returns here, because all of +the interesting behavior happens in the body of the async blocks. + Notice that if you flip the order of the arguments to `race`, the order of the “started” messages changes, even though the `fast` future always completes first. That’s because the implementation of this particular `race` function is -not fair. It always runs the futures passed as arguments in the order they’re -passed. Other implementations *are* fair, and will randomly choose which future -to poll first. Regardless of whether the implementation of race we’re using is -fair, though, *one* of the futures will run up to the first `await` in its body -before another task can start. - -Recall from the “Our First Async Program” section of this chapter on page XX -that at each await point, Rust gives a runtime a chance to pause the task and -switch to another one if the future being awaited isn’t ready. The inverse is -also true: Rust *only* pauses async blocks and hands control back to a runtime -at an await point. Everything between await points is synchronous. +not fair. It always runs the futures passed in as arguments in the order in +which they’re passed. Other implementations *are* fair and will randomly choose +which future to poll first. Regardless of whether the implementation of race +we’re using is fair, though, *one* of the futures will run up to the first +`await` in its body before another task can start. + +Recall from Our First Async Program that at each +await point, Rust gives a runtime a chance to pause the task and switch to +another one if the future being awaited isn’t ready. The inverse is also true: +Rust *only* pauses async blocks and hands control back to a runtime at an await +point. Everything between await points is synchronous. That means if you do a bunch of work in an async block without an await point, that future will block any other futures from making progress. You may sometimes hear this referred to as one future *starving* other futures. In some cases, that may not be a big deal. However, if you are doing some kind of expensive -setup or long-running work, or if you have a future which will keep doing some -particular task indefinitely, you’ll need to think about when and where to -hand control back to the runtime. +setup or long-running work, or if you have a future that will keep doing some +particular task indefinitely, you’ll need to think about when and where to hand +control back to the runtime. By the same token, if you have long-running blocking operations, async can be a useful tool for providing ways for different parts of the program to relate to @@ -1450,15 +1561,16 @@ each other. But *how* would you hand control back to the runtime in those cases? -### Yielding +<!-- Old headings. Do not remove or links may break. --> + +<a id="yielding"></a> + +### Yielding Control to the Runtime Let’s simulate a long-running operation. Listing 17-22 introduces a `slow` -function. It uses `std::thread::sleep` instead of `trpl::sleep` so that calling -`slow` will block the current thread for some number of milliseconds. We can use -`slow` to stand in for real-world operations which are both long-running and -blocking. +function. -Filename: src/main.rs +src/main.rs ``` fn slow(name: &str, ms: u64) { @@ -1469,38 +1581,49 @@ fn slow(name: &str, ms: u64) { Listing 17-22: Using `thread::sleep` to simulate slow operations +This code uses `std::thread::sleep` instead of `trpl::sleep` so that calling +`slow` will block the current thread for some number of milliseconds. We can use +`slow` to stand in for real-world operations that are both long-running and +blocking. + In Listing 17-23, we use `slow` to emulate doing this kind of CPU-bound work in -a pair of futures. To begin, each future only hands control back to the runtime -*after* carrying out a bunch of slow operations. +a pair of futures. -Filename: src/main.rs +src/main.rs ``` -let a = async { - println!("'a' started."); - slow("a", 30); - slow("a", 10); - slow("a", 20); - trpl::sleep(Duration::from_millis(50)).await; - println!("'a' finished."); -}; + let a = async { + println!("'a' started."); + slow("a", 30); + slow("a", 10); + slow("a", 20); + trpl::sleep(Duration::from_millis(50)).await; + println!("'a' finished."); + }; -let b = async { - println!("'b' started."); - slow("b", 75); - slow("b", 10); - slow("b", 15); - slow("b", 350); - trpl::sleep(Duration::from_millis(50)).await; - println!("'b' finished."); -}; + let b = async { + println!("'b' started."); + slow("b", 75); + slow("b", 10); + slow("b", 15); + slow("b", 350); + trpl::sleep(Duration::from_millis(50)).await; + println!("'b' finished."); + }; -trpl::race(a, b).await; + trpl::race(a, b).await; ``` Listing 17-23: Using `thread::sleep` to simulate slow operations -If you run this, you will see this output: +To begin, each future only hands control back to the runtime *after* carrying +out a bunch of slow operations. If you run this code, you will see this output: + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-23/ +cargo run +copy just the output +--> ``` 'a' started. @@ -1518,44 +1641,45 @@ If you run this, you will see this output: As with our earlier example, `race` still finishes as soon as `a` is done. There’s no interleaving between the two futures, though. The `a` future does all of its work until the `trpl::sleep` call is awaited, then the `b` future does -all of its work until its own `trpl::sleep` call is awaited, and then the `a` +all of its work until its own `trpl::sleep` call is awaited, and finally the `a` future completes. To allow both futures to make progress between their slow tasks, we need await points so we can hand control back to the runtime. That means we need something we can await! We can already see this kind of handoff happening in Listing 17-23: if we removed the `trpl::sleep` at the end of the `a` future, it would complete -without the `b` future running *at all*. Maybe we could use the `sleep` function -as a starting point? - -Filename: src/main.rs - -``` -let one_ms = Duration::from_millis(1); - -let a = async { - println!("'a' started."); - slow("a", 30); - trpl::sleep(one_ms).await; - slow("a", 10); - trpl::sleep(one_ms).await; - slow("a", 20); - trpl::sleep(one_ms).await; - println!("'a' finished."); -}; - -let b = async { - println!("'b' started."); - slow("b", 75); - trpl::sleep(one_ms).await; - slow("b", 10); - trpl::sleep(one_ms).await; - slow("b", 15); - trpl::sleep(one_ms).await; - slow("b", 35); - trpl::sleep(one_ms).await; - println!("'b' finished."); -}; +without the `b` future running *at all*. Let’s try using the `sleep` function as +a starting point for letting operations switch off making progress, as shown in +Listing 17-24. + +src/main.rs + +``` + let one_ms = Duration::from_millis(1); + + let a = async { + println!("'a' started."); + slow("a", 30); + trpl::sleep(one_ms).await; + slow("a", 10); + trpl::sleep(one_ms).await; + slow("a", 20); + trpl::sleep(one_ms).await; + println!("'a' finished."); + }; + + let b = async { + println!("'b' started."); + slow("b", 75); + trpl::sleep(one_ms).await; + slow("b", 10); + trpl::sleep(one_ms).await; + slow("b", 15); + trpl::sleep(one_ms).await; + slow("b", 35); + trpl::sleep(one_ms).await; + println!("'b' finished."); + }; ``` Listing 17-24: Using `sleep` to let operations switch off making progress @@ -1563,6 +1687,12 @@ Listing 17-24: Using `sleep` to let operations switch off making progress In Listing 17-24, we add `trpl::sleep` calls with await points between each call to `slow`. Now the two futures’ work is interleaved: +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-24 +cargo run +copy just the output +--> + ``` 'a' started. 'a' ran for 30ms @@ -1578,92 +1708,93 @@ to `slow`. Now the two futures’ work is interleaved: The `a` future still runs for a bit before handing off control to `b`, because it calls `slow` before ever calling `trpl::sleep`, but after that the futures swap back and forth each time one of them hits an await point. In this case, we -have done that after every call to `slow`, but we could break up the work -however makes the most sense to us. +have done that after every call to `slow`, but we could break up the work in +whatever way makes the most sense to us. We don’t really want to *sleep* here, though: we want to make progress as fast as we can. We just need to hand back control to the runtime. We can do that directly, using the `yield_now` function. In Listing 17-25, we replace all those `sleep` calls with `yield_now`. -Filename: src/main.rs - -``` -let a = async { - println!("'a' started."); - slow("a", 30); - trpl::yield_now().await; - slow("a", 10); - trpl::yield_now().await; - slow("a", 20); - trpl::yield_now().await; - println!("'a' finished."); -}; - -let b = async { - println!("'b' started."); - slow("b", 75); - trpl::yield_now().await; - slow("b", 10); - trpl::yield_now().await; - slow("b", 15); - trpl::yield_now().await; - slow("b", 35); - trpl::yield_now().await; - println!("'b' finished."); -}; +src/main.rs + +``` + let a = async { + println!("'a' started."); + slow("a", 30); + trpl::yield_now().await; + slow("a", 10); + trpl::yield_now().await; + slow("a", 20); + trpl::yield_now().await; + println!("'a' finished."); + }; + + let b = async { + println!("'b' started."); + slow("b", 75); + trpl::yield_now().await; + slow("b", 10); + trpl::yield_now().await; + slow("b", 15); + trpl::yield_now().await; + slow("b", 35); + trpl::yield_now().await; + println!("'b' finished."); + }; ``` Listing 17-25: Using `yield_now` to let operations switch off making progress -This is both clearer about the actual intent and can be significantly faster -than using `sleep`, because timers such as the one used by `sleep` often have -limits to how granular they can be. The version of `sleep` we are using, for -example, will always sleep for at least a millisecond, even if we pass it a +This code is both clearer about the actual intent and can be significantly +faster than using `sleep`, because timers such as the one used by `sleep` often +have limits on how granular they can be. The version of `sleep` we are using, +for example, will always sleep for at least a millisecond, even if we pass it a `Duration` of one nanosecond. Again, modern computers are *fast*: they can do a lot in one millisecond! You can see this for yourself by setting up a little benchmark, such as the one in Listing 17-26. (This isn’t an especially rigorous way to do performance -testing, but it suffices to show the difference here.) Here, we skip all the -status printing, pass a one-nanosecond `Duration` to `trpl::sleep`, and let -each future run by itself, with no switching between the futures. Then we run -for 1,000 iterations and see how long the future using `trpl::sleep` takes -compared to the future using `trpl::yield_now`. +testing, but it suffices to show the difference here.) -Filename: src/main.rs +src/main.rs ``` -let one_ns = Duration::from_nanos(1); -let start = Instant::now(); -async { - for _ in 1..1000 { - trpl::sleep(one_ns).await; - } -} -.await; -let time = Instant::now() - start; -println!( - "'sleep' version finished after {} seconds.", - time.as_secs_f32() -); - -let start = Instant::now(); -async { - for _ in 1..1000 { - trpl::yield_now().await; - } -} -.await; -let time = Instant::now() - start; -println!( - "'yield' version finished after {} seconds.", - time.as_secs_f32() -); + let one_ns = Duration::from_nanos(1); + let start = Instant::now(); + async { + for _ in 1..1000 { + trpl::sleep(one_ns).await; + } + } + .await; + let time = Instant::now() - start; + println!( + "'sleep' version finished after {} seconds.", + time.as_secs_f32() + ); + + let start = Instant::now(); + async { + for _ in 1..1000 { + trpl::yield_now().await; + } + } + .await; + let time = Instant::now() - start; + println!( + "'yield' version finished after {} seconds.", + time.as_secs_f32() + ); ``` Listing 17-26: Comparing the performance of `sleep` and `yield_now` +Here, we skip all the status printing, pass a one-nanosecond `Duration` to +`trpl::sleep`, and let each future run by itself, with no switching between the +futures. Then we run for 1,000 iterations and see how long the future using +`trpl::sleep` takes compared to the future using `trpl::yield_now`. + The version with `yield_now` is *way* faster! This means that async can be useful even for compute-bound tasks, depending on @@ -1676,42 +1807,40 @@ operating systems, this is the *only* kind of multitasking! In real-world code, you won’t usually be alternating function calls with await points on every single line, of course. While yielding control in this way is -relatively inexpensive, it’s not free! In many cases, trying to break up a +relatively inexpensive, it’s not free. In many cases, trying to break up a compute-bound task might make it significantly slower, so sometimes it’s better -for *overall* performance to let an operation block briefly. You should always +for *overall* performance to let an operation block briefly. Always measure to see what your code’s actual performance bottlenecks are. The -underlying dynamic is an important one to keep in mind if you *are* seeing a -lot of work happening in serial that you expected to happen concurrently, -though! +underlying dynamic is important to keep in mind, though, if you *are* seeing a +lot of work happening in serial that you expected to happen concurrently! ### Building Our Own Async Abstractions We can also compose futures together to create new patterns. For example, we can build a `timeout` function with async building blocks we already have. When -we’re done, the result will be another building block we could use to build up -yet further async abstractions. +we’re done, the result will be another building block we could use to create +still more async abstractions. Listing 17-27 shows how we would expect this `timeout` to work with a slow future. -Filename: src/main.rs +src/main.rs ``` -let slow = async { - trpl::sleep(Duration::from_millis(100)).await; - "I finished!" -}; + let slow = async { + trpl::sleep(Duration::from_millis(100)).await; + "I finished!" + }; -match timeout(slow, Duration::from_millis(10)).await { - Ok(message) => println!("Succeeded with '{message}'"), - Err(duration) => { - println!("Failed after {} seconds", duration.as_secs()) - } -} + match timeout(slow, Duration::from_millis(10)).await { + Ok(message) => println!("Succeeded with '{message}'"), + Err(duration) => { + println!("Failed after {} seconds", duration.as_secs()) + } + } ``` -Listing 17-27: Using our imagined `timeout` to run a slow operation with a time -limit +Listing 17-27: Using our imagined `timeout` to run a slow operation with a time limit Let’s implement this! To begin, let’s think about the API for `timeout`: @@ -1727,7 +1856,9 @@ Let’s implement this! To begin, let’s think about the API for `timeout`: Listing 17-28 shows this declaration. -Filename: src/main.rs +<!-- This is not tested because it intentionally does not compile. --> + +src/main.rs ``` async fn timeout<F: Future>( @@ -1745,19 +1876,16 @@ need: we want to race the future passed in against the duration. We can use `trpl::sleep` to make a timer future from the duration, and use `trpl::race` to run that timer with the future the caller passes in. -We also know that `race` is not fair, and polls arguments in the order they are -passed. Thus, we pass `future_to_try` to `race` first so it gets a chance to -complete even if `max_time` is a very short duration. If `future_to_try` -finishes first, `race` will return `Left` with the output from `future`. If -`timer` finishes first, `race` will return `Right` with the timer’s output of -`()`. +We also know that `race` is not fair, polling arguments in the order in which +they are passed. Thus, we pass `future_to_try` to `race` first so it gets a +chance to complete even if `max_time` is a very short duration. If +`future_to_try` finishes first, `race` will return `Left` with the output from +`future_to_try`. If `timer` finishes first, `race` will return `Right` with the +timer’s output of `()`. -In Listing 17-29, we match on the result of awaiting `trpl::race`. If the -`future_to_try` succeeded and we get a `Left(output)`, we return `Ok(output)`. -If the sleep timer elapsed instead and we get a `Right(())`, we ignore the `()` -with `_` and return `Err(max_time)` instead. +In Listing 17-29, we match on the result of awaiting `trpl::race`. -Filename: src/main.rs +src/main.rs ``` use trpl::Either; @@ -1792,7 +1920,11 @@ async fn timeout<F: Future>( Listing 17-29: Defining `timeout` with `race` and `sleep` -With that, we have a working `timeout`, built out of two other async helpers. If +If the `future_to_try` succeeds and we get a `Left(output)`, we return +`Ok(output)`. If the sleep timer elapses instead and we get a `Right(())`, we +ignore the `()` with `_` and return `Err(max_time)` instead. + +With that, we have a working `timeout` built out of two other async helpers. If we run our code, it will print the failure mode after the timeout: ``` @@ -1800,124 +1932,131 @@ Failed after 2 seconds ``` Because futures compose with other futures, you can build really powerful tools -using smaller async building blocks. For example, you can use this same -approach to combine timeouts with retries, and in turn use those with things -such as network calls—one of the examples from the beginning of the chapter! +using smaller async building blocks. For example, you can use this same approach +to combine timeouts with retries, and in turn use those with operations such as +network calls (one of the examples from the beginning of the chapter). -In practice, you will usually work directly with `async` and `await`, and -secondarily with functions and macros such as `join`, `join_all`, `race`, and -so on. You’ll only need to reach for `pin` now and again to use them with those +In practice, you’ll usually work directly with `async` and `await`, and +secondarily with functions and macros such as `join`, `join_all`, `race`, and so +on. You’ll only need to reach for `pin` now and again to use futures with those APIs. We’ve now seen a number of ways to work with multiple futures at the same time. Up next, we’ll look at how we can work with multiple futures in a -sequence over time, with *streams*. Here are a couple more things you might want +sequence over time with *streams*. Here are a couple more things you might want to consider first, though: * We used a `Vec` with `join_all` to wait for all of the futures in some group to finish. How could you use a `Vec` to process a group of futures in - sequence, instead? What are the tradeoffs of doing that? + sequence instead? What are the tradeoffs of doing that? * Take a look at the `futures::stream::FuturesUnordered` type from the `futures` crate. How would using it be different from using a `Vec`? (Don’t worry about - the fact that it is from the `stream` part of the crate; it works just fine + the fact that it’s from the `stream` part of the crate; it works just fine with any collection of futures.) -## Streams - -So far in this chapter, we have mostly stuck to individual futures. The one big -exception was the async channel we used. Recall how we used the receiver for -our async channel in the “Message Passing” section of this chapter on page XX. -The async `recv` method produces a sequence of items over time. This is an -instance of a much more general pattern, often called a *stream*. - -A sequence of items is something we’ve seen before, when we looked at the -`Iterator` trait in “The `Iterator` Trait and the `next` Method” section of -Chapter 13 on page XX, but there are two differences between iterators and the -async channel receiver. The first difference is the element of time: iterators -are synchronous, while the channel receiver is asynchronous. The second -difference is the API. When working directly with an `Iterator`, we call its -synchronous `next` method. With the `trpl::Receiver` stream in particular, we -called an asynchronous `recv` method instead, but these APIs otherwise feel -very similar. - -That similarity isn’t a coincidence. A stream is similar to an asynchronous -form of iteration. Whereas the `trpl::Receiver` specifically waits to receive -messages, though, the general-purpose stream API is much more general: it -provides the next item the way `Iterator` does, but asynchronously. The -similarity between iterators and streams in Rust means we can actually create a -stream from any iterator. As with an iterator, we can work with a stream by -calling its `next` method and then awaiting the output, as in Listing 17-30. - -Filename: src/main.rs - -``` -let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; -let iter = values.iter().map(|n| n * 2); -let mut stream = trpl::stream_from_iter(iter); - -while let Some(value) = stream.next().await { - println!("The value was: {value}"); -} +## Streams: Futures in Sequence + +<!-- Old headings. Do not remove or links may break. --> + +<a id="streams"></a> + +So far in this chapter, we’ve mostly stuck to individual futures. The one big +exception was the async channel we used. Recall how we used the receiver for our +async channel earlier in this chapter in the “Message +Passing” section. The async `recv` method +produces a sequence of items over time. This is an instance of a much more +general pattern known as a *stream*. + +We saw a sequence of items back in Chapter 13, when we looked at the `Iterator` +trait in The Iterator Trait and the `next` Method section, but there are two differences between iterators and the async +channel receiver. The first difference is time: iterators are synchronous, while +the channel receiver is asynchronous. The second is the API. When working +directly with `Iterator`, we call its synchronous `next` method. With the +`trpl::Receiver` stream in particular, we called an asynchronous `recv` method +instead. Otherwise, these APIs otherwise feel very similar, and that similarity +isn’t a coincidence. A stream is like an asynchronous form of iteration. Whereas +the `trpl::Receiver` specifically waits to receive messages, though, the +general-purpose stream API is much broader: it provides the next item the +way `Iterator` does, but asynchronously. + +The similarity between iterators and streams in Rust means we can actually +create a stream from any iterator. As with an iterator, we can work with a +stream by calling its `next` method and then awaiting the output, as in Listing +17-30. + +src/main.rs + +``` + let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let iter = values.iter().map(|n| n * 2); + let mut stream = trpl::stream_from_iter(iter); + + while let Some(value) = stream.next().await { + println!("The value was: {value}"); + } ``` Listing 17-30: Creating a stream from an iterator and printing its values We start with an array of numbers, which we convert to an iterator and then call `map` on to double all the values. Then we convert the iterator into a stream -using the `trpl::stream_from_iter` function. Then we loop over the items in the +using the `trpl::stream_from_iter` function. Next, we loop over the items in the stream as they arrive with the `while let` loop. -Unfortunately, when we try to run the code, it doesn’t compile. Instead, as we -can see in the output, it reports that there is no `next` method available. +Unfortunately, when we try to run the code, it doesn’t compile, but instead it reports that there’s no `next` method available: + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-30 +cargo build +copy only the error output +--> ``` error[E0599]: no method named `next` found for struct `Iter` in the current scope - --> src/main.rs:8:40 - | -8 | while let Some(value) = stream.next().await { - | ^^^^ - | - = note: the full type name has been written to '~/projects/hello-async/target/debug/deps/async_await-bbd5bb8f6851cb5f.long-type-18426562901668632191.txt' - = note: consider using `--verbose` to print the full type name to the console - = help: items from traits can only be used if the trait is in scope + --> src/main.rs:10:40 + | +10 | while let Some(value) = stream.next().await { + | ^^^^ + | + = note: the full type name has been written to 'file:///projects/async_await/target/debug/deps/async_await-9de943556a6001b8.long-type-1281356139287206597.txt' + = note: consider using `--verbose` to print the full type name to the console + = help: items from traits can only be used if the trait is in scope help: the following traits which provide `next` are implemented but not in scope; perhaps you want to import one of them - | -1 + use futures_util::stream::stream::StreamExt; - | -1 + use std::iter::Iterator; - | -1 + use std::str::pattern::Searcher; - | -1 + use trpl::StreamExt; - | + | +1 + use crate::trpl::StreamExt; + | +1 + use futures_util::stream::stream::StreamExt; + | +1 + use std::iter::Iterator; + | +1 + use std::str::pattern::Searcher; + | help: there is a method `try_next` with a similar name - | -8 | while let Some(value) = stream.try_next().await { - | ~~~~~~~~ - -For more information about this error, try `rustc --explain E0599`. + | +10 | while let Some(value) = stream.try_next().await { + | ~~~~~~~~ ``` -As the output suggests, the reason for the compiler error is that we need the +As this output explains, the reason for the compiler error is that we need the right trait in scope to be able to use the `next` method. Given our discussion -so far, you might reasonably expect that to be `Stream`, but the trait we need -here is actually `StreamExt`. The `Ext` there is for “extension”: this is a -common pattern in the Rust community for extending one trait with another. - -Why do we need `StreamExt` instead of `Stream`, and what does the `Stream` trait -itself do? Briefly, the answer is that throughout the Rust ecosystem, the -`Stream` trait defines a low-level interface which effectively combines the -`Iterator` and `Future` traits. The `StreamExt` trait supplies a higher-level -set of APIs on top of `Stream`, including the `next` method as well as other -utility methods similar to those provided by the `Iterator` trait. We’ll return -to the `Stream` and `StreamExt` traits in a bit more detail at the end of the -chapter. For now, this is enough to let us keep moving. +so far, you might reasonably expect that trait to be `Stream`, but it’s actually +`StreamExt`. Short for *extension*, `Ext` is a common pattern in the +Rust community for extending one trait with another. + +We’ll explain the `Stream` and `StreamExt` traits in a bit more detail at the +end of the chapter, but for now all you need to know is that the `Stream` trait +defines a low-level interface that effectively combines the `Iterator` and +`Future` traits. `StreamExt` supplies a higher-level set of APIs on top of +`Stream`, including the `next` method as well as other utility methods similar +to those provided by the `Iterator` trait. `Stream` and `StreamExt` are not yet +part of Rust’s standard library, but most ecosystem crates use the same +definition. The fix to the compiler error is to add a `use` statement for `trpl::StreamExt`, as in Listing 17-31. -Filename: src/main.rs +src/main.rs ``` use trpl::StreamExt; @@ -1942,7 +2081,7 @@ more, now that we have `StreamExt` in scope, we can use all of its utility methods, just as with iterators. For example, in Listing 17-32, we use the `filter` method to filter out everything but multiples of three and five. -Filename: src/main.rs +src/main.rs ``` use trpl::StreamExt; @@ -1963,35 +2102,28 @@ fn main() { } ``` -Listing 17-32: Filtering a `Stream` with the `StreamExt::filter` method +Listing 17-32: Filtering a stream with the `StreamExt::filter` method -Of course, this isn’t very interesting. We could do that with normal iterators -and without any async at all. So let’s look at some of the other things we can -do which are unique to streams. +Of course, this isn’t very interesting, since we could do the same with normal +iterators and without any async at all. Let’s look at what +we can do that *is* unique to streams. ### Composing Streams Many concepts are naturally represented as streams: items becoming available in -a queue, or working with more data than can fit in a computer’s memory by only -pulling chunks of it from the file system at a time, or data arriving over the +a queue, chunks of data being pulled incrementally from the filesystem when the +full data set is too large for the computer’s , or data arriving over the network over time. Because streams are futures, we can use them with any other -kind of future, too, and we can combine them in interesting ways. For example, -we can batch up events to avoid triggering too many network calls, set timeouts -on sequences of long-running operations, or throttle user interface events to -avoid doing needless work. +kind of future and combine them in interesting ways. For example, we can batch +up events to avoid triggering too many network calls, set timeouts on sequences +of long-running operations, or throttle user interface events to avoid doing +needless work. -Let’s start by building a little stream of messages, as a stand-in for a stream +Let’s start by building a little stream of messages as a stand-in for a stream of data we might see from a WebSocket or another real-time communication -protocol. In Listing 17-33, we create a function `get_messages` which returns -`impl Stream<Item = String>`. For its implementation, we create an async -channel, loop over the first ten letters of the English alphabet, and send them -across the channel. - -We also use a new type: `ReceiverStream`, which converts the `rx` receiver from -the `trpl::channel` into a `Stream` with a `next` method. Back in `main`, we use -a `while let` loop to print all the messages from the stream. +protocol, as shown in Listing 17-33. -Filename: src/main.rs +src/main.rs ``` use trpl::{ReceiverStream, Stream, StreamExt}; @@ -2020,8 +2152,19 @@ fn get_messages() -> impl Stream<Item = String> { Listing 17-33: Using the `rx` receiver as a `ReceiverStream` +First, we create a function called `get_messages` that returns `impl Stream<Item = String>`. For its implementation, we create an async channel, loop over the +first 10 letters of the English alphabet, and send them across the channel. + +We also use a new type: `ReceiverStream`, which converts the `rx` receiver from +the `trpl::channel` into a `Stream` with a `next` method. Back in `main`, we use +a `while let` loop to print all the messages from the stream. + When we run this code, we get exactly the results we would expect: +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the threads running differently rather than +changes in the compiler --> + ``` Message: 'a' Message: 'b' @@ -2035,21 +2178,12 @@ Message: 'i' Message: 'j' ``` -We could do this with the regular `Receiver` API, or even the regular `Iterator` -API, though. Let’s add something that requires streams: adding a timeout -which applies to every item in the stream, and a delay on the items we emit. +Again, we could do this with the regular `Receiver` API or even the regular +`Iterator` API, though, so let’s add a feature that requires streams: adding a +timeout that applies to every item in the stream, and a delay on the items we +emit, as shown in Listing 17-34. -In Listing 17-34, we start by adding a timeout to the stream with the `timeout` -method, which comes from the `StreamExt` trait. Then we update the body of the -`while let` loop, because the stream now returns a `Result`. The `Ok` variant -indicates a message arrived in time; the `Err` variant indicates that the -timeout elapsed before any message arrived. We `match` on that result and either -print the message when we receive it successfully, or print a notice about the -timeout. Finally, notice that we pin the messages after applying the timeout to -them, because the timeout helper produces a stream which needs to be pinned to -be polled. - -Filename: src/main.rs +src/main.rs ``` use std::{pin::pin, time::Duration}; @@ -2070,19 +2204,23 @@ fn main() { } ``` -Listing 17-34: Using the `StreamExt::timeout` method to set a time limit on the -items in a stream +Listing 17-34: Using the `StreamExt::timeout` method to set a time limit on the items in a stream + +We start by adding a timeout to the stream with the `timeout` method, which +comes from the `StreamExt` trait. Then we update the body of the `while let` +loop, because the stream now returns a `Result`. The `Ok` variant indicates a +message arrived in time; the `Err` variant indicates that the timeout elapsed +before any message arrived. We `match` on that result and either print the +message when we receive it successfully or print a notice about the timeout. +Finally, notice that we pin the messages after applying the timeout to them, +because the timeout helper produces a stream that needs to be pinned to be +polled. However, because there are no delays between messages, this timeout does not change the behavior of the program. Let’s add a variable delay to the messages -we send. In `get_messages`, we use the `enumerate` iterator method with the -`messages` array so that we can get the index of each item we are sending along -with the item itself. Then we apply a 100 millisecond delay to even-index items -and a 300 millisecond delay to odd-index items, to simulate the different delays -we might see from a stream of messages in the real world. Because our timeout is -for 200 milliseconds, this should affect half of the messages. +we send, as shown in Listing 17-35. -Filename: src/main.rs +src/main.rs ``` fn get_messages() -> impl Stream<Item = String> { @@ -2102,8 +2240,14 @@ fn get_messages() -> impl Stream<Item = String> { } ``` -Listing 17-35: Sending messages through `tx` with an async delay without making -`get_messages` an async function +Listing 17-35: Sending messages through `tx` with an async delay without making `get_messages` an async function + +In `get_messages`, we use the `enumerate` iterator method with the `messages` +array so that we can get the index of each item we’re sending along with the +item itself. Then we apply a 100-millisecond delay to even-index items and a +300-millisecond delay to odd-index items to simulate the different delays we +might see from a stream of messages in the real world. Because our timeout is +for 200 milliseconds, this should affect half of the messages. To sleep between messages in the `get_messages` function without blocking, we need to use async. However, we can’t make `get_messages` itself into an async @@ -2111,24 +2255,29 @@ function, because then we’d return a `Future<Output = Stream<Item = String>>` instead of a `Stream<Item = String>>`. The caller would have to await `get_messages` itself to get access to the stream. But remember: everything in a given future happens linearly; concurrency happens *between* futures. Awaiting -`get_messages` would require it to send all the messages, including sleeping -between sending each message, before returning the receiver stream. As a result, -the timeout would end up useless. There would be no delays in the stream itself: -the delays would all happen before the stream was even available. +`get_messages` would require it to send all the messages, including the sleep +delay between each message, before returning the receiver stream. As a result, +the timeout would be useless. There would be no delays in the stream itself; +they would all happen before the stream was even available. + +Instead, we leave `get_messages` as a regular function that returns a stream, +and we spawn a task to handle the async `sleep` calls. -Instead, we leave `get_messages` as a regular function which returns a stream, -and spawn a task to handle the async `sleep` calls. +> Note: Calling `spawn_task` in this way works because we already set up our +> runtime; had we not, it would cause a panic. Other implementations choose +> different tradeoffs: they might spawn a new runtime and avoid the panic but +> end up with a bit of extra overhead, or they may simply not provide a +> standalone way to spawn tasks without reference to a runtime. Make sure you +> know what tradeoff your runtime has chosen and write your code accordingly! -> Note: calling `spawn_task` in this way works because we already set up our -> runtime. Calling this particular implementation of `spawn_task` *without* -> first setting up a runtime will cause a panic. Other implementations choose -> different tradeoffs: they might spawn a new runtime and so avoid the panic but -> end up with a bit of extra overhead, or simply not provide a standalone way to -> spawn tasks without reference to a runtime. You should make sure you know what -> tradeoff your runtime has chosen and write your code accordingly! +Now our code has a much more interesting result. Between every other pair of +messages, a `Problem: Elapsed(())` error. -Now our code has a much more interesting result! Between every other pair of -messages, we see an error reported: `Problem: Elapsed(())`. +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-35 +cargo run +copy only the program output, *not* the compiler output +--> ``` Message: 'a' @@ -2148,36 +2297,27 @@ Problem: Elapsed(()) Message: 'j' ``` -The timeout doesn’t prevent the messages from arriving in the end—we still get -all of the original messages. This is because our channel is unbounded: it can -hold as many messages as we can fit in memory. If the message doesn’t arrive -before the timeout, our stream handler will account for that, but when it polls -the stream again, the message may now have arrived. +The timeout doesn’t prevent the messages from arriving in the end. We still get +all of the original messages, because our channel is *unbounded*: it can hold as +many messages as we can fit in memory. If the message doesn’t arrive before the +timeout, our stream handler will account for that, but when it polls the stream +again, the message may now have arrived. -You can get different behavior if needed by using other kinds of channels, or -other kinds of streams more generally. Let’s see one of those in practice in our -final example for this section, by combining a stream of time intervals with -this stream of messages. +You can get different behavior if needed by using other kinds of channels or +other kinds of streams more generally. Let’s see one of those in practice by +combining a stream of time intervals with this stream of messages. ### Merging Streams First, let’s create another stream, which will emit an item every millisecond if we let it run directly. For simplicity, we can use the `sleep` function to send -a message on a delay, and combine it with the same approach of creating a stream -from a channel we used in `get_messages`. The difference is that this time, -we’re going to send back the count of intervals which has elapsed, so the return -type will be `impl Stream<Item = u32>`, and we can call the function -`get_intervals`. +a message on a delay and combine it with the same approach we used in +`get_messages` of creating a stream from a channel. The difference is that this +time, we’re going to send back the count of intervals that have elapsed, so the +return type will be `impl Stream<Item = u32>`, and we can call the function +`get_intervals` (see Listing 17-36). -In Listing 17-36, we start by defining a `count` in the task. (We could define -it outside the task, too, but it is clearer to limit the scope of any given -variable.) Then we create an infinite loop. Each iteration of the loop -asynchronously sleeps for one millisecond, increments the count, and then sends -it over the channel. Because this is all wrapped in the task created by -`spawn_task`, all of it will get cleaned up along with the runtime, including -the infinite loop. - -Filename: src/main.rs +src/main.rs ``` fn get_intervals() -> impl Stream<Item = u32> { @@ -2196,70 +2336,78 @@ fn get_intervals() -> impl Stream<Item = u32> { } ``` -Listing 17-36: Creating a stream with a counter that will be emitted once every -millisecond +Listing 17-36: Creating a stream with a counter that will be emitted once every millisecond + +We start by defining a `count` in the task. (We could define it outside the +task, too, but it’s clearer to limit the scope of any given variable.) Then we +create an infinite loop. Each iteration of the loop asynchronously sleeps for +one millisecond, increments the count, and then sends it over the channel. +Because this is all wrapped in the task created by `spawn_task`, all of +it—including the infinite loop—will get cleaned up along with the runtime. -This kind of infinite loop, which only ends when the whole runtime gets torn +This kind of infinite loop, which ends only when the whole runtime gets torn down, is fairly common in async Rust: many programs need to keep running indefinitely. With async, this doesn’t block anything else, as long as there is at least one await point in each iteration through the loop. -Back in our main function’s async block, we start by calling `get_intervals`. -Then we merge the `messages` and `intervals` streams with the `merge` method, -which combines multiple streams into one stream that produces items from any of -the source streams as soon as the items are available, without imposing any -particular ordering. Finally, we loop over that combined stream instead of over -`messages` (Listing 17-37). +Now, back in our main function’s async block, we can attempt to merge the +`messages` and `intervals` streams, as shown in Listing 17-37. -Filename: src/main.rs +src/main.rs ``` -let messages = get_messages().timeout(Duration::from_millis(200)); -let intervals = get_intervals(); -let merged = messages.merge(intervals); + let messages = get_messages().timeout(Duration::from_millis(200)); + let intervals = get_intervals(); + let merged = messages.merge(intervals); ``` -Listing 17-37: Attempting to merge streams of messages and intervals +Listing 17-37: Attempting to merge the `messages` and `intervals` streams -At this point, neither `messages` nor `intervals` needs to be pinned or -mutable, because both will be combined into the single `merged` stream. -However, this call to `merge` does not compile! (Neither does the `next` call -in the `while let` loop, but we’ll come back to that after fixing this.) The -two streams have different types. The `messages` stream has the type -`Timeout<impl Stream<Item = String>>`, where `Timeout` is the type which -implements `Stream` for a `timeout` call. Meanwhile, the `intervals` stream has -the type `impl Stream<Item = u32>`. To merge these two streams, we need to -transform one of them to match the other. +We start by calling `get_intervals`. Then we merge the `messages` and +`intervals` streams with the `merge` method, which combines multiple streams +into one stream that produces items from any of the source streams as soon as +the items are available, without imposing any particular ordering. Finally, we +loop over that combined stream instead of over `messages`. -In Listing 17-38, we rework the `intervals` stream, because `messages` is -already in the basic format we want and has to handle timeout errors. First, we -can use the `map` helper method to transform the `intervals` into a string. -Second, we need to match the `Timeout` from `messages`. Because we don’t -actually *want* a timeout for `intervals`, though, we can just create a timeout -which is longer than the other durations we are using. Here, we create a -10-second timeout with `Duration::from_secs(10)`. Finally, we need to make -`stream` mutable, so that the `while let` loop’s `next` calls can iterate -through the stream, and pin it so that it’s safe to do so. +At this point, neither `messages` nor `intervals` needs to be pinned or mutable, +because both will be combined into the single `merged` stream. However, this +call to `merge` doesn’t compile! (Neither does the `next` call in the `while let` loop, but we’ll come back to that.) This is because the two streams have +different types. The `messages` stream has the type `Timeout<impl Stream<Item = String>>`, where `Timeout` is the type that implements `Stream` for a `timeout` +call. The `intervals` stream has the type `impl Stream<Item = u32>`. To merge +these two streams, we need to transform one of them to match the other. We’ll +rework the intervals stream, because messages is already in the basic format we +want and has to handle timeout errors (see Listing 17-38). + +<!-- We cannot directly test this one, because it never stops. --> -Filename: src/main.rs +src/main.rs ``` -let messages = get_messages().timeout(Duration::from_millis(200)); -let intervals = get_intervals() - .map(|count| format!("Interval: {count}")) - .timeout(Duration::from_secs(10)); -let merged = messages.merge(intervals); -let mut stream = pin!(merged); + let messages = get_messages().timeout(Duration::from_millis(200)); + let intervals = get_intervals() + .map(|count| format!("Interval: {count}")) + .timeout(Duration::from_secs(10)); + let merged = messages.merge(intervals); + let mut stream = pin!(merged); ``` -Listing 17-38: Aligning the types of the the `intervals` stream with the type -of the `messages` stream +Listing 17-38: Aligning the type of the the `intervals` stream with the type of the `messages` stream + +First, we can use the `map` helper method to transform the `intervals` into a +string. Second, we need to match the `Timeout` from `messages`. Because we don’t +actually *want* a timeout for `intervals`, though, we can just create a timeout +which is longer than the other durations we are using. Here, we create a +10-second timeout with `Duration::from_secs(10)`. Finally, we need to make +`stream` mutable, so that the `while let` loop’s `next` calls can iterate +through the stream, and pin it so that it’s safe to do so. That gets us *almost* +to where we need to be. Everything type checks. If you run this, though, there +will be two problems. First, it will never stop! You’ll need to stop it with +<span class="keystroke">ctrl-c</span>. Second, the messages from the English +alphabet will be buried in the midst of all the interval counter messages: -That gets us *almost* to where we need to be. Everything type checks. If you run -this, though, there will be two problems. First, it will never stop! You’ll -need to stop it with <span class="keystroke">ctrl-c</span>. Second, the -messages from the English alphabet will be buried in the midst of all the -interval counter messages: +<!-- Not extracting output because changes to this output aren't significant; +the changes are likely to be due to the tasks running differently rather than +changes in the compiler --> ``` --snip-- @@ -2273,42 +2421,47 @@ Interval: 43 --snip-- ``` -Listing 17-39 shows one way to solve these last two problems. First, we use the -`throttle` method on the `intervals` stream, so that it doesn’t overwhelm the -`messages` stream. Throttling is a way of limiting the rate at which a function -will be called—or, in this case, how often the stream will be polled. Once every -hundred milliseconds should do, because that is in the same ballpark as how -often our messages arrive. - -To limit the number of items we will accept from a stream, we can use the `take` -method. We apply it to the *merged* stream, because we want to limit the final -output, not just one stream or the other. +Listing 17-39 shows one way to solve these last two problems. -Filename: src/main.rs +src/main.rs ``` -let messages = get_messages().timeout(Duration::from_millis(200)); -let intervals = get_intervals() - .map(|count| format!("Interval: {count}")) - .throttle(Duration::from_millis(100)) - .timeout(Duration::from_secs(10)); -let merged = messages.merge(intervals).take(20); -let mut stream = pin!(merged); + let messages = get_messages().timeout(Duration::from_millis(200)); + let intervals = get_intervals() + .map(|count| format!("Interval: {count}")) + .throttle(Duration::from_millis(100)) + .timeout(Duration::from_secs(10)); + let merged = messages.merge(intervals).take(20); + let mut stream = pin!(merged); ``` Listing 17-39: Using `throttle` and `take` to manage the merged streams -Now when we run the program, it stops after pulling twenty items from the -stream, and the intervals don’t overwhelm the messages. We also don’t get -`Interval: 100` or `Interval: 200` or so on, but instead get `Interval: 1`, -`Interval: 2`, and so on—even though we have a source stream which *can* -produce an event every millisecond. That’s because the `throttle` call -produces a new stream, wrapping the original stream, so that the original -stream only gets polled at the throttle rate, not its own “native” rate. We -don’t have a bunch of unhandled interval messages we’re choosing to -ignore. Instead, we never produce those interval messages in the first place! -This is the inherent “laziness” of Rust’s futures at work again, allowing us to -choose our performance characteristics. +First, we use the `throttle` method on the `intervals` stream so that it doesn’t +overwhelm the `messages` stream. *Throttling* is a way of limiting the rate at +which a function will be called—or, in this case, how often the stream will be +polled. Once every 100 milliseconds should do, because that’s roughly how often +our messages arrive. + +To limit the number of items we will accept from a stream, we apply the `take` +method to the `merged` stream, because we want to limit the final output, not +just one stream or the other. + +Now when we run the program, it stops after pulling 20 items from the stream, +and the intervals don’t overwhelm the messages. We also don’t get `Interval: 100` or `Interval: 200` or so on, but instead get `Interval: 1`, `Interval: 2`, +and so on—even though we have a source stream that *can* produce an event every +millisecond. That’s because the `throttle` call produces a new stream that wraps +the original stream so that the original stream gets polled only at the throttle +rate, not its own “native” rate. We don’t have a bunch of unhandled interval +messages we’re choosing to ignore. Instead, we never produce those interval +messages in the first place! This is the inherent “laziness” of Rust’s futures +at work again, allowing us to choose our performance characteristics. + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-39 +cargo run +copy and paste only the program output +--> ``` Interval: 1 @@ -2336,12 +2489,12 @@ Interval: 12 There’s one last thing we need to handle: errors! With both of these channel-based streams, the `send` calls could fail when the other side of the channel closes—and that’s just a matter of how the runtime executes the futures -which make up the stream. Up until now we have ignored this by calling `unwrap`, -but in a well-behaved app, we should explicitly handle the error, at minimum by -ending the loop so we don’t try to send any more messages! Listing 17-40 shows -a simple error strategy: print the issue and then `break` from the loops. As -usual, the correct way to handle a message send error will vary—just make sure -you have a strategy. +that make up the stream. Up until now, we’ve ignored this possibility by calling +`unwrap`, but in a well-behaved app, we should explicitly handle the error, at +minimum by ending the loop so we don’t try to send any more messages. Listing +17-40 shows a simple error strategy: print the issue and then `break` from the +loops. + ``` fn get_messages() -> impl Stream<Item = String> { @@ -2386,25 +2539,35 @@ fn get_intervals() -> impl Stream<Item = u32> { Listing 17-40: Handling errors and shutting down the loops -Now that we’ve seen a bunch of async in practice, let’s take a step back and -dig into a few of the details of how `Future`, `Stream`, and the other key -traits which Rust uses to make async work. +As usual, the correct way to handle a message send error will vary; just make +sure you have a strategy. + +Now that we’ve seen a bunch of async in practice, let’s take a step back and dig +into a few of the details of how `Future`, `Stream`, and the other key traits +Rust uses to make async work. + +## A Closer Look at the Traits for Async + +<!-- Old headings. Do not remove or links may break. --> -## Digging Into the Traits for Async +<a id="digging-into-the-traits-for-async"></a> Throughout the chapter, we’ve used the `Future`, `Pin`, `Unpin`, `Stream`, and -`StreamExt` traits in various ways. So far, though, we’ve avoided digging too -far into the details of how they work or how they fit together. Much of the time -when writing Rust day to day, this is fine. Sometimes, though, you’ll hit -situations where understanding a few more of these details matters. In this -section, we’ll dig down *enough* further to help with those situations—while -still leaving the *really* deep dive for other documentation! +`StreamExt` traits in various ways. So far, though, we’ve avoided getting too +far into the details of how they work or how they fit together, which is fine +most of the time for your day-to-day Rust work. Sometimes, though, you’ll +encounter situations where you’ll need to understand a few more of these +details. In this section, we’ll dig in just enough to help in those scenarios, +still leaving the *really* deep dive for other documentation. -### Future +<!-- Old headings. Do not remove or links may break. --> -Back in the “Futures and the Async Syntax” section of this chapter on page XX, -we noted that `Future` is a trait. Let’s start by taking a closer look at how -it works. Here is how Rust defines a `Future`: +<a id="future"></a> + +### The Future Trait + +Let’s start by taking a closer look at how the `Future` trait works. Here’s how +Rust defines it: ``` use std::pin::Pin; @@ -2424,8 +2587,8 @@ First, `Future`’s associated type `Output` says what the future resolves to. This is analogous to the `Item` associated type for the `Iterator` trait. Second, `Future` also has the `poll` method, which takes a special `Pin` reference for its `self` parameter and a mutable reference to a `Context` type, -and returns a `Poll<Self::Output>`. We’ll talk a little more about `Pin` and -`Context` later in the section. For now, let’s focus on what the method returns, +and returns a `Poll<Self::Output>`. We’ll talk more about `Pin` and +`Context` in a moment. For now, let’s focus on what the method returns, the `Poll` type: ``` @@ -2435,20 +2598,20 @@ enum Poll<T> { } ``` -This `Poll` type is similar to an `Option`: it has one variant which has a value -(`Ready(T)`), and one which does not (`Pending`). It means something quite -different, though! The `Pending` variant indicates that the future still has -work to do, so the caller will need to check again later. The `Ready` variant -indicates that the `Future` has finished its work and the `T` value is +This `Poll` type is similar to an `Option`. It has one variant that has a value, +`Ready(T)`, and one which does not, `Pending`. `Poll` means something quite +different from `Option`, though! The `Pending` variant indicates that the future +still has work to do, so the caller will need to check again later. The `Ready` +variant indicates that the future has finished its work and the `T` value is available. > Note: With most futures, the caller should not call `poll` again after the > future has returned `Ready`. Many futures will panic if polled again after -> becoming ready! Futures which are safe to poll again will say so explicitly in -> their documentation. This is similar to how `Iterator::next` behaves! +> becoming ready. Futures that are safe to poll again will say so explicitly in +> their documentation. This is similar to how `Iterator::next` behaves. -Under the hood, when you see code which uses `await`, Rust compiles that to code -which calls `poll`. If you look back at Listing 17-4, where we printed out the +When you see code that uses `await`, Rust compiles it under the hood to code +that calls `poll`. If you look back at Listing 17-4, where we printed out the page title for a single URL once it resolved, Rust compiles it into something kind of (although not exactly) like this: @@ -2464,9 +2627,9 @@ match page_title(url).poll() { } ``` -What should we do when the `Future` is still `Pending`? We need some way to try -again… and again, and again, until the future is finally ready. In other words, -a loop: +What should we do when the future is still `Pending`? We need some way to try +again, and again, and again, until the future is finally ready. In other words, +we need a loop: ``` let mut page_title_fut = page_title(url); @@ -2484,73 +2647,78 @@ loop { ``` If Rust compiled it to exactly that code, though, every `await` would be -blocking—exactly the opposite of what we were going for! Instead, Rust needs -makes sure that the loop can hand off control to something which can pause work -on this future and work on other futures and check this one again later. That -“something” is an async runtime, and this scheduling and coordination work is -one of the main jobs for a runtime. - -Recall our description (in the “Counting” section of this chapter on page XX) -of waiting on `rx.recv`. The `recv` call returns a `Future`, and awaiting it -polls it. In our initial discussion, we noted that a runtime will pause the -future until it’s ready with either `Some(message)` or `None` when the channel -closes. With our deeper understanding of `Future` in place, and specifically -`Future::poll`, we can see how that works. The runtime knows the future isn’t -ready when it returns `Poll::Pending`. Conversely, the runtime knows the future -is ready and advances it when `poll` returns `Poll::Ready(Some(message))` or -`Poll::Ready(None)`. - -The exact details of how a runtime does that are more than we will cover in even -this deep dive section. The key here is to see the basic mechanic of futures: a -runtime *polls* each future it is responsible for, putting it back to sleep when -it is not yet ready. - -### Pinning and the Pin and Unpin Traits - -When we introduced the idea of pinning while working on Listing 17-17, we ran -into a very gnarly error message. Here is the relevant part of it again: - -``` -error[E0277]: `{async block@src/main.rs:8:23: 20:10}` cannot be unpinned - --> src/main.rs:46:33 +blocking—exactly the opposite of what we were going for! Instead, Rust makes +sure that the loop can hand off control to something that can pause work on this +future to work on other futures and then check this one again later. As we’ve +seen, that something is an async runtime, and this scheduling and coordination +work is one of its main jobs. + +Earlier in the chapter, we described waiting on `rx.recv`. The `recv` call +returns a future, and awaiting the future polls it. We noted that a runtime will +pause the future until it’s ready with either `Some(message)` or `None` when the +channel closes. With our deeper understanding of the `Future` trait, and +specifically `Future::poll`, we can see how that works. The runtime knows the +future isn’t ready when it returns `Poll::Pending`. Conversely, the runtime +knows the future *is* ready and advances it when `poll` returns +`Poll::Ready(Some(message))` or `Poll::Ready(None)`. + +The exact details of how a runtime does that are beyond the scope of this book, +but the key is to see the basic mechanics of futures: a runtime *polls* each +future it is responsible for, putting the future back to sleep when it is not +yet ready. + +<!-- Old headings. Do not remove or links may break. --> + +<a id="pinning-and-the-pin-and-unpin-traits"></a> + +### The Pin and Unpin Traits + +When we introduced the idea of pinning in Listing 17-16, we ran into a very +gnarly error message. Here is the relevant part of it again: + +<!-- manual-regeneration +cd listings/ch17-async-await/listing-17-16 +cargo build +copy *only* the final `error` block from the errors +--> + +``` +error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned + --> src/main.rs:48:33 | -46 | trpl::join_all(futures).await; - | ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:8:23: 20:10}`, which is required by `Box<{async block@src/main.rs:8:23: 20:10}>: std::future::Future` +48 | trpl::join_all(futures).await; + | ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}`, which is required by `Box<{async block@src/main.rs:10:23: 10:33}>: Future` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:8:23: 20:10}>` to implement `std::future::Future` -note: required by a bound in `JoinAll` - --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 + = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` +note: required by a bound in `futures_util::future::join_all::JoinAll` + --> file:///home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8 | 27 | pub struct JoinAll<F> | ------- required by a bound in this struct 28 | where 29 | F: Future, | ^^^^^^ required by this bound in `JoinAll` - -Some errors have detailed explanations: E0277, E0308. -For more information about an error, try `rustc --explain E0277`. ``` -When we read this error message carefully, it not only tells us that we need to -pin the values, but also tells us why pinning is required. The `trpl::join_all` -function returns a struct called `JoinAll`. That struct is generic over a type -`F`, which is constrained to implement the `Future` trait. Directly awaiting a -future with `await` pins the future implicitly. That’s why we don’t need to use -`pin!` everywhere we want to await futures. +This error message tells us not only that we need to pin the values but also why +pinning is required. The `trpl::join_all` function returns a struct called +`JoinAll`. That struct is generic over a type `F`, which is constrained to +implement the `Future` trait. Directly awaiting a future with `await` pins the +future implicitly. That’s why we don’t need to use `pin!` everywhere we want to +await futures. However, we’re not directly awaiting a future here. Instead, we construct a new future, `JoinAll`, by passing a collection of futures to the `join_all` -function. The signature for `join_all` produces requires that the type of the -items in the collection all implement the `Future` trait, and `Box<T>` only -implements `Future` if the `T` that it wraps is a future which implements the -`Unpin` trait. +function. The signature for `join_all` requires that the types of the items in +the collection all implement the `Future` trait, and `Box<T>` implements +`Future` only if the `T` it wraps is a future that implements the `Unpin` trait. -That’s a lot! But we can understand it, if we dive a little further into how the -`Future` type actually works, in particular around *pinning*. +That’s a lot to absorb! To really understand it, let’s we dive a little further +into how the `Future` trait actually works, in particular around *pinning*. -Let’s look again at the definition of `Future`: +Look again at the definition of the `Future` trait: ``` use std::pin::Pin; @@ -2564,210 +2732,203 @@ pub trait Future { } ``` -The `cx` parameter and its `Context` type is the key to how a runtime actually -knows when to check any given future, while still being lazy. The details of how -that works are beyond the scope of this chapter, though: you generally only need -to worry about it when writing a custom `Future` implementation. - -Instead, we’ll focus on the type for `self`. This is the first time we’ve seen -a method where `self` has a type annotation. A type annotation for `self` is -similar to type annotations for other function parameters, with two key -differences. First, when we specify the type of `self` in this way, we’re -telling Rust what type `self` must be to call this method. Second, a type -annotation on `self` can’t be just any type. It’s only allowed to be the type -on which the method is implemented, a reference or smart pointer to that type, -or a `Pin` wrapping a reference to that type. We’ll see more on this syntax in -Chapter 18. For now, it’s enough to know that if we want to poll a future (to -check whether it is `Pending` or `Ready(Output)`), we need a mutable reference -to the type, which is wrapped in a `Pin`. - -`Pin` is a wrapper type. In some ways, it’s similar to the `Box`, `Rc`, and -other smart pointer types we saw in Chapter 15, which also wrap other types. -Unlike those, however, `Pin` only works with *pointer types* such as references -(`&` and `&mut`) and smart pointers (`Box`, `Rc`, and so on). To be precise, -`Pin` works with types which implement the `Deref` or `DerefMut` traits, which -we covered in the “Treating Smart Pointers Like Regular References with the -Deref Trait” section of Chapter 15 on page XX. You can think of this restriction -as equivalent to only working with pointers, though, because implementing -`Deref` or `DerefMut` means your type behaves similarly to a pointer type. `Pin` -is also not a pointer itself, and it doesn’t have any behavior of its own the -way `Rc` and `Arc` do with ref counting. It’s purely a tool the compiler can use -to uphold the relevant guarantees, by wrapping pointers in the type. - -Recalling that `await` is implemented in terms of calls to `poll`, this starts -to explain the error message we saw above—but that was in terms of `Unpin`, not -`Pin`. So what exactly are `Pin` and `Unpin`, how do they relate, and why does -`Future` need `self` to be in a `Pin` type to call `poll`? - -In the “”ur First Async Program” section of this chapter on page XX, we -described how a series of await points in a future get compiled into a state -machine—and noted how the compiler helps make sure that state machine follows -all of Rust’s normal rules around safety, including borrowing and ownership. To -make that work, Rust looks at what data is needed between each await point and -the next await point or the end of the async block. It then creates a -corresponding variant in the state machine it creates. Each variant gets the -access it needs to the data that will be used in that section of the source -code, whether by taking ownership of that data or by getting a mutable or +The `cx` parameter and its `Context` type are the key to how a runtime actually +knows when to check any given future while still being lazy. Again, the details +of how that works are beyond the scope of this chapter, and you generally only +need to think about this when writing a custom `Future` implementation. We’ll +focus instead on the type for `self`, as this is the first time we’ve seen a +method where `self` has a type annotation. A type annotation for `self` is works +like type annotations for other function parameters, but with two key +differences: + +* It tells Rust what type `self` must be for the method to be called. + +* It can’t be just any type. It’s restricted to the type on which the method is + implemented, a reference or smart pointer to that type, or a `Pin` wrapping a + reference to that type. + +We’ll see more on this syntax in Chapter 18. For now, +it’s enough to know that if we want to poll a future to check whether it is +`Pending` or `Ready(Output)`, we need a `Pin`-wrapped mutable reference to the +type. + +`Pin` is a wrapper for pointer-like types such as `&`, `&mut`, `Box`, and `Rc`. +(Technically, `Pin` works with types that implement the `Deref` or `DerefMut` +traits, but this is effectively equivalent to working only with pointers.) `Pin` +is not a pointer itself and doesn’t have any behavior of its own like `Rc` and +`Arc` do with reference counting; it’s purely a tool the compiler can use to +enforce constraints on pointer usage. + +Recalling that `await` is implemented in terms of calls to `poll` starts to +explain the error message we saw earlier, but that was in terms of `Unpin`, not +`Pin`. So how exactly does `Pin` relate to `Unpin`, and why does `Future` need +`self` to be in a `Pin` type to call `poll`? + +Remember from earlier in this chapter a series of await points in a future get +compiled into a state machine, and the compiler makes sure that state machine +follows all of Rust’s normal rules around safety, including borrowing and +ownership. To make that work, Rust looks at what data is needed between one +await point and either the next await point or the end of the async block. It +then creates a corresponding variant in the compiled state machine. Each variant +gets the access it needs to the data that will be used in that section of the +source code, whether by taking ownership of that data or by getting a mutable or immutable reference to it. -So far so good: if we get anything wrong about the ownership or references in a +So far, so good: if we get anything wrong about the ownership or references in a given async block, the borrow checker will tell us. When we want to move around the future that corresponds to that block—like moving it into a `Vec` to pass to -`join_all`, the way we did back in—things get trickier. +`join_all`—things get trickier. -When we move a future—whether by pushing into a data structure to use as an -iterator with `join_all`, or returning them from a function—that actually means +When we move a future—whether by pushing it into a data structure to use as an +iterator with `join_all` or by returning it from a function—that actually means moving the state machine Rust creates for us. And unlike most other types in Rust, the futures Rust creates for async blocks can end up with references to -themselves in the fields of any given variant, as in Figure 17-4 (a simplified -illustration to help you get a feel for the idea, rather than digging into what -are often fairly complicated details). +themselves in the fields of any given variant, as shown in the simplified illustration in Figure 17-4. -<img alt="Concurrent work flow" src="img/trpl17-04.svg" /> +<img alt="A single-column, three-row table representing a future, fut1, which has data values 0 and 1 in the first two rows and an arrow pointing from the third row back to the second row, representing an internal reference within the future." src="img/trpl17-04.svg" class="center" /> Figure 17-4: A self-referential data type. -By default, though, any object which has a reference to itself is unsafe to -move, because references always point to the actual memory address of the thing -they refer to. If you move the data structure itself, those internal references -will be left pointing to the old location. However, that memory location is now -invalid. For one thing, its value will not be updated when you make changes to -the data structure. For another—and more importantly!—the computer is now free -to reuse that memory for other things! You could end up reading completely -unrelated data later. +By default, though, any object that has a reference to itself is unsafe to move, +because references always point to the actual memory address of whatever they +refer to (see Figure 17-5). If you move the data structure itself, those +internal references will be left pointing to the old location. However, that +memory location is now invalid. For one thing, its value will not be updated +when you make changes to the data structure. For another—more important—thing, +the computer is now free to reuse that memory for other purposes! You could end +up reading completely unrelated data later. -<img alt="Concurrent work flow" src="img/trpl17-05.svg" /> +<img alt="Two tables, depicting two futures, fut1 and fut2, each of which has one column and three rows, representing the result of having moved a future out of fut1 into fut2. The first, fut1, is grayed out, with a question mark in each index, representing unknown memory. The second, fut2, has 0 and 1 in the first and second rows and an arrow pointing from its third row back to the second row of fut1, representing a pointer that is referencing the old location in memory of the future before it was moved." src="img/trpl17-05.svg" class="center" /> -Figure 17-5: The unsafe result of moving a self-referential data type. +Figure 17-5: The unsafe result of moving a self-referential data type -In principle, the Rust compiler could try to update every reference to an object -every time it gets moved. That would potentially be a lot of performance -overhead, especially given there can be a whole web of references that need -updating. On the other hand, if we could make sure the data structure in -question *doesn’t move in memory*, we don’t have to update any references. -This is exactly what Rust’s borrow checker requires: you can’t move an item -which has any active references to it using safe code. +Theoretically, the Rust compiler could try to update every reference to an +object whenever it gets moved, but that could add a lot of performance overhead, +especially if a whole web of references needs updating. If we could instead make +sure the data structure in question *doesn’t move in memory*, we wouldn’t have +to update any references. This is exactly what Rust’s borrow checker requires: +in safe code, it prevents you from moving any item with an active reference to +it. `Pin` builds on that to give us the exact guarantee we need. When we *pin* a value by wrapping a pointer to that value in `Pin`, it can no longer move. Thus, if you have `Pin<Box<SomeType>>`, you actually pin the `SomeType` value, *not* -the `Box` pointer. Figure 17-6 illustrates this: +the `Box` pointer. Figure 17-6 illustrates this process. -<img alt="Concurrent work flow" src="img/trpl17-06.svg" /> +<img alt="Three boxes laid out side by side. The first is labeled “Pin”, the second “b1”, and the third “pinned”. Within “pinned” is a table labeled “fut”, with a single column; it represents a future with cells for each part of the data structure. Its first cell has the value “0”, its second cell has an arrow coming out of it and pointing to the fourth and final cell, which has the value “1” in it, and the third cell has dashed lines and an ellipsis to indicate there may be other parts to the data structure. All together, the “fut” table represents a future which is self-referential. An arrow leaves the box labeled “Pin”, goes through the box labeled “b1” and has terminates inside the “pinned” box at the “fut” table." src="img/trpl17-06.svg" class="center" /> -Figure 17-6: Pinning a `Box` which points to a self-referential future type. +Figure 17-6: Pinning a `Box` that points to a self-referential future type. In fact, the `Box` pointer can still move around freely. Remember: we care about -making sure the data ultimately being referenced stays in its place. If a -pointer moves around, but the data it points to is in the same place, as in -Figure 17-7, there’s no potential problem. (How you would do this with a `Pin` -wrapping a `Box` is more than we’ll get into in this particular discussion, -but it would make for a good exercise! If you look at the docs for the types as -well as the `std::pin` module, you might be able to work out how you would do -that.) The key is that the self-referential type itself cannot move, because it -is still pinned. +making sure the data ultimately being referenced stays in place. If a pointer +moves around, *but the data it points to is in the same place*, as in Figure +17-7, there’s no potential problem. As an independent exercise, look at the docs +for the types as well as the `std::pin` module and try to work out how you’d do +this with a `Pin` wrapping a `Box`.) The key is that the self-referential type +itself cannot move, because it is still pinned. -<img alt="Concurrent work flow" src="img/trpl17-07.svg" /> +<img alt="Four boxes laid out in three rough columns, identical to the previous diagram with a change to the second column. Now there are two boxes in the second column, labeled “b1” and “b2”, “b1” is grayed out, and the arrow from “Pin” goes through “b2” instead of “b1”, indicating that the pointer has moved from “b1” to “b2”, but the data in “pinned” has not moved." src="img/trpl17-07.svg" class="center" /> Figure 17-7: Moving a `Box` which points to a self-referential future type. -However, most types are perfectly safe to move around, even if they happen to -be behind a `Pin` pointer. We only need to think about pinning when items have -internal references. Primitive values such as numbers and booleans don’t have -any internal references, so they’re obviously safe. Neither do most types you -normally work with in Rust. A `Vec`, for example, doesn’t have any internal -references it needs to keep up to date this way, so you can move it around -without worrying. If you have a `Pin<Vec<String>>`, you’d have to do everything -via the safe but restrictive APIs provided by `Pin`, even though a -`Vec<String>` is always safe to move if there are no other references to it. We -need a way to tell the compiler that it’s actually just fine to move items -around in cases such as these. For that, we have `Unpin`. - -`Unpin` is a marker trait, similar to the `Send` and `Sync` traits we saw in the -“Extensible Concurrency with the `Sync` and `Send` Traits” section of Chapter -16 on page XX. Recall that marker traits have no functionality of their own. -They exist only to tell the compiler that it’s safe to use the type which -implements a given trait in a particular context. `Unpin` informs the compiler -that a given type does *not* need to uphold any particular guarantees about -whether the value in question can be moved. +However, most types are perfectly safe to move around, even if they happen to be +behind a `Pin` pointer. We only need to think about pinning when items have +internal references. Primitive values such as numbers and Booleans are safe +since they obviously don’t have any internal references, so they’re obviously +safe. Neither do most types you normally work with in Rust. You can move around +a `Vec`, for example, without worrying. Given only what we have seen so far, if +you have a `Pin<Vec<String>>`, you’d have to do everything via the safe but +restrictive APIs provided by `Pin`, even though a `Vec<String>` is always safe +to move if there are no other references to it. We need a way to tell the +compiler that it’s fine to move items around in cases like this—and there’s +where `Unpin` comes into play. + +`Unpin` is a marker trait, similar to the `Send` and `Sync` traits we saw in +Chapter 16, and thus has no functionality of its own. Marker traits exist only +to tell the compiler it’s safe to use the type implementing a given trait in a +particular context. `Unpin` informs the compiler that a given type does *not* +need to uphold any guarantees about whether the value in question can be safely +moved. + +<!-- + The inline `<code>` in the next block is to allow the inline `<em>` inside it, + matching what NoStarch does style-wise, and emphasizing within the text here + that it is something distinct from a normal type. +--> Just as with `Send` and `Sync`, the compiler implements `Unpin` automatically -for all types where it can prove it is safe. The special case, again similar to -`Send` and `Sync`, is the case where `Unpin` is *not* implemented for a type. -The notation for this is `impl !Unpin for SomeType`, where `SomeType` is the -name of a type which *does* need to uphold those guarantees to be safe whenever -a pointer to that type it is used in a `Pin`. +for all types where it can prove it is safe. A special case, again similar to +`Send` and `Sync`, is where `Unpin` is *not* implemented for a type. The +notation for this is <code>impl !Unpin for <em>SomeType</em></code>, where +<code><em>SomeType</em></code> is the name of a type that *does* need to uphold +those guarantees to be safe whenever a pointer to that type is used in a `Pin`. In other words, there are two things to keep in mind about the relationship between `Pin` and `Unpin`. First, `Unpin` is the “normal” case, and `!Unpin` is the special case. Second, whether a type implements `Unpin` or `!Unpin` *only* -matters when using a pinned pointer to that type like `Pin<&mut SomeType>`. +matters when you’re using a pinned pointer to that type like <code>Pin\<&mut +<em>SomeType</em>\></code>. To make that concrete, think about a `String`: it has a length and the Unicode -characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure -17-8. However, `String` automatically implements `Unpin`, the same as most other -types in Rust. +characters that make it up. We can wrap a `String` in `Pin`, as seen in Figure +17-8. However, `String` automatically implements `Unpin`, as do most other types +in Rust. -<img alt="Concurrent work flow" src="img/trpl17-08.svg" /> +<img alt="Concurrent work flow" src="img/trpl17-08.svg" class="center" /> -Figure 17-8: Pinning a String, with a dotted line indicating that the String -implements the `Unpin` trait, so it is not pinned. +Figure 17-8: Pinning a `String`; the dotted line indicates that the `String` implements the `Unpin` trait, and thus is not pinned. -As a result, we can do things which would be illegal if `String` implemented -`!Unpin` instead, such as replace one string with another at the exact same +As a result, we can do things that would be illegal if `String` implemented +`!Unpin` instead, such as replacing one string with another at the exact same location in memory as in Figure 17-9. This doesn’t violate the `Pin` contract, because `String` has no internal references that make it unsafe to move around! That is precisely why it implements `Unpin` rather than `!Unpin`. -<img alt="Concurrent work flow" src="img/trpl17-09.svg" /> +<img alt="Concurrent work flow" src="img/trpl17-09.svg" class="center" /> -Figure 17-9: Replacing the String with an entirely different String in memory. +Figure 17-9: Replacing the `String` with an entirely different `String` in memory. Now we know enough to understand the errors reported for that `join_all` call from back in Listing 17-17. We originally tried to move the futures produced by async blocks into a `Vec<Box<dyn Future<Output = ()>>>`, but as we’ve seen, -those futures may have internal references, so they don’t automatically -implement `Unpin`. Once we pin them, we can pass the resulting `Pin` type into -the `Vec`, confident that the underlying data in the futures will *not* be -moved. +those futures may have internal references, so they don’t implement `Unpin`. +They need to be pinned, and then we can pass the `Pin` type into the `Vec`, +confident that the underlying data in the futures will *not* be moved. `Pin` and `Unpin` are mostly important for building lower-level libraries, or -when you’re building a runtime itself, rather than for day to day Rust code. +when you’re building a runtime itself, rather than for day-to-day Rust code. When you see these traits in error messages, though, now you’ll have a better -idea of how to fix the code! - -> Note: This combination of `Pin` and `Unpin` allows a whole class of complex -> types to be safe in Rust which are otherwise difficult to implement because -> they’re self-referential. Types which require `Pin` show up *most* commonly -> in async Rust today, but you might—very rarely!—see it in other contexts, too. -> +idea of how to fix your code! + +> Note: This combination of `Pin` and `Unpin` makes it possible to safely +> implement a whole class of complex types in Rust that would otherwise prove +> challenging because they’re self-referential. Types that require `Pin` show up +> most commonly in async Rust today, but every once in a while, you might see +> them in other contexts, too. +> > The specifics of how `Pin` and `Unpin` work, and the rules they’re required > to uphold, are covered extensively in the API documentation for `std::pin`, so -> if you’d like to understand them more deeply, that’s a great place to start. -> -> If you want to understand how things work “under the hood” in even more -> detail, the official *Asynchronous Programming in Rust* book available at -> *https://rust-lang.github.io/async-book/* has you covered: -> -> * Chapter 2: Under the Hood: Executing Futures and Tasks -> * Chapter 4: Pinning +> if you’re interested in learning more, that’s a great place to start. +> +> If you want to understand how things work under the hood in even more detail, +> see Chapters 2 at *https://rust-lang.github.io/async-book/02_execution/01_chapter.html* and 4 at *https://rust-lang.github.io/async-book/04_pinning/01_chapter.html* of *Asynchronous +> Programming in Rust* at *https://rust-lang.github.io/async-book/*. ### The Stream Trait -Now that we have a deeper grasp on the `Future`, `Pin`, and `Unpin` traits, we -can turn our attention to the `Stream` trait. As described in the section -introducing streams, streams are similar to asynchronous iterators. Unlike -`Iterator` and `Future`, there is no definition of a `Stream` trait in the -standard library as of the time of writing, but there *is* a very common -definition from the `futures` crate used throughout the ecosystem. +Now that you have a deeper grasp on the `Future`, `Pin`, and `Unpin` traits, we +can turn our attention to the `Stream` trait. As you learned earlier in the +chapter, streams are similar to asynchronous iterators. Unlike `Iterator` and +`Future`, however, `Stream` has no definition in the standard library as of this +writing, but there *is* a very common definition from the `futures` crate used +throughout the ecosystem. -Let’s review the definitions of the `Iterator` and `Future` traits, so we can -build up to how a `Stream` trait that merges them together might look. From -`Iterator`, we have the idea of a sequence: its `next` method provides an -`Option<Self::Item>`. From `Future`, we have the idea of readiness over time: -its `poll` method provides a `Poll<Self::Output>`. To represent a sequence of -items which become ready over time, we define a `Stream` trait which puts those -features together: +Let’s review the definitions of the `Iterator` and `Future` traits before +looking at how a `Stream` trait might merge them together. From `Iterator`, we +have the idea of a sequence: its `next` method provides an `Option<Self::Item>`. +From `Future`, we have the idea of readiness over time: its `poll` method +provides a `Poll<Self::Output>`. To represent a sequence of items that become +ready over time, we define a `Stream` trait that puts those features together: ``` use std::pin::Pin; @@ -2783,10 +2944,10 @@ trait Stream { } ``` -The `Stream` trait defines an associated type `Item` for the type of the items -produced by the stream. This is similar to `Iterator`: there may be zero to -many of these, and unlike `Future`, where there is always a single `Output` -(even if it’s the unit type `()`). +The `Stream` trait defines an associated type called `Item` for the type of the +items produced by the stream. This is similar to `Iterator`, where there may be +zero to many items, and unlike `Future`, where there is always a single +`Output`, even if it’s the unit type `()`. `Stream` also defines a method to get those items. We call it `poll_next`, to make it clear that it polls in the same way `Future::poll` does and produces a @@ -2796,16 +2957,16 @@ checked for readiness, just as a future does. The inner type is `Option`, because it needs to signal whether there are more messages, just as an iterator does. -Something very similar to this will likely end up standardized as part of Rust’s -standard library. In the meantime, it’s part of the toolkit of most runtimes, -so you can rely on it, and everything we cover below should generally apply! +Something very similar to this definition will likely end up as part of Rust’s +standard library. In the meantime, it’s part of the toolkit of most runtimes, so +you can rely on it, and everything we cover next should generally apply! In the example we saw in the section on streaming, though, we didn’t use `poll_next` *or* `Stream`, but instead used `next` and `StreamExt`. We *could* work directly in terms of the `poll_next` API by hand-writing our own `Stream` state machines, of course, just as we *could* work with futures directly via -their `poll` method. Using `await` is much nicer, though, so the `StreamExt` -trait supplies the `next` method so we can do just that. +their `poll` method. Using `await` is much nicer, though, and the `StreamExt` +trait supplies the `next` method so we can do just that: ``` trait StreamExt: Stream { @@ -2817,68 +2978,72 @@ trait StreamExt: Stream { } ``` +<!-- +TODO: update this if/when tokio/etc. update their MSRV and switch to using async functions +in traits, since the lack thereof is the reason they do not yet have this. +--> + > Note: The actual definition we used earlier in the chapter looks slightly -> different than this, because it supports versions of Rust which did not yet +> different than this, because it supports versions of Rust that did not yet > support using async functions in traits. As a result, it looks like this: -> -> ``` +> +> ````rust,ignore > fn next(&mut self) -> Next<'_, Self> where Self: Unpin; -> ``` -> -> That `Next` type is a `struct` which implements `Future` and gives a way to -> name the lifetime of the reference to `self` with `Next<'_, Self>`, so that -> `await` can work with this method! +> ```` +> +> That `Next` type is a `struct` that implements `Future` and allows us to name +> the lifetime of the reference to `self` with `Next<'_, Self>`, so that `await` +> can work with this method. The `StreamExt` trait is also the home of all the interesting methods available to use with streams. `StreamExt` is automatically implemented for every type -which implements `Stream`, but these traits are defined separately so that the -community can iterate on the foundational trait distinctly from the convenience -APIs. +that implements `Stream`, but these traits are defined separately to enable the +community to iterate on convenience APIs without affecting the foundational +trait. In the version of `StreamExt` used in the `trpl` crate, the trait not only -defines the `next` method, it also supplies an implementation of `next`, which -correctly handles the details of calling `Stream::poll_next`. This means that -even when you need to write your own streaming data type, you *only* have to -implement `Stream`, and then anyone who uses your data type can use `StreamExt` -and its methods with it automatically. +defines the `next` method but also supplies a default implementation of `next` +that correctly handles the details of calling `Stream::poll_next`. This means +that even when you need to write your own streaming data type, you *only* have +to implement `Stream`, and then anyone who uses your data type can use +`StreamExt` and its methods with it automatically. That’s all we’re going to cover for the lower-level details on these traits. To wrap up, let’s consider how futures (including streams), tasks, and threads all fit together! -## Futures, Tasks, and Threads +## Putting It All Together: Futures, Tasks, and Threads -As we saw in the “Using Threads to Run Code Simultaneously” section of Chapter -16 on page XX, threads provide one approach to concurrency. We’ve seen another -approach to concurrency in this chapter, using async with futures and streams. -You might be wondering why you would choose one or the other. The answer is: it -depends! And in many cases, the choice isn’t threads *or* async but rather -threads *and* async. +As we saw in Chapter 16, threads provide one approach to +concurrency. We’ve seen another approach in this chapter: using async with +futures and streams. If you‘re wondering when to choose method over the other, +the answer is: it depends! And in many cases, the choice isn’t threads *or* +async but rather threads *and* async. Many operating systems have supplied threading-based concurrency models for -decades now, and many programming languages have support for them as a result. -However, they are not without their tradeoffs. On many operating systems, they +decades now, and many programming languages support them as a result. However, +these models are not without their tradeoffs. On many operating systems, they use a fair bit of memory for each thread, and they come with some overhead for starting up and shutting down. Threads are also only an option when your -operating system and hardware support them! Unlike mainstream desktop and mobile +operating system and hardware support them. Unlike mainstream desktop and mobile computers, some embedded systems don’t have an OS at all, so they also don’t -have threads! +have threads. The async model provides a different—and ultimately complementary—set of tradeoffs. In the async model, concurrent operations don’t require their own threads. Instead, they can run on tasks, as when we used `trpl::spawn_task` to -kick off work from a synchronous function throughout the streams section. A task -is similar to a thread, but instead of being managed by the operating system, -it’s managed by library-level code: the runtime. +kick off work from a synchronous function in the streams section. A task is +similar to a thread, but instead of being managed by the operating system, it’s +managed by library-level code: the runtime. -In the previous section, we saw that we could build a `Stream` by using an async -channel and spawning an async task which we could call from synchronous code. We -could do the exact same thing with a thread! In Listing 17-40, we used +In the previous section, we saw that we could build a stream by using an async +channel and spawning an async task we could call from synchronous code. We can +do the exact same thing with a thread. In Listing 17-40, we used `trpl::spawn_task` and `trpl::sleep`. In Listing 17-41, we replace those with the `thread::spawn` and `thread::sleep` APIs from the standard library in the `get_intervals` function. -Filename: src/main.rs +src/main.rs ``` fn get_intervals() -> impl Stream<Item = u32> { @@ -2903,18 +3068,18 @@ fn get_intervals() -> impl Stream<Item = u32> { } ``` -Listing 17-41: Using the `std::thread` APIs instead of the async `trpl` APIs -for the `get_intervals` function +Listing 17-41: Using the `std::thread` APIs instead of the async `trpl` APIs for the `get_intervals` function -If you run this, the output is identical. And notice how little changes here -from the perspective of the calling code! What’s more, even though one of our -functions spawned an async task on the runtime and the other spawned an -OS thread, the resulting streams were unaffected by the differences. +If you run this code, the output is identical to that of Listing 17-40. And +notice how little changes here from the perspective of the calling code. What’s +more, even though one of our functions spawned an async task on the runtime and +the other spawned an OS thread, the resulting streams were unaffected by the +differences. -Despite the similarities, these two approaches behave very differently, although -we might have a hard time measuring it in this very simple example. We could -spawn millions of async tasks on any modern personal computer. If we tried to do -that with threads, we would literally run out of memory! +Despite their similarities, these two approaches behave very differently, +although we might have a hard time measuring it in this very simple example. We +could spawn millions of async tasks on any modern personal computer. If we tried +to do that with threads, we would literally run out of memory! However, there’s a reason these APIs are so similar. Threads act as a boundary for sets of synchronous operations; concurrency is possible *between* threads. @@ -2927,51 +3092,47 @@ that regard, tasks are similar to lightweight, runtime-managed threads with added capabilities that come from being managed by a runtime instead of by the operating system. -This doesn’t mean that async tasks are always better than threads, any more than -that threads are always better than tasks. - -Concurrency with threads is in some ways a simpler programming model than -concurrency with `async`. That can be a strength or a weakness. Threads are -somewhat “fire and forget,” they have no native equivalent to a future, so they -simply run to completion, without interruption except by the operating system -itself. That is, they have no built-in support for *intra-task concurrency* the -way futures do. Threads in Rust also have no mechanisms for cancellation—a -subject we haven’t covered in depth in this chapter, but which is implicit in -the fact that whenever we ended a future, its state got cleaned up correctly. +This doesn’t mean that async tasks are always better than threads (or vice +versa). Concurrency with threads is in some ways a simpler programming model +than concurrency with `async`. That can be a strength or a weakness. Threads are +somewhat “fire and forget”; they have no native equivalent to a future, so they +simply run to completion without being interrupted except by the operating +system itself. That is, they have no built-in support for *intratask +concurrency* the way futures do. Threads in Rust also have no mechanisms for +cancellation—a subject we haven’t covered explicitly in this chapter but was +implied by the fact that whenever we ended a future, its state got cleaned up +correctly. These limitations also make threads harder to compose than futures. It’s much more difficult, for example, to use threads to build helpers such as the -`timeout` we built in the “Building Our Own Async Abstractions” section of this -chapter on page XX or the `throttle` method we used with streams in the -“Composing Streams” section of this chapter on page XX. The fact that futures -are richer data structures means they can be composed together more naturally, -as we have seen. - -Tasks then give *additional* control over futures, allowing you to choose where -and how to group the futures. And it turns out that threads and tasks often -work very well together, because tasks can (at least in some runtimes) be moved -around between threads. We haven’t mentioned it up until now, but under the -hood the `Runtime` we have been using, including the `spawn_blocking` and -`spawn_task` functions, is multithreaded by default! Many runtimes use an -approach called *work stealing* to transparently move tasks around between -threads based on the current utilization of the threads, with the aim of -improving the overall performance of the system. To build that actually requires -threads *and* tasks, and therefore futures. - -As a default way of thinking about which to use when: +`timeout` and `throttle` methods we built earlier in this chapter. The fact that +futures are richer data structures means they can be composed together more +naturally, as we have seen. + +Tasks, then, give us *additional* control over futures, allowing us to choose +where and how to group them. And it turns out that threads and tasks often work +very well together, because tasks can (at least in some runtimes) be moved +around between threads. In fact, under the hood, the runtime we’ve been +using—including the `spawn_blocking` and `spawn_task` functions—is multithreaded +by default! Many runtimes use an approach called *work stealing* to +transparently move tasks around between threads, based on how the threads are +currently being utilized, to improve the system’s overall performance. That +approach actually requires threads *and* tasks, and therefore futures. + +When thinking about which method to use when, consider these rules of thumb: * If the work is *very parallelizable*, such as processing a bunch of data where each part can be processed separately, threads are a better choice. * If the work is *very concurrent*, such as handling messages from a bunch of - different sources which may come in a different intervals or different rates, + different sources that may come in at different intervals or different rates, async is a better choice. -And if you need some mix of parallelism and concurrency, you don’t have to -choose between threads and async. You can use them together freely, letting each -one serve the part it is best at. For example, Listing 17-42 shows a fairly -common example of this kind of mix in real-world Rust code. +And if you need both parallelism and concurrency, you don’t have to choose +between threads and async. You can use them together freely, letting each one +play the part it’s best at. For example, Listing 17-42 shows a fairly common +example of this kind of mix in real-world Rust code. -Filename: src/main.rs +src/main.rs ``` use std::{thread, time::Duration}; @@ -2994,30 +3155,28 @@ fn main() { } ``` -Listing 17-42: Sending messages with blocking code in a thread and awaiting the -messages in an async block +Listing 17-42: Sending messages with blocking code in a thread and awaiting the messages in an async block -We begin by creating an async channel. Then we spawn a thread which takes +We begin by creating an async channel, then spawn a thread that takes ownership of the sender side of the channel. Within the thread, we send the -numbers 1 through 10, and sleep for a second in between each. Finally, we run a +numbers 1 through 10, sleeping for a second between each. Finally, we run a future created with an async block passed to `trpl::run` just as we have throughout the chapter. In that future, we await those messages, just as in the other message-passing examples we have seen. -To return to the examples we opened the chapter with: you could imagine running -a set of video encoding tasks using a dedicated thread, because video encoding -is compute bound, but notifying the UI that those operations are done with an -async channel. Examples of this kind of mix abound! +To return to the scenario we opened the chapter with, imagine running a set of +video encoding tasks using a dedicated thread (because video encoding is +compute-bound) but notifying the UI that those operations are done with an async +channel. There are countless examples of these kinds of combinations in +real-world use cases. ## Summary -This isn’t the last you’ll see of concurrency in this book: the project in -Chapter 21 will use the concepts in this chapter in a more realistic situation -than the smaller examples discussed here—and compare more directly what it looks -like to solve these kinds of problems with threading vs. with tasks and futures. +This isn’t the last you’ll see of concurrency in this book. The project in +Chapter 21 at *ch21-00-final-project-a-web-server.html* will apply these concepts in a more realistic situation +than the simpler examples discussed here and compare problem-solving with threading versus tasks more directly. -Whether with threads, with futures and tasks, or with the combination of them -all, Rust gives you the tools you need to write safe, fast, concurrent +No matter which of these approaches you choose, Rust gives you the tools you need to write safe, fast, concurrent code—whether for a high-throughput web server or an embedded operating system. Next, we’ll talk about idiomatic ways to model problems and structure solutions diff --git a/nostarch/chapter18.md b/nostarch/chapter18.md index 946d20a112..6332802221 100644 --- a/nostarch/chapter18.md +++ b/nostarch/chapter18.md @@ -6,16 +6,16 @@ directory, so all fixes need to be made in `/src/`. [TOC] -# Object-Oriented Programming Features +# Object-Oriented Programming Features of Rust Object-oriented programming (OOP) is a way of modeling programs. Objects as a programmatic concept were introduced in the programming language Simula in the 1960s. Those objects influenced Alan Kay’s programming architecture in which objects pass messages to each other. To describe this architecture, he coined the term *object-oriented programming* in 1967. Many competing definitions -describe what OOP is, and by some of these definitions Rust is object oriented +describe what OOP is, and by some of these definitions Rust is object-oriented, but by others it is not. In this chapter, we’ll explore certain characteristics -that are commonly considered object oriented and how those characteristics +that are commonly considered object-oriented and how those characteristics translate to idiomatic Rust. We’ll then show you how to implement an object-oriented design pattern in Rust and discuss the trade-offs of doing so versus implementing a solution using some of Rust’s strengths instead. @@ -23,7 +23,7 @@ versus implementing a solution using some of Rust’s strengths instead. ## Characteristics of Object-Oriented Languages There is no consensus in the programming community about what features a -language must have to be considered object oriented. Rust is influenced by many +language must have to be considered object-oriented. Rust is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages share certain common characteristics, namely objects, encapsulation, and @@ -33,20 +33,20 @@ Rust supports it. ### Objects Contain Data and Behavior The book *Design Patterns: Elements of Reusable Object-Oriented Software* by -Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, -1994), colloquially referred to as *The Gang of Four* book, is a catalog of -object-oriented design patterns. It defines OOP in this way: +Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley +Professional, 1994), colloquially referred to as *The Gang of Four* book, is a +catalog of object-oriented design patterns. It defines OOP this way: -Object-oriented programs are made up of objects. An *object* packages both data -and the procedures that operate on that data. The procedures are typically -called *methods* or *operations*. +> Object-oriented programs are made up of objects. An **object** packages both +> data and the procedures that operate on that data. The procedures are +> typically called **methods** or **operations**. -Using this definition, Rust is object oriented: structs and enums have data, +Using this definition, Rust is object-oriented: structs and enums have data, and `impl` blocks provide methods on structs and enums. Even though structs and enums with methods aren’t *called* objects, they provide the same functionality, according to the Gang of Four’s definition of objects. -### Encapsulation That Hides Implementation Details +### Encapsulation that Hides Implementation Details Another aspect commonly associated with OOP is the idea of *encapsulation*, which means that the implementation details of an object aren’t accessible to @@ -61,12 +61,12 @@ keyword to decide which modules, types, functions, and methods in our code should be public, and by default everything else is private. For example, we can define a struct `AveragedCollection` that has a field containing a vector of `i32` values. The struct can also have a field that contains the average of -the values in the vector, meaning the average doesn’t have to be computed on -demand whenever anyone needs it. In other words, `AveragedCollection` will -cache the calculated average for us. Listing 17-1 has the definition of the -`AveragedCollection` struct. +the values in the vector, meaning the average doesn’t have to be computed +on demand whenever anyone needs it. In other words, `AveragedCollection` will +cache the calculated average for us. Listing 18-1 has the definition of the +`AveragedCollection` struct: -Filename: src/lib.rs +src/lib.rs ``` pub struct AveragedCollection { @@ -75,16 +75,15 @@ pub struct AveragedCollection { } ``` -Listing 17-1: An `AveragedCollection` struct that maintains a list of integers -and the average of the items in the collection +Listing 18-1: An `AveragedCollection` struct that maintains a list of integers and the average of the items in the collection The struct is marked `pub` so that other code can use it, but the fields within the struct remain private. This is important in this case because we want to ensure that whenever a value is added or removed from the list, the average is also updated. We do this by implementing `add`, `remove`, and `average` methods -on the struct, as shown in Listing 17-2. +on the struct, as shown in Listing 18-2: -Filename: src/lib.rs +src/lib.rs ``` impl AveragedCollection { @@ -115,12 +114,11 @@ impl AveragedCollection { } ``` -Listing 17-2: Implementations of the public methods `add`, `remove`, and -`average` on `AveragedCollection` +Listing 18-2: Implementations of the public methods `add`, `remove`, and `average` on `AveragedCollection` The public methods `add`, `remove`, and `average` are the only ways to access -or modify data in an instance of `AveragedCollection`. When an item is added to -`list` using the `add` method or removed using the `remove` method, the +or modify data in an instance of `AveragedCollection`. When an item is added +to `list` using the `add` method or removed using the `remove` method, the implementations of each call the private `update_average` method that handles updating the `average` field as well. @@ -134,15 +132,15 @@ Because we’ve encapsulated the implementation details of the struct `AveragedCollection`, we can easily change aspects, such as the data structure, in the future. For instance, we could use a `HashSet<i32>` instead of a `Vec<i32>` for the `list` field. As long as the signatures of the `add`, -`remove`, and `average` public methods stayed the same, code using -`AveragedCollection` wouldn’t need to change. If we made `list` public instead, -this wouldn’t necessarily be the case: `HashSet<i32>` and `Vec<i32>` have -different methods for adding and removing items, so the external code would -likely have to change if it were modifying `list` directly. +`remove`, and `average` public methods stay the same, code using +`AveragedCollection` wouldn’t need to change in order to compile. If we made +`list` public instead, this wouldn’t necessarily be the case: `HashSet<i32>` and +`Vec<i32>` have different methods for adding and removing items, so the external +code would likely have to change if it were modifying `list` directly. -If encapsulation is a required aspect for a language to be considered object -oriented, then Rust meets that requirement. The option to use `pub` or not for -different parts of code enables encapsulation of implementation details. +If encapsulation is a required aspect for a language to be considered +object-oriented, then Rust meets that requirement. The option to use `pub` or +not for different parts of code enables encapsulation of implementation details. ### Inheritance as a Type System and as Code Sharing @@ -150,8 +148,8 @@ different parts of code enables encapsulation of implementation details. another object’s definition, thus gaining the parent object’s data and behavior without you having to define them again. -If a language must have inheritance to be object oriented, then Rust is not -such a language. There is no way to define a struct that inherits the parent +If a language must have inheritance to be an object-oriented language, then +Rust is not one. There is no way to define a struct that inherits the parent struct’s fields and method implementations without using a macro. However, if you’re used to having inheritance in your programming toolbox, you @@ -177,14 +175,14 @@ called *polymorphism*, which means that you can substitute multiple objects for each other at runtime if they share certain characteristics. > ### Polymorphism -> +> > To many people, polymorphism is synonymous with inheritance. But it’s -actually a more general concept that refers to code that can work with data of -multiple types. For inheritance, those types are generally subclasses. -> +> actually a more general concept that refers to code that can work with data +> of multiple types. For inheritance, those types are generally subclasses. +> > Rust instead uses generics to abstract over different possible types and -trait bounds to impose constraints on what those types must provide. This is -sometimes called *bounded parametric polymorphism*. +> trait bounds to impose constraints on what those types must provide. This is +> sometimes called *bounded parametric polymorphism*. Inheritance has recently fallen out of favor as a programming design solution in many programming languages because it’s often at risk of sharing more code @@ -192,8 +190,8 @@ than necessary. Subclasses shouldn’t always share all characteristics of their parent class but will do so with inheritance. This can make a program’s design less flexible. It also introduces the possibility of calling methods on subclasses that don’t make sense or that cause errors because the methods don’t -apply to the subclass. In addition, some languages will only allow single -inheritance (meaning a subclass can only inherit from one class), further +apply to the subclass. In addition, some languages will only allow *single +inheritance* (meaning a subclass can only inherit from one class), further restricting the flexibility of a program’s design. For these reasons, Rust takes the different approach of using trait objects @@ -221,7 +219,7 @@ some types for people to use, such as `Button` or `TextField`. In addition, instance, one programmer might add an `Image` and another might add a `SelectBox`. -We won’t implement a full-fledged GUI library for this example but will show +We won’t implement a fully fledged GUI library for this example but will show how the pieces would fit together. At the time of writing the library, we can’t know and define all the types other programmers might want to create. But we do know that `gui` needs to keep track of many values of different types, and it @@ -242,17 +240,17 @@ allow users to extend it with new types. To implement the behavior we want `gui` to have, we’ll define a trait named `Draw` that will have one method named `draw`. Then we can define a vector that -takes a *trait object*. A trait object points to both an instance of a type +takes a trait object. A *trait object* points to both an instance of a type implementing our specified trait and a table used to look up trait methods on that type at runtime. We create a trait object by specifying some sort of -pointer, such as a `&` reference or a `Box<T>` smart pointer, then the `dyn` +pointer, such as an `&` reference or a `Box<T>` smart pointer, then the `dyn` keyword, and then specifying the relevant trait. (We’ll talk about the reason -trait objects must use a pointer in “Dynamically Sized Types and the Sized -Trait” on page XX.) We can use trait objects in place of a generic or concrete -type. Wherever we use a trait object, Rust’s type system will ensure at compile -time that any value used in that context will implement the trait object’s -trait. Consequently, we don’t need to know all the possible types at compile -time. +trait objects must use a pointer in “Dynamically Sized Types and the `Sized` +Trait” in Chapter 20.) We can use trait +objects in place of a generic or concrete type. Wherever we use a trait object, +Rust’s type system will ensure at compile time that any value used in that +context will implement the trait object’s trait. Consequently, we don’t need to +know all the possible types at compile time. We’ve mentioned that, in Rust, we refrain from calling structs and enums “objects” to distinguish them from other languages’ objects. In a struct or @@ -265,10 +263,10 @@ a trait object. Trait objects aren’t as generally useful as objects in other languages: their specific purpose is to allow abstraction across common behavior. -Listing 17-3 shows how to define a trait named `Draw` with one method named -`draw`. +Listing 18-3 shows how to define a trait named `Draw` with one method named +`draw`: -Filename: src/lib.rs +src/lib.rs ``` pub trait Draw { @@ -276,15 +274,15 @@ pub trait Draw { } ``` -Listing 17-3: Definition of the `Draw` trait +Listing 18-3: Definition of the `Draw` trait This syntax should look familiar from our discussions on how to define traits -in Chapter 10. Next comes some new syntax: Listing 17-4 defines a struct named +in Chapter 10. Next comes some new syntax: Listing 18-4 defines a struct named `Screen` that holds a vector named `components`. This vector is of type -`Box<dyn Draw>`, which is a trait object; it’s a stand-in for any type inside a -`Box` that implements the `Draw` trait. +`Box<dyn Draw>`, which is a trait object; it’s a stand-in for any type inside +a `Box` that implements the `Draw` trait. -Filename: src/lib.rs +src/lib.rs ``` pub struct Screen { @@ -292,13 +290,12 @@ pub struct Screen { } ``` -Listing 17-4: Definition of the `Screen` struct with a `components` field -holding a vector of trait objects that implement the `Draw` trait +Listing 18-4: Definition of the `Screen` struct with a `components` field holding a vector of trait objects that implement the `Draw` trait On the `Screen` struct, we’ll define a method named `run` that will call the -`draw` method on each of its `components`, as shown in Listing 17-5. +`draw` method on each of its `components`, as shown in Listing 18-5: -Filename: src/lib.rs +src/lib.rs ``` impl Screen { @@ -310,17 +307,16 @@ impl Screen { } ``` -Listing 17-5: A `run` method on `Screen` that calls the `draw` method on each -component +Listing 18-5: A `run` method on `Screen` that calls the `draw` method on each component This works differently from defining a struct that uses a generic type -parameter with trait bounds. A generic type parameter can only be substituted -with one concrete type at a time, whereas trait objects allow for multiple +parameter with trait bounds. A generic type parameter can be substituted with +only one concrete type at a time, whereas trait objects allow for multiple concrete types to fill in for the trait object at runtime. For example, we -could have defined the `Screen` struct using a generic type and a trait bound, -as in Listing 17-6. +could have defined the `Screen` struct using a generic type and a trait bound +as in Listing 18-6: -Filename: src/lib.rs +src/lib.rs ``` pub struct Screen<T: Draw> { @@ -339,8 +335,7 @@ where } ``` -Listing 17-6: An alternate implementation of the `Screen` struct and its `run` -method using generics and trait bounds +Listing 18-6: An alternate implementation of the `Screen` struct and its `run` method using generics and trait bounds This restricts us to a `Screen` instance that has a list of components all of type `Button` or all of type `TextField`. If you’ll only ever have homogeneous @@ -358,9 +353,9 @@ Now we’ll add some types that implement the `Draw` trait. We’ll provide the `Button` type. Again, actually implementing a GUI library is beyond the scope of this book, so the `draw` method won’t have any useful implementation in its body. To imagine what the implementation might look like, a `Button` struct -might have fields for `width`, `height`, and `label`, as shown in Listing 17-7. +might have fields for `width`, `height`, and `label`, as shown in Listing 18-7: -Filename: src/lib.rs +src/lib.rs ``` pub struct Button { @@ -376,7 +371,7 @@ impl Draw for Button { } ``` -Listing 17-7: A `Button` struct that implements the `Draw` trait +Listing 18-7: A `Button` struct that implements the `Draw` trait The `width`, `height`, and `label` fields on `Button` will differ from the fields on other components; for example, a `TextField` type might have those @@ -389,10 +384,10 @@ happens when a user clicks the button. These kinds of methods won’t apply to types like `TextField`. If someone using our library decides to implement a `SelectBox` struct that has -`width`, `height`, and `options` fields, they would implement the `Draw` trait -on the `SelectBox` type as well, as shown in Listing 17-8. +`width`, `height`, and `options` fields, they implement the `Draw` trait on the +`SelectBox` type as well, as shown in Listing 18-8: -Filename: src/main.rs +src/main.rs ``` use gui::Draw; @@ -410,16 +405,15 @@ impl Draw for SelectBox { } ``` -Listing 17-8: Another crate using `gui` and implementing the `Draw` trait on a -`SelectBox` struct +Listing 18-8: Another crate using `gui` and implementing the `Draw` trait on a `SelectBox` struct Our library’s user can now write their `main` function to create a `Screen` instance. To the `Screen` instance, they can add a `SelectBox` and a `Button` by putting each in a `Box<T>` to become a trait object. They can then call the `run` method on the `Screen` instance, which will call `draw` on each of the -components. Listing 17-9 shows this implementation. +components. Listing 18-9 shows this implementation: -Filename: src/main.rs +src/main.rs ``` use gui::{Button, Screen}; @@ -448,8 +442,7 @@ fn main() { } ``` -Listing 17-9: Using trait objects to store values of different types that -implement the same trait +Listing 18-9: Using trait objects to store values of different types that implement the same trait When we wrote the library, we didn’t know that someone might add the `SelectBox` type, but our `Screen` implementation was able to operate on the @@ -458,9 +451,9 @@ means it implements the `draw` method. This concept—of being concerned only with the messages a value responds to rather than the value’s concrete type—is similar to the concept of *duck -typing* in dynamically typed languages: if it walks like a duck and quacks like -a duck, then it must be a duck! In the implementation of `run` on `Screen` in -Listing 17-5, `run` doesn’t need to know what the concrete type of each +typing* in dynamically typed languages: if it walks like a duck and quacks +like a duck, then it must be a duck! In the implementation of `run` on `Screen` +in Listing 18-5, `run` doesn’t need to know what the concrete type of each component is. It doesn’t check whether a component is an instance of a `Button` or a `SelectBox`, it just calls the `draw` method on the component. By specifying `Box<dyn Draw>` as the type of the values in the `components` @@ -473,10 +466,10 @@ value implements a particular method at runtime or worry about getting errors if a value doesn’t implement a method but we call it anyway. Rust won’t compile our code if the values don’t implement the traits that the trait objects need. -For example, Listing 17-10 shows what happens if we try to create a `Screen` -with a `String` as a component. +For example, Listing 18-10 shows what happens if we try to create a `Screen` +with a `String` as a component: -Filename: src/main.rs +src/main.rs ``` use gui::Screen; @@ -490,38 +483,42 @@ fn main() { } ``` -Listing 17-10: Attempting to use a type that doesn’t implement the trait -object’s trait +Listing 18-10: Attempting to use a type that doesn’t implement the trait object’s trait We’ll get this error because `String` doesn’t implement the `Draw` trait: ``` +$ cargo run + Compiling gui v0.1.0 (file:///projects/gui) error[E0277]: the trait bound `String: Draw` is not satisfied --> src/main.rs:5:26 | 5 | components: vec![Box::new(String::from("Hi"))], - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is -not implemented for `String` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String` | - = note: required for the cast to the object type `dyn Draw` + = help: the trait `Draw` is implemented for `Button` + = note: required for the cast from `Box<String>` to `Box<dyn Draw>` + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `gui` (bin "gui") due to 1 previous error ``` -This error lets us know that either we’re passing something to `Screen` that we -didn’t mean to pass and so should pass a different type, or we should implement +This error lets us know that either we’re passing something to `Screen` we +didn’t mean to pass and so should pass a different type or we should implement `Draw` on `String` so that `Screen` is able to call `draw` on it. ### Trait Objects Perform Dynamic Dispatch -Recall in “Performance of Code Using Generics” on page XX our discussion on the -monomorphization process performed by the compiler when we use trait bounds on -generics: the compiler generates nongeneric implementations of functions and -methods for each concrete type that we use in place of a generic type -parameter. The code that results from monomorphization is doing *static -dispatch*, which is when the compiler knows what method you’re calling at -compile time. This is opposed to *dynamic dispatch*, which is when the compiler -can’t tell at compile time which method you’re calling. In dynamic dispatch -cases, the compiler emits code that at runtime will figure out which method to -call. +Recall in “Performance of Code Using +Generics” in Chapter 10 our +discussion on the monomorphization process performed on generics by the +compiler: the compiler generates nongeneric implementations of functions and +methods for each concrete type that we use in place of a generic type parameter. +The code that results from monomorphization is doing *static dispatch*, which is +when the compiler knows what method you’re calling at compile time. This is +opposed to *dynamic dispatch*, which is when the compiler can’t tell at compile +time which method you’re calling. In dynamic dispatch cases, the compiler emits +code that at runtime will figure out which method to call. When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’t know all the types that might be used with the code that’s using trait objects, @@ -529,9 +526,11 @@ so it doesn’t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. This lookup incurs a runtime cost that doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a -method’s code, which in turn prevents some optimizations. However, we did get -extra flexibility in the code that we wrote in Listing 17-5 and were able to -support in Listing 17-9, so it’s a trade-off to consider. +method’s code, which in turn prevents some optimizations, and Rust has some +rules about where you can and cannot use dynamic dispatch, called *dyn +compatibility* at *https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility*. However, we did get extra flexibility in the code +that we wrote in Listing 18-5 and were able to support in Listing 18-9, so it’s +a trade-off to consider. ## Implementing an Object-Oriented Design Pattern @@ -540,7 +539,7 @@ pattern is that we define a set of states a value can have internally. The states are represented by a set of *state objects*, and the value’s behavior changes based on its state. We’re going to work through an example of a blog post struct that has a field to hold its state, which will be a state object -from the set “draft,” “review,” or “published.” +from the set “draft”, “review”, or “published”. The state objects share functionality: in Rust, of course, we use structs and traits rather than objects and inheritance. Each state object is responsible @@ -554,9 +553,9 @@ value holding the state or the code that uses the value. We’ll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects. -First we’re going to implement the state pattern in a more traditional +First, we’re going to implement the state pattern in a more traditional object-oriented way, then we’ll use an approach that’s a bit more natural in -Rust. Let’s dig in to incrementally implement a blog post workflow using the +Rust. Let’s dig in to incrementally implementing a blog post workflow using the state pattern. The final functionality will look like this: @@ -565,66 +564,65 @@ The final functionality will look like this: 1. When the draft is done, a review of the post is requested. 1. When the post is approved, it gets published. 1. Only published blog posts return content to print, so unapproved posts can’t -accidentally be published. + accidentally be published. Any other changes attempted on a post should have no effect. For example, if we try to approve a draft blog post before we’ve requested a review, the post should remain an unpublished draft. -Listing 17-11 shows this workflow in code form: this is an example usage of the +Listing 18-11 shows this workflow in code form: this is an example usage of the API we’ll implement in a library crate named `blog`. This won’t compile yet because we haven’t implemented the `blog` crate. -Filename: src/main.rs +src/main.rs ``` use blog::Post; fn main() { - 1 let mut post = Post::new(); + let mut post = Post::new(); - 2 post.add_text("I ate a salad for lunch today"); - 3 assert_eq!("", post.content()); + post.add_text("I ate a salad for lunch today"); + assert_eq!("", post.content()); - 4 post.request_review(); - 5 assert_eq!("", post.content()); + post.request_review(); + assert_eq!("", post.content()); - 6 post.approve(); - 7 assert_eq!("I ate a salad for lunch today", post.content()); + post.approve(); + assert_eq!("I ate a salad for lunch today", post.content()); } ``` -Listing 17-11: Code that demonstrates the desired behavior we want our `blog` -crate to have +Listing 18-11: Code that demonstrates the desired behavior we want our `blog` crate to have -We want to allow the user to create a new draft blog post with `Post::new` [1]. -We want to allow text to be added to the blog post [2]. If we try to get the -post’s content immediately, before approval, we shouldn’t get any text because -the post is still a draft. We’ve added `assert_eq!` in the code for -demonstration purposes [3]. An excellent unit test for this would be to assert -that a draft blog post returns an empty string from the `content` method, but -we’re not going to write tests for this example. +We want to allow the user to create a new draft blog post with `Post::new`. We +want to allow text to be added to the blog post. If we try to get the post’s +content immediately, before approval, we shouldn’t get any text because the +post is still a draft. We’ve added `assert_eq!` in the code for demonstration +purposes. An excellent unit test for this would be to assert that a draft blog +post returns an empty string from the `content` method, but we’re not going to +write tests for this example. -Next, we want to enable a request for a review of the post [4], and we want -`content` to return an empty string while waiting for the review [5]. When the -post receives approval [6], it should get published, meaning the text of the -post will be returned when `content` is called [7]. +Next, we want to enable a request for a review of the post, and we want +`content` to return an empty string while waiting for the review. When the post +receives approval, it should get published, meaning the text of the post will +be returned when `content` is called. Notice that the only type we’re interacting with from the crate is the `Post` type. This type will use the state pattern and will hold a value that will be one of three state objects representing the various states a post can be -in—draft, review, or published. Changing from one state to another will be -managed internally within the `Post` type. The states change in response to the -methods called by our library’s users on the `Post` instance, but they don’t -have to manage the state changes directly. Also, users can’t make a mistake -with the states, such as publishing a post before it’s reviewed. +in—draft, waiting for review, or published. Changing from one state to another +will be managed internally within the `Post` type. The states change in +response to the methods called by our library’s users on the `Post` instance, +but they don’t have to manage the state changes directly. Also, users can’t +make a mistake with the states, like publishing a post before it’s reviewed. ### Defining Post and Creating a New Instance in the Draft State Let’s get started on the implementation of the library! We know we need a public `Post` struct that holds some content, so we’ll start with the definition of the struct and an associated public `new` function to create an -instance of `Post`, as shown in Listing 17-12. We’ll also make a private +instance of `Post`, as shown in Listing 18-12. We’ll also make a private `State` trait that will define the behavior that all state objects for a `Post` must have. @@ -632,7 +630,7 @@ Then `Post` will hold a trait object of `Box<dyn State>` inside an `Option<T>` in a private field named `state` to hold the state object. You’ll see why the `Option<T>` is necessary in a bit. -Filename: src/lib.rs +src/lib.rs ``` pub struct Post { @@ -643,8 +641,8 @@ pub struct Post { impl Post { pub fn new() -> Post { Post { - 1 state: Some(Box::new(Draft {})), - 2 content: String::new(), + state: Some(Box::new(Draft {})), + content: String::new(), } } } @@ -656,8 +654,7 @@ struct Draft {} impl State for Draft {} ``` -Listing 17-12: Definition of a `Post` struct and a `new` function that creates -a new `Post` instance, a `State` trait, and a `Draft` struct +Listing 18-12: Definition of a `Post` struct and a `new` function that creates a new `Post` instance, a `State` trait, and a `Draft` struct The `State` trait defines the behavior shared by different post states. The state objects are `Draft`, `PendingReview`, and `Published`, and they will all @@ -666,37 +663,35 @@ we’ll start by defining just the `Draft` state because that is the state we want a post to start in. When we create a new `Post`, we set its `state` field to a `Some` value that -holds a `Box` [1]. This `Box` points to a new instance of the `Draft` struct. -This ensures that whenever we create a new instance of `Post`, it will start -out as a draft. Because the `state` field of `Post` is private, there is no way -to create a `Post` in any other state! In the `Post::new` function, we set the -`content` field to a new, empty `String` [2]. +holds a `Box`. This `Box` points to a new instance of the `Draft` struct. +This ensures whenever we create a new instance of `Post`, it will start out as +a draft. Because the `state` field of `Post` is private, there is no way to +create a `Post` in any other state! In the `Post::new` function, we set the +`content` field to a new, empty `String`. ### Storing the Text of the Post Content -We saw in Listing 17-11 that we want to be able to call a method named +We saw in Listing 18-11 that we want to be able to call a method named `add_text` and pass it a `&str` that is then added as the text content of the blog post. We implement this as a method, rather than exposing the `content` field as `pub`, so that later we can implement a method that will control how the `content` field’s data is read. The `add_text` method is pretty -straightforward, so let’s add the implementation in Listing 17-13 to the `impl -Post` block. +straightforward, so let’s add the implementation in Listing 18-13 to the `impl Post` block: -Filename: src/lib.rs +src/lib.rs ``` impl Post { - --snip-- + // --snip-- pub fn add_text(&mut self, text: &str) { self.content.push_str(text); } } ``` -Listing 17-13: Implementing the `add_text` method to add text to a post’s -`content` +Listing 18-13: Implementing the `add_text` method to add text to a post’s `content` -The `add_text` method takes a mutable reference to `self` because we’re +The `add_text` method takes a mutable reference to `self`, because we’re changing the `Post` instance that we’re calling `add_text` on. We then call `push_str` on the `String` in `content` and pass the `text` argument to add to the saved `content`. This behavior doesn’t depend on the state the post is in, @@ -708,56 +703,55 @@ support. Even after we’ve called `add_text` and added some content to our post, we still want the `content` method to return an empty string slice because the post is -still in the draft state, as shown at [3] in Listing 17-11. For now, let’s +still in the draft state, as shown on line 7 of Listing 18-11. For now, let’s implement the `content` method with the simplest thing that will fulfill this requirement: always returning an empty string slice. We’ll change this later once we implement the ability to change a post’s state so it can be published. So far, posts can only be in the draft state, so the post content should always -be empty. Listing 17-14 shows this placeholder implementation. +be empty. Listing 18-14 shows this placeholder implementation: -Filename: src/lib.rs +src/lib.rs ``` impl Post { - --snip-- + // --snip-- pub fn content(&self) -> &str { "" } } ``` -Listing 17-14: Adding a placeholder implementation for the `content` method on -`Post` that always returns an empty string slice +Listing 18-14: Adding a placeholder implementation for the `content` method on `Post` that always returns an empty string slice -With this added `content` method, everything in Listing 17-11 up to the line at -[3] works as intended. +With this added `content` method, everything in Listing 18-11 up to line 7 +works as intended. -### Requesting a Review Changes the Post’s State +### Requesting a Review of the Post Changes Its State Next, we need to add functionality to request a review of a post, which should -change its state from `Draft` to `PendingReview`. Listing 17-15 shows this code. +change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code: -Filename: src/lib.rs +src/lib.rs ``` impl Post { - --snip-- - 1 pub fn request_review(&mut self) { - 2 if let Some(s) = self.state.take() { - 3 self.state = Some(s.request_review()) + // --snip-- + pub fn request_review(&mut self) { + if let Some(s) = self.state.take() { + self.state = Some(s.request_review()) } } } trait State { - 4 fn request_review(self: Box<Self>) -> Box<dyn State>; + fn request_review(self: Box<Self>) -> Box<dyn State>; } struct Draft {} impl State for Draft { fn request_review(self: Box<Self>) -> Box<dyn State> { - 5 Box::new(PendingReview {}) + Box::new(PendingReview {}) } } @@ -765,20 +759,19 @@ struct PendingReview {} impl State for PendingReview { fn request_review(self: Box<Self>) -> Box<dyn State> { - 6 self + self } } ``` -Listing 17-15: Implementing `request_review` methods on `Post` and the `State` -trait +Listing 18-15: Implementing `request_review` methods on `Post` and the `State` trait We give `Post` a public method named `request_review` that will take a mutable -reference to `self` [1]. Then we call an internal `request_review` method on -the current state of `Post` [3], and this second `request_review` method -consumes the current state and returns a new state. +reference to `self`. Then we call an internal `request_review` method on the +current state of `Post`, and this second `request_review` method consumes the +current state and returns a new state. -We add the `request_review` method to the `State` trait [4]; all types that +We add the `request_review` method to the `State` trait; all types that implement the trait will now need to implement the `request_review` method. Note that rather than having `self`, `&self`, or `&mut self` as the first parameter of the method, we have `self: Box<Self>`. This syntax means the @@ -789,8 +782,8 @@ ownership of `Box<Self>`, invalidating the old state so the state value of the To consume the old state, the `request_review` method needs to take ownership of the state value. This is where the `Option` in the `state` field of `Post` comes in: we call the `take` method to take the `Some` value out of the `state` -field and leave a `None` in its place because Rust doesn’t let us have -unpopulated fields in structs [2]. This lets us move the `state` value out of +field and leave a `None` in its place, because Rust doesn’t let us have +unpopulated fields in structs. This lets us move the `state` value out of `Post` rather than borrowing it. Then we’ll set the post’s `state` value to the result of this operation. @@ -800,11 +793,11 @@ the `state` value. This ensures `Post` can’t use the old `state` value after we’ve transformed it into a new state. The `request_review` method on `Draft` returns a new, boxed instance of a new -`PendingReview` struct [5], which represents the state when a post is waiting -for a review. The `PendingReview` struct also implements the `request_review` -method but doesn’t do any transformations. Rather, it returns itself [6] -because when we request a review on a post already in the `PendingReview` -state, it should stay in the `PendingReview` state. +`PendingReview` struct, which represents the state when a post is waiting for a +review. The `PendingReview` struct also implements the `request_review` method +but doesn’t do any transformations. Rather, it returns itself, because when we +request a review on a post already in the `PendingReview` state, it should stay +in the `PendingReview` state. Now we can start seeing the advantages of the state pattern: the `request_review` method on `Post` is the same no matter its `state` value. Each @@ -813,19 +806,23 @@ state is responsible for its own rules. We’ll leave the `content` method on `Post` as is, returning an empty string slice. We can now have a `Post` in the `PendingReview` state as well as in the `Draft` state, but we want the same behavior in the `PendingReview` state. -Listing 17-11 now works up to the line at [5]! +Listing 18-11 now works up to line 10! + +<!-- Old headings. Do not remove or links may break. --> + +<a id="adding-the-approve-method-that-changes-the-behavior-of-content"></a> ### Adding approve to Change the Behavior of content The `approve` method will be similar to the `request_review` method: it will set `state` to the value that the current state says it should have when that -state is approved, as shown in Listing 17-16. +state is approved, as shown in Listing 18-16: -Filename: src/lib.rs +src/lib.rs ``` impl Post { - --snip-- + // --snip-- pub fn approve(&mut self) { if let Some(s) = self.state.take() { self.state = Some(s.approve()) @@ -841,18 +838,18 @@ trait State { struct Draft {} impl State for Draft { - --snip-- + // --snip-- fn approve(self: Box<Self>) -> Box<dyn State> { - 1 self + self } } struct PendingReview {} impl State for PendingReview { - --snip-- + // --snip-- fn approve(self: Box<Self>) -> Box<dyn State> { - 2 Box::new(Published {}) + Box::new(Published {}) } } @@ -869,54 +866,53 @@ impl State for Published { } ``` -Listing 17-16: Implementing the `approve` method on `Post` and the `State` trait +Listing 18-16: Implementing the `approve` method on `Post` and the `State` trait We add the `approve` method to the `State` trait and add a new struct that implements `State`, the `Published` state. Similar to the way `request_review` on `PendingReview` works, if we call the `approve` method on a `Draft`, it will have no effect because `approve` will -return `self` [1]. When we call `approve` on `PendingReview`, it returns a new, -boxed instance of the `Published` struct [2]. The `Published` struct implements -the `State` trait, and for both the `request_review` method and the `approve` -method, it returns itself because the post should stay in the `Published` state -in those cases. +return `self`. When we call `approve` on `PendingReview`, it returns a new, +boxed instance of the `Published` struct. The `Published` struct implements the +`State` trait, and for both the `request_review` method and the `approve` +method, it returns itself, because the post should stay in the `Published` +state in those cases. Now we need to update the `content` method on `Post`. We want the value returned from `content` to depend on the current state of the `Post`, so we’re going to have the `Post` delegate to a `content` method defined on its `state`, -as shown in Listing 17-17. +as shown in Listing 18-17: -Filename: src/lib.rs +src/lib.rs ``` impl Post { - --snip-- + // --snip-- pub fn content(&self) -> &str { self.state.as_ref().unwrap().content(self) } - --snip-- + // --snip-- } ``` -Listing 17-17: Updating the `content` method on `Post` to delegate to a -`content` method on `State` +Listing 18-17: Updating the `content` method on `Post` to delegate to a `content` method on `State` -Because the goal is to keep all of these rules inside the structs that -implement `State`, we call a `content` method on the value in `state` and pass -the post instance (that is, `self`) as an argument. Then we return the value -that’s returned from using the `content` method on the `state` value. +Because the goal is to keep all these rules inside the structs that implement +`State`, we call a `content` method on the value in `state` and pass the post +instance (that is, `self`) as an argument. Then we return the value that’s +returned from using the `content` method on the `state` value. We call the `as_ref` method on the `Option` because we want a reference to the value inside the `Option` rather than ownership of the value. Because `state` -is an `Option<Box<dyn State>>`, when we call `as_ref`, an `Option<&Box<dyn -State>>` is returned. If we didn’t call `as_ref`, we would get an error because +is an `Option<Box<dyn State>>`, when we call `as_ref`, an `Option<&Box<dyn State>>` is returned. If we didn’t call `as_ref`, we would get an error because we can’t move `state` out of the borrowed `&self` of the function parameter. -We then call the `unwrap` method, which we know will never panic because we +We then call the `unwrap` method, which we know will never panic, because we know the methods on `Post` ensure that `state` will always contain a `Some` value when those methods are done. This is one of the cases we talked about in -“Cases in Which You Have More Information Than the Compiler” on page XX when we +the “Cases In Which You Have More Information Than the +Compiler” section of Chapter 9 when we know that a `None` value is never possible, even though the compiler isn’t able to understand that. @@ -925,53 +921,53 @@ will take effect on the `&` and the `Box` so the `content` method will ultimately be called on the type that implements the `State` trait. That means we need to add `content` to the `State` trait definition, and that is where we’ll put the logic for what content to return depending on which state we -have, as shown in Listing 17-18. +have, as shown in Listing 18-18: -Filename: src/lib.rs +src/lib.rs ``` trait State { - --snip-- + // --snip-- fn content<'a>(&self, post: &'a Post) -> &'a str { - 1 "" + "" } } ---snip-- +// --snip-- struct Published {} impl State for Published { - --snip-- + // --snip-- fn content<'a>(&self, post: &'a Post) -> &'a str { - 2 &post.content + &post.content } } ``` -Listing 17-18: Adding the `content` method to the `State` trait +Listing 18-18: Adding the `content` method to the `State` trait We add a default implementation for the `content` method that returns an empty -string slice [1]. That means we don’t need to implement `content` on the -`Draft` and `PendingReview` structs. The `Published` struct will override the -`content` method and return the value in `post.content` [2]. +string slice. That means we don’t need to implement `content` on the `Draft` +and `PendingReview` structs. The `Published` struct will override the `content` +method and return the value in `post.content`. Note that we need lifetime annotations on this method, as we discussed in Chapter 10. We’re taking a reference to a `post` as an argument and returning a reference to part of that `post`, so the lifetime of the returned reference is related to the lifetime of the `post` argument. -And we’re done—all of Listing 17-11 now works! We’ve implemented the state +And we’re done—all of Listing 18-11 now works! We’ve implemented the state pattern with the rules of the blog post workflow. The logic related to the rules lives in the state objects rather than being scattered throughout `Post`. -> ### Why Not An Enum? -> +> #### Why Not An Enum? +> > You may have been wondering why we didn’t use an `enum` with the different -possible post states as variants. That’s certainly a possible solution; try it -and compare the end results to see which you prefer! One disadvantage of using -an enum is that every place that checks the value of the enum will need a -`match` expression or similar to handle every possible variant. This could get -more repetitive than this trait object solution. +> possible post states as variants. That’s certainly a possible solution, try +> it and compare the end results to see which you prefer! One disadvantage of +> using an enum is every place that checks the value of the enum will need a +> `match` expression or similar to handle every possible variant. This could +> get more repetitive than this trait object solution. ### Trade-offs of the State Pattern @@ -999,11 +995,11 @@ functionality. To see the simplicity of maintaining code that uses the state pattern, try a few of these suggestions: * Add a `reject` method that changes the post’s state from `PendingReview` back -to `Draft`. + to `Draft`. * Require two calls to `approve` before the state can be changed to `Published`. * Allow users to add text content only when a post is in the `Draft` state. -Hint: have the state object responsible for what might change about the content -but not responsible for modifying the `Post`. + Hint: have the state object responsible for what might change about the + content but not responsible for modifying the `Post`. One downside of the state pattern is that, because the states implement the transitions between states, some of the states are coupled to each other. If we @@ -1015,22 +1011,22 @@ another design pattern. Another downside is that we’ve duplicated some logic. To eliminate some of the duplication, we might try to make default implementations for the -`request_review` and `approve` methods on the `State` trait that return `self`. -However, this wouldn’t work: when using `State` as a trait object, the trait -doesn’t know what the concrete `self` will be exactly, so the return type isn’t -known at compile time. +`request_review` and `approve` methods on the `State` trait that return `self`; +however, this would not be dyn compatible, because the trait doesn’t know what +the concrete `self` will be exactly. We want to be able to use `State` as a +trait object, so we need its methods to be dyn compatible. Other duplication includes the similar implementations of the `request_review` and `approve` methods on `Post`. Both methods delegate to the implementation of the same method on the value in the `state` field of `Option` and set the new value of the `state` field to the result. If we had a lot of methods on `Post` that followed this pattern, we might consider defining a macro to eliminate the -repetition (see “Macros” on page XX). +repetition (see the “Macros” section in Chapter 20). By implementing the state pattern exactly as it’s defined for object-oriented languages, we’re not taking as full advantage of Rust’s strengths as we could. Let’s look at some changes we can make to the `blog` crate that can make -invalid states and transitions into compile-time errors. +invalid states and transitions into compile time errors. #### Encoding States and Behavior as Types @@ -1040,9 +1036,9 @@ outside code has no knowledge of them, we’ll encode the states into different types. Consequently, Rust’s type checking system will prevent attempts to use draft posts where only published posts are allowed by issuing a compiler error. -Let’s consider the first part of `main` in Listing 17-11: +Let’s consider the first part of `main` in Listing 18-11: -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1053,17 +1049,19 @@ fn main() { } ``` + + We still enable the creation of new posts in the draft state using `Post::new` and the ability to add text to the post’s content. But instead of having a `content` method on a draft post that returns an empty string, we’ll make it so draft posts don’t have the `content` method at all. That way, if we try to get a draft post’s content, we’ll get a compiler error telling us the method doesn’t exist. As a result, it will be impossible for us to accidentally -display draft post content in production because that code won’t even compile. -Listing 17-19 shows the definition of a `Post` struct and a `DraftPost` struct, -as well as methods on each. +display draft post content in production, because that code won’t even compile. +Listing 18-19 shows the definition of a `Post` struct and a `DraftPost` struct, +as well as methods on each: -Filename: src/lib.rs +src/lib.rs ``` pub struct Post { @@ -1075,43 +1073,42 @@ pub struct DraftPost { } impl Post { - 1 pub fn new() -> DraftPost { + pub fn new() -> DraftPost { DraftPost { content: String::new(), } } - 2 pub fn content(&self) -> &str { + pub fn content(&self) -> &str { &self.content } } impl DraftPost { - 3 pub fn add_text(&mut self, text: &str) { + pub fn add_text(&mut self, text: &str) { self.content.push_str(text); } } ``` -Listing 17-19: A `Post` with a `content` method and a `DraftPost` without a -`content` method +Listing 18-19: A `Post` with a `content` method and `DraftPost` without a `content` method Both the `Post` and `DraftPost` structs have a private `content` field that stores the blog post text. The structs no longer have the `state` field because we’re moving the encoding of the state to the types of the structs. The `Post` struct will represent a published post, and it has a `content` method that -returns the `content` [2]. +returns the `content`. We still have a `Post::new` function, but instead of returning an instance of -`Post`, it returns an instance of `DraftPost` [1]. Because `content` is private +`Post`, it returns an instance of `DraftPost`. Because `content` is private and there aren’t any functions that return `Post`, it’s not possible to create an instance of `Post` right now. The `DraftPost` struct has an `add_text` method, so we can add text to -`content` as before [3], but note that `DraftPost` does not have a `content` -method defined! So now the program ensures all posts start as draft posts, and -draft posts don’t have their content available for display. Any attempt to get -around these constraints will result in a compiler error. +`content` as before, but note that `DraftPost` does not have a `content` method +defined! So now the program ensures all posts start as draft posts, and draft +posts don’t have their content available for display. Any attempt to get around +these constraints will result in a compiler error. #### Implementing Transitions as Transformations into Different Types @@ -1119,15 +1116,15 @@ So how do we get a published post? We want to enforce the rule that a draft post has to be reviewed and approved before it can be published. A post in the pending review state should still not display any content. Let’s implement these constraints by adding another struct, `PendingReviewPost`, defining the -`request_review` method on `DraftPost` to return a `PendingReviewPost` and +`request_review` method on `DraftPost` to return a `PendingReviewPost`, and defining an `approve` method on `PendingReviewPost` to return a `Post`, as -shown in Listing 17-20. +shown in Listing 18-20: -Filename: src/lib.rs +src/lib.rs ``` impl DraftPost { - --snip-- + // --snip-- pub fn request_review(self) -> PendingReviewPost { PendingReviewPost { content: self.content, @@ -1148,9 +1145,7 @@ impl PendingReviewPost { } ``` -Listing 17-20: A `PendingReviewPost` that gets created by calling -`request_review` on `DraftPost` and an `approve` method that turns a -`PendingReviewPost` into a published `Post` +Listing 18-20: A `PendingReviewPost` that gets created by calling `request_review` on `DraftPost` and an `approve` method that turns a `PendingReviewPost` into a published `Post` The `request_review` and `approve` methods take ownership of `self`, thus consuming the `DraftPost` and `PendingReviewPost` instances and transforming @@ -1170,9 +1165,9 @@ called on, so we need to add more `let post =` shadowing assignments to save the returned instances. We also can’t have the assertions about the draft and pending review posts’ contents be empty strings, nor do we need them: we can’t compile code that tries to use the content of posts in those states any longer. -The updated code in `main` is shown in Listing 17-21. +The updated code in `main` is shown in Listing 18-21: -Filename: src/main.rs +src/main.rs ``` use blog::Post; @@ -1190,8 +1185,7 @@ fn main() { } ``` -Listing 17-21: Modifications to `main` to use the new implementation of the -blog post workflow +Listing 18-21: Modifications to `main` to use the new implementation of the blog post workflow The changes we needed to make to `main` to reassign `post` mean that this implementation doesn’t quite follow the object-oriented state pattern anymore: @@ -1202,7 +1196,7 @@ compile time! This ensures that certain bugs, such as display of the content of an unpublished post, will be discovered before they make it to production. Try the tasks suggested at the start of this section on the `blog` crate as it -is after Listing 17-21 to see what you think about the design of this version +is after Listing 18-21 to see what you think about the design of this version of the code. Note that some of the tasks might be completed already in this design. @@ -1217,17 +1211,16 @@ object-oriented languages don’t have. ## Summary -Regardless of whether you think Rust is an object-oriented language after +No matter whether or not you think Rust is an object-oriented language after reading this chapter, you now know that you can use trait objects to get some object-oriented features in Rust. Dynamic dispatch can give your code some flexibility in exchange for a bit of runtime performance. You can use this flexibility to implement object-oriented patterns that can help your code’s maintainability. Rust also has other features, like ownership, that object-oriented languages don’t have. An object-oriented pattern won’t always -be the best way to take advantage of Rust’s strengths, but it is an available +be the best way to take advantage of Rust’s strengths, but is an available option. Next, we’ll look at patterns, which are another of Rust’s features that enable lots of flexibility. We’ve looked at them briefly throughout the book but haven’t seen their full capability yet. Let’s go! - diff --git a/nostarch/chapter19.md b/nostarch/chapter19.md index 40c7f10a1c..bf7e5d2bc1 100644 --- a/nostarch/chapter19.md +++ b/nostarch/chapter19.md @@ -67,40 +67,39 @@ match x { } ``` -The patterns in this `match` expression are the `None` and `Some(i)` to the +The patterns in this `match` expression are the `None` and `Some(i)` on the left of each arrow. One requirement for `match` expressions is that they need to be *exhaustive* in the sense that all possibilities for the value in the `match` expression must be accounted for. One way to ensure you’ve covered every possibility is to have -a catchall pattern for the last arm: for example, a variable name matching any +a catch-all pattern for the last arm: for example, a variable name matching any value can never fail and thus covers every remaining case. The particular pattern `_` will match anything, but it never binds to a variable, so it’s often used in the last match arm. The `_` pattern can be useful when you want to ignore any value not specified, for example. We’ll -cover the `_` pattern in more detail in “Ignoring Values in a Pattern” on page -XX. +cover the `_` pattern in more detail in the “Ignoring Values in a +Pattern” section later in this +chapter. ### Conditional if let Expressions -In Chapter 6, we discussed how to use `if let` expressions mainly as a shorter +In Chapter 6 we discussed how to use `if let` expressions mainly as a shorter way to write the equivalent of a `match` that only matches one case. Optionally, `if let` can have a corresponding `else` containing code to run if the pattern in the `if let` doesn’t match. -Listing 18-1 shows that it’s also possible to mix and match `if let`, `else -if`, and `else if let` expressions. Doing so gives us more flexibility than a +Listing 19-1 shows that it’s also possible to mix and match `if let`, `else if`, and `else if let` expressions. Doing so gives us more flexibility than a `match` expression in which we can express only one value to compare with the -patterns. Also, Rust doesn’t require that the conditions in a series of `if -let`, `else if`, and `else if let` arms relate to each other. +patterns. Also, Rust doesn’t require that the conditions in a series of `if let`, `else if`, `else if let` arms relate to each other. -The code in Listing 18-1 determines what color to make your background based on +The code in Listing 19-1 determines what color to make your background based on a series of checks for several conditions. For this example, we’ve created variables with hardcoded values that a real program might receive from user input. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -108,102 +107,101 @@ fn main() { let is_tuesday = false; let age: Result<u8, _> = "34".parse(); - 1 if let Some(color) = favorite_color { - 2 println!( - "Using your favorite, {color}, as the background" - ); - 3 } else if is_tuesday { - 4 println!("Tuesday is green day!"); - 5 } else if let Ok(age) = age { - 6 if age > 30 { - 7 println!("Using purple as the background color"); + if let Some(color) = favorite_color { + println!("Using your favorite color, {color}, as the background"); + } else if is_tuesday { + println!("Tuesday is green day!"); + } else if let Ok(age) = age { + if age > 30 { + println!("Using purple as the background color"); } else { - 8 println!("Using orange as the background color"); + println!("Using orange as the background color"); } - 9 } else { - 10 println!("Using blue as the background color"); + } else { + println!("Using blue as the background color"); } } ``` -Listing 18-1: Mixing `if let`, `else if`, `else if let`, and `else` +Listing 19-1: Mixing `if let`, `else if`, `else if let`, and `else` -If the user specifies a favorite color [1], that color is used as the -background [2]. If no favorite color is specified and today is Tuesday [3], the -background color is green [4]. Otherwise, if the user specifies their age as a -string and we can parse it as a number successfully [5], the color is either -purple [7] or orange [8] depending on the value of the number [6]. If none of -these conditions apply [9], the background color is blue [10]. +If the user specifies a favorite color, that color is used as the background. +If no favorite color is specified and today is Tuesday, the background color is +green. Otherwise, if the user specifies their age as a string and we can parse +it as a number successfully, the color is either purple or orange depending on +the value of the number. If none of these conditions apply, the background +color is blue. This conditional structure lets us support complex requirements. With the -hardcoded values we have here, this example will print `Using purple as the -background color`. +hardcoded values we have here, this example will print `Using purple as the background color`. -You can see that `if let` can also introduce shadowed variables in the same way -that `match` arms can: the line `if let Ok(age) = age` [5] introduces a new -shadowed `age` variable that contains the value inside the `Ok` variant. This -means we need to place the `if age > 30` condition [6] within that block: we -can’t combine these two conditions into `if let Ok(age) = age && age > 30`. The -shadowed `age` we want to compare to 30 isn’t valid until the new scope starts -with the curly bracket. +You can see that `if let` can also introduce new variables which shadow existing +variables in the same way that `match` arms can: the line `if let Ok(age) = age` +introduces a new `age` variable that contains the value inside the `Ok` variant, +shadowing the existing `age` variable. This means we need to place the `if age > 30` condition within that block: we can’t combine these two conditions into `if let Ok(age) = age && age > 30`. The new `age` we want to compare to 30 isn’t +valid until the new scope starts with the curly bracket. The downside of using `if let` expressions is that the compiler doesn’t check for exhaustiveness, whereas with `match` expressions it does. If we omitted the -last `else` block [9] and therefore missed handling some cases, the compiler -would not alert us to the possible logic bug. +last `else` block and therefore missed handling some cases, the compiler would +not alert us to the possible logic bug. ### while let Conditional Loops Similar in construction to `if let`, the `while let` conditional loop allows a -`while` loop to run for as long as a pattern continues to match. In Listing -18-2, we code a `while let` loop that uses a vector as a stack and prints the -values in the vector in the opposite order in which they were pushed. +`while` loop to run for as long as a pattern continues to match. We first saw a +`while let` loop in Chapter 17, where we used it to keep looping as long as a +stream produced new values. Similarly, in Listing 19-2 we show a `while let` +loop that waits on messages sent between threads, but in this case checking a +`Result` instead of an `Option`. -Filename: src/main.rs ``` -let mut stack = Vec::new(); - -stack.push(1); -stack.push(2); -stack.push(3); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + for val in [1, 2, 3] { + tx.send(val).unwrap(); + } + }); -while let Some(top) = stack.pop() { - println!("{top}"); -} + while let Ok(value) = rx.recv() { + println!("{value}"); + } ``` -Listing 18-2: Using a `while let` loop to print values for as long as -`stack.pop()` returns `Some` +Listing 19-2: Using a `while let` loop to print values for as long as `rx.recv()` returns `Ok` -This example prints `3`, `2`, and then `1`. The `pop` method takes the last -element out of the vector and returns `Some(value)`. If the vector is empty, -`pop` returns `None`. The `while` loop continues running the code in its block -as long as `pop` returns `Some`. When `pop` returns `None`, the loop stops. We -can use `while let` to pop every element off our stack. +This example prints 1, 2, and 3. When we saw `recv` back in Chapter 16, we +unwrapped the error directly, or interacted with it as an iterator using a `for` +loop. As Listing 19-2 shows, though, we can also use `while let`, because the +`recv` method returns `Ok` as long as the sender is producing messages, and then +produces an `Err` once the sender side disconnects. ### for Loops In a `for` loop, the value that directly follows the keyword `for` is a -pattern. For example, in `for x in y`, the `x` is the pattern. Listing 18-3 -demonstrates how to use a pattern in a `for` loop to *destructure*, or break +pattern. For example, in `for x in y` the `x` is the pattern. Listing 19-3 +demonstrates how to use a pattern in a `for` loop to destructure, or break apart, a tuple as part of the `for` loop. -Filename: src/main.rs ``` -let v = vec!['a', 'b', 'c']; + let v = vec!['a', 'b', 'c']; -for (index, value) in v.iter().enumerate() { - println!("{value} is at index {index}"); -} + for (index, value) in v.iter().enumerate() { + println!("{value} is at index {index}"); + } ``` -Listing 18-3: Using a pattern in a `for` loop to destructure a tuple +Listing 19-3: Using a pattern in a `for` loop to destructure a tuple -The code in Listing 18-3 will print the following: +The code in Listing 19-3 will print the following: ``` +$ cargo run + Compiling patterns v0.1.0 (file:///projects/patterns) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s + Running `target/debug/patterns` a is at index 0 b is at index 1 c is at index 2 @@ -234,82 +232,87 @@ like this: let PATTERN = EXPRESSION; ``` -In statements like `let x = 5;` with a variable name in the PATTERN slot, the +In statements like `let x = 5;` with a variable name in the `PATTERN` slot, the variable name is just a particularly simple form of a pattern. Rust compares -the expression against the pattern and assigns any names it finds. So, in the +the expression against the pattern and assigns any names it finds. So in the `let x = 5;` example, `x` is a pattern that means “bind what matches here to the variable `x`.” Because the name `x` is the whole pattern, this pattern effectively means “bind everything to the variable `x`, whatever the value is.” -To see the pattern-matching aspect of `let` more clearly, consider Listing -18-4, which uses a pattern with `let` to destructure a tuple. +To see the pattern matching aspect of `let` more clearly, consider Listing +19-4, which uses a pattern with `let` to destructure a tuple. + ``` -let (x, y, z) = (1, 2, 3); + let (x, y, z) = (1, 2, 3); ``` -Listing 18-4: Using a pattern to destructure a tuple and create three variables -at once +Listing 19-4: Using a pattern to destructure a tuple and create three variables at once Here, we match a tuple against a pattern. Rust compares the value `(1, 2, 3)` -to the pattern `(x, y, z)` and sees that the value matches the pattern, in that -it sees that the number of elements is the same in both, so Rust binds `1` to -`x`, `2` to `y`, and `3` to `z`. You can think of this tuple pattern as nesting -three individual variable patterns inside it. +to the pattern `(x, y, z)` and sees that the value matches the pattern, so Rust +binds `1` to `x`, `2` to `y`, and `3` to `z`. You can think of this tuple +pattern as nesting three individual variable patterns inside it. If the number of elements in the pattern doesn’t match the number of elements in the tuple, the overall type won’t match and we’ll get a compiler error. For -example, Listing 18-5 shows an attempt to destructure a tuple with three +example, Listing 19-5 shows an attempt to destructure a tuple with three elements into two variables, which won’t work. + ``` -let (x, y) = (1, 2, 3); + let (x, y) = (1, 2, 3); ``` -Listing 18-5: Incorrectly constructing a pattern whose variables don’t match -the number of elements in the tuple +Listing 19-5: Incorrectly constructing a pattern whose variables don’t match the number of elements in the tuple Attempting to compile this code results in this type error: ``` +$ cargo run + Compiling patterns v0.1.0 (file:///projects/patterns) error[E0308]: mismatched types --> src/main.rs:2:9 | 2 | let (x, y) = (1, 2, 3); - | ^^^^^^ --------- this expression has type `({integer}, {integer}, -{integer})` + | ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})` | | | expected a tuple with 3 elements, found one with 2 elements | = note: expected tuple `({integer}, {integer}, {integer})` found tuple `(_, _)` + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `patterns` (bin "patterns") due to 1 previous error ``` To fix the error, we could ignore one or more of the values in the tuple using -`_` or `..`, as you’ll see in “Ignoring Values in a Pattern” on page XX. If the -problem is that we have too many variables in the pattern, the solution is to -make the types match by removing variables so the number of variables equals -the number of elements in the tuple. +`_` or `..`, as you’ll see in the “Ignoring Values in a +Pattern” section. If the problem +is that we have too many variables in the pattern, the solution is to make the +types match by removing variables so the number of variables equals the number +of elements in the tuple. ### Function Parameters -Function parameters can also be patterns. The code in Listing 18-6, which +Function parameters can also be patterns. The code in Listing 19-6, which declares a function named `foo` that takes one parameter named `x` of type `i32`, should by now look familiar. + ``` fn foo(x: i32) { // code goes here } ``` -Listing 18-6: A function signature using patterns in the parameters +Listing 19-6: A function signature uses patterns in the parameters The `x` part is a pattern! As we did with `let`, we could match a tuple in a -function’s arguments to the pattern. Listing 18-7 splits the values in a tuple +function’s arguments to the pattern. Listing 19-7 splits the values in a tuple as we pass it to a function. -Filename: src/main.rs +src/main.rs ``` fn print_coordinates(&(x, y): &(i32, i32)) { @@ -322,16 +325,16 @@ fn main() { } ``` -Listing 18-7: A function with parameters that destructure a tuple +Listing 19-7: A function with parameters that destructure a tuple This code prints `Current location: (3, 5)`. The values `&(3, 5)` match the pattern `&(x, y)`, so `x` is the value `3` and `y` is the value `5`. We can also use patterns in closure parameter lists in the same way as in -function parameter lists because closures are similar to functions, as +function parameter lists, because closures are similar to functions, as discussed in Chapter 13. -At this point, you’ve seen several ways to use patterns, but patterns don’t +At this point, you’ve seen several ways of using patterns, but patterns don’t work the same in every place we can use them. In some places, the patterns must be irrefutable; in other circumstances, they can be refutable. We’ll discuss these two concepts next. @@ -342,17 +345,16 @@ Patterns come in two forms: refutable and irrefutable. Patterns that will match for any possible value passed are *irrefutable*. An example would be `x` in the statement `let x = 5;` because `x` matches anything and therefore cannot fail to match. Patterns that can fail to match for some possible value are -*refutable*. An example would be `Some(x)` in the expression `if let Some(x) = -a_value` because if the value in the `a_value` variable is `None` rather than +*refutable*. An example would be `Some(x)` in the expression `if let Some(x) = a_value` because if the value in the `a_value` variable is `None` rather than `Some`, the `Some(x)` pattern will not match. Function parameters, `let` statements, and `for` loops can only accept -irrefutable patterns because the program cannot do anything meaningful when -values don’t match. The `if let` and `while let` expressions accept refutable -and irrefutable patterns, but the compiler warns against irrefutable patterns -because, by definition, they’re intended to handle possible failure: the -functionality of a conditional is in its ability to perform differently -depending on success or failure. +irrefutable patterns, because the program cannot do anything meaningful when +values don’t match. The `if let` and `while let` expressions and the +`let`-`else` statement accept refutable and irrefutable patterns, but the +compiler warns against irrefutable patterns because by definition they’re +intended to handle possible failure: the functionality of a conditional is in +its ability to perform differently depending on success or failure. In general, you shouldn’t have to worry about the distinction between refutable and irrefutable patterns; however, you do need to be familiar with the concept @@ -361,38 +363,42 @@ those cases, you’ll need to change either the pattern or the construct you’r using the pattern with, depending on the intended behavior of the code. Let’s look at an example of what happens when we try to use a refutable pattern -where Rust requires an irrefutable pattern and vice versa. Listing 18-8 shows a -`let` statement, but for the pattern, we’ve specified `Some(x)`, a refutable +where Rust requires an irrefutable pattern and vice versa. Listing 19-8 shows a +`let` statement, but for the pattern we’ve specified `Some(x)`, a refutable pattern. As you might expect, this code will not compile. + ``` -let Some(x) = some_option_value; + let Some(x) = some_option_value; ``` -Listing 18-8: Attempting to use a refutable pattern with `let` +Listing 19-8: Attempting to use a refutable pattern with `let` -If `some_option_value` were a `None` value, it would fail to match the pattern +If `some_option_value` was a `None` value, it would fail to match the pattern `Some(x)`, meaning the pattern is refutable. However, the `let` statement can only accept an irrefutable pattern because there is nothing valid the code can do with a `None` value. At compile time, Rust will complain that we’ve tried to use a refutable pattern where an irrefutable pattern is required: ``` -error[E0005]: refutable pattern in local binding: `None` not covered - --> src/main.rs:3:9 - | -3 | let Some(x) = some_option_value; - | ^^^^^^^ pattern `None` not covered - | - = note: `let` bindings require an "irrefutable pattern", like a `struct` or -an `enum` with only one variant - = note: for more information, visit -https://doc.rust-lang.org/book/ch18-02-refutability.html - = note: the matched value is of type `Option<i32>` -help: you might want to use `if let` to ignore the variant that isn't matched - | -3 | let x = if let Some(x) = some_option_value { x } else { todo!() }; - | ++++++++++ ++++++++++++++++++++++ +$ cargo run + Compiling patterns v0.1.0 (file:///projects/patterns) +error[E0005]: refutable pattern in local binding + --> src/main.rs:3:9 + | +3 | let Some(x) = some_option_value; + | ^^^^^^^ pattern `None` not covered + | + = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant + = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html + = note: the matched value is of type `Option<i32>` +help: you might want to use `let else` to handle the variant that isn't matched + | +3 | let Some(x) = some_option_value else { todo!() }; + | ++++++++++++++++ + +For more information about this error, try `rustc --explain E0005`. +error: could not compile `patterns` (bin "patterns") due to 1 previous error ``` Because we didn’t cover (and couldn’t cover!) every valid value with the @@ -400,46 +406,53 @@ pattern `Some(x)`, Rust rightfully produces a compiler error. If we have a refutable pattern where an irrefutable pattern is needed, we can fix it by changing the code that uses the pattern: instead of using `let`, we -can use `if let`. Then, if the pattern doesn’t match, the code will just skip +can use `if let`. Then if the pattern doesn’t match, the code will just skip the code in the curly brackets, giving it a way to continue validly. Listing -18-9 shows how to fix the code in Listing 18-8. +19-9 shows how to fix the code in Listing 19-8. + ``` -if let Some(x) = some_option_value { - println!("{x}"); -} + if let Some(x) = some_option_value { + println!("{x}"); + } ``` -Listing 18-9: Using `if let` and a block with refutable patterns instead of -`let` +Listing 19-9: Using `if let` and a block with refutable patterns instead of `let` + +We’ve given the code an out! This code is perfectly valid now. However, +if we give `if let` an irrefutable pattern (a pattern that will always +match), such as `x`, as shown in Listing 19-10, the compiler will give a +warning. -We’ve given the code an out! This code is perfectly valid, although it means we -cannot use an irrefutable pattern without receiving an error. If we give `if -let` a pattern that will always match, such as `x`, as shown in Listing 18-10, -the compiler will give a warning. ``` -if let x = 5 { - println!("{x}"); -}; + if let x = 5 { + println!("{x}"); + }; ``` -Listing 18-10: Attempting to use an irrefutable pattern with `if let` +Listing 19-10: Attempting to use an irrefutable pattern with `if let` Rust complains that it doesn’t make sense to use `if let` with an irrefutable pattern: ``` +$ cargo run + Compiling patterns v0.1.0 (file:///projects/patterns) warning: irrefutable `if let` pattern --> src/main.rs:2:8 | 2 | if let x = 5 { | ^^^^^^^^^ | - = note: `#[warn(irrefutable_let_patterns)]` on by default - = note: this pattern will always match, so the `if let` is -useless + = note: this pattern will always match, so the `if let` is useless = help: consider replacing the `if let` with a `let` + = note: `#[warn(irrefutable_let_patterns)]` on by default + +warning: `patterns` (bin "patterns") generated 1 warning + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s + Running `target/debug/patterns` +5 ``` For this reason, match arms must use refutable patterns, except for the last @@ -454,110 +467,101 @@ patterns. ## Pattern Syntax -In this section, we gather all the syntax that is valid in patterns and discuss -why and when you might want to use each one. +In this section, we gather all the syntax valid in patterns and discuss why and +when you might want to use each one. ### Matching Literals As you saw in Chapter 6, you can match patterns against literals directly. The following code gives some examples: -Filename: src/main.rs - ``` -let x = 1; + let x = 1; -match x { - 1 => println!("one"), - 2 => println!("two"), - 3 => println!("three"), - _ => println!("anything"), -} + match x { + 1 => println!("one"), + 2 => println!("two"), + 3 => println!("three"), + _ => println!("anything"), + } ``` -This code prints `one` because the value in `x` is `1`. This syntax is useful +This code prints `one` because the value in `x` is 1. This syntax is useful when you want your code to take an action if it gets a particular concrete value. ### Matching Named Variables Named variables are irrefutable patterns that match any value, and we’ve used -them many times in this book. However, there is a complication when you use -named variables in `match` expressions. Because `match` starts a new scope, -variables declared as part of a pattern inside the `match` expression will -shadow those with the same name outside the `match` construct, as is the case -with all variables. In Listing 18-11, we declare a variable named `x` with the -value `Some(5)` and a variable `y` with the value `10`. We then create a -`match` expression on the value `x`. Look at the patterns in the match arms and -`println!` at the end, and try to figure out what the code will print before -running this code or reading further. +them many times in the book. However, there is a complication when you use named +variables in `match`, `if let`, or `while let` expressions. Because each of +these kinds of expression starts a new scope, variables declared as part of a +pattern inside the expression will shadow those with the same name outside, as +is the case with all variables. In Listing 19-11, we declare a variable named +`x` with the value `Some(5)` and a variable `y` with the value `10`. We then +create a `match` expression on the value `x`. Look at the patterns in the match +arms and `println!` at the end, and try to figure out what the code will print +before running this code or reading further. -Filename: src/main.rs +src/main.rs ``` -fn main() { - 1 let x = Some(5); - 2 let y = 10; + let x = Some(5); + let y = 10; match x { - 3 Some(50) => println!("Got 50"), - 4 Some(y) => println!("Matched, y = {y}"), - 5 _ => println!("Default case, x = {:?}", x), + Some(50) => println!("Got 50"), + Some(y) => println!("Matched, y = {y}"), + _ => println!("Default case, x = {x:?}"), } - 6 println!("at the end: x = {:?}, y = {y}", x); -} + println!("at the end: x = {x:?}, y = {y}"); ``` -Listing 18-11: A `match` expression with an arm that introduces a shadowed -variable `y` +Listing 19-11: A `match` expression with an arm that introduces a new variable which shadows an existing variable `y` Let’s walk through what happens when the `match` expression runs. The pattern -in the first match arm [3] doesn’t match the defined value of `x` [1], so the -code continues. +in the first match arm doesn’t match the defined value of `x`, so the code +continues. -The pattern in the second match arm [4] introduces a new variable named `y` -that will match any value inside a `Some` value. Because we’re in a new scope -inside the `match` expression, this is a new `y` variable, not the `y` we -declared at the beginning with the value `10` [2]. This new `y` binding will -match any value inside a `Some`, which is what we have in `x`. Therefore, this -new `y` binds to the inner value of the `Some` in `x`. That value is `5`, so -the expression for that arm executes and prints `Matched, y = 5`. +The pattern in the second match arm introduces a new variable named `y` that +will match any value inside a `Some` value. Because we’re in a new scope inside +the `match` expression, this is a new `y` variable, not the `y` we declared at +the beginning with the value 10. This new `y` binding will match any value +inside a `Some`, which is what we have in `x`. Therefore, this new `y` binds to +the inner value of the `Some` in `x`. That value is `5`, so the expression for +that arm executes and prints `Matched, y = 5`. If `x` had been a `None` value instead of `Some(5)`, the patterns in the first two arms wouldn’t have matched, so the value would have matched to the -underscore [5]. We didn’t introduce the `x` variable in the pattern of the +underscore. We didn’t introduce the `x` variable in the pattern of the underscore arm, so the `x` in the expression is still the outer `x` that hasn’t -been shadowed. In this hypothetical case, the `match` would print `Default -case, x = None`. +been shadowed. In this hypothetical case, the `match` would print `Default case, x = None`. When the `match` expression is done, its scope ends, and so does the scope of -the inner `y`. The last `println!` [6] produces `at the end: x = Some(5), y = -10`. +the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. To create a `match` expression that compares the values of the outer `x` and -`y`, rather than introducing a shadowed variable, we would need to use a match -guard conditional instead. We’ll talk about match guards in “Extra Conditionals -with Match Guards” on page XX. +`y`, rather than introducing a new variable which shadows the existing `y` +variable, we would need to use a match guard conditional instead. We’ll talk +about match guards later in the “Extra Conditionals with Match +Guards” section. ### Multiple Patterns -In `match` expressions, you can match multiple patterns using the `|` syntax, -which is the pattern *or* operator. For example, in the following code we match -the value of `x` against the match arms, the first of which has an *or* option, -meaning if the value of `x` matches either of the values in that arm, that -arm’s code will run: - -Filename: src/main.rs +You can match multiple patterns using the `|` syntax, which is the pattern *or* +operator. For example, in the following code we match the value of `x` against +the match arms, the first of which has an *or* option, meaning if the value of +`x` matches either of the values in that arm, that arm’s code will run: ``` -let x = 1; + let x = 1; -match x { - 1 | 2 => println!("one or two"), - 3 => println!("three"), - _ => println!("anything"), -} + match x { + 1 | 2 => println!("one or two"), + 3 => println!("three"), + _ => println!("anything"), + } ``` This code prints `one or two`. @@ -568,22 +572,20 @@ The `..=` syntax allows us to match to an inclusive range of values. In the following code, when a pattern matches any of the values within the given range, that arm will execute: -Filename: src/main.rs - ``` -let x = 5; + let x = 5; -match x { - 1..=5 => println!("one through five"), - _ => println!("something else"), -} + match x { + 1..=5 => println!("one through five"), + _ => println!("something else"), + } ``` -If `x` is `1`, `2`, `3`, `4`, or `5`, the first arm will match. This syntax is -more convenient for multiple match values than using the `|` operator to -express the same idea; if we were to use `|`, we would have to specify `1 | 2 | -3 | 4 | 5`. Specifying a range is much shorter, especially if we want to match, -say, any number between 1 and 1,000! +If `x` is 1, 2, 3, 4, or 5, the first arm will match. This syntax is more +convenient for multiple match values than using the `|` operator to express the +same idea; if we were to use `|` we would have to specify `1 | 2 | 3 | 4 | 5`. +Specifying a range is much shorter, especially if we want to match, say, any +number between 1 and 1,000! The compiler checks that the range isn’t empty at compile time, and because the only types for which Rust can tell if a range is empty or not are `char` and @@ -591,20 +593,17 @@ numeric values, ranges are only allowed with numeric or `char` values. Here is an example using ranges of `char` values: -Filename: src/main.rs - ``` -let x = 'c'; + let x = 'c'; -match x { - 'a'..='j' => println!("early ASCII letter"), - 'k'..='z' => println!("late ASCII letter"), - _ => println!("something else"), -} + match x { + 'a'..='j' => println!("early ASCII letter"), + 'k'..='z' => println!("late ASCII letter"), + _ => println!("something else"), + } ``` -Rust can tell that `'c'` is within the first pattern’s range and prints `early -ASCII letter`. +Rust can tell that `'c'` is within the first pattern’s range and prints `early ASCII letter`. ### Destructuring to Break Apart Values @@ -613,10 +612,10 @@ different parts of these values. Let’s walk through each value. #### Destructuring Structs -Listing 18-12 shows a `Point` struct with two fields, `x` and `y`, that we can +Listing 19-12 shows a `Point` struct with two fields, `x` and `y`, that we can break apart using a pattern with a `let` statement. -Filename: src/main.rs +src/main.rs ``` struct Point { @@ -633,7 +632,7 @@ fn main() { } ``` -Listing 18-12: Destructuring a struct’s fields into separate variables +Listing 19-12: Destructuring a struct’s fields into separate variables This code creates the variables `a` and `b` that match the values of the `x` and `y` fields of the `p` struct. This example shows that the names of the @@ -643,11 +642,11 @@ easier to remember which variables came from which fields. Because of this common usage, and because writing `let Point { x: x, y: y } = p;` contains a lot of duplication, Rust has a shorthand for patterns that match struct fields: you only need to list the name of the struct field, and the variables created -from the pattern will have the same names. Listing 18-13 behaves in the same -way as the code in Listing 18-12, but the variables created in the `let` +from the pattern will have the same names. Listing 19-13 behaves in the same +way as the code in Listing 19-12, but the variables created in the `let` pattern are `x` and `y` instead of `a` and `b`. -Filename: src/main.rs +src/main.rs ``` struct Point { @@ -664,7 +663,7 @@ fn main() { } ``` -Listing 18-13: Destructuring struct fields using struct field shorthand +Listing 19-13: Destructuring struct fields using struct field shorthand This code creates the variables `x` and `y` that match the `x` and `y` fields of the `p` variable. The outcome is that the variables `x` and `y` contain the @@ -675,11 +674,11 @@ rather than creating variables for all the fields. Doing so allows us to test some of the fields for particular values while creating variables to destructure the other fields. -In Listing 18-14, we have a `match` expression that separates `Point` values +In Listing 19-14, we have a `match` expression that separates `Point` values into three cases: points that lie directly on the `x` axis (which is true when -`y = 0`), on the `y` axis (`x = 0`), or on neither axis. +`y = 0`), on the `y` axis (`x = 0`), or neither. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -695,7 +694,7 @@ fn main() { } ``` -Listing 18-14: Destructuring and matching literal values in one pattern +Listing 19-14: Destructuring and matching literal values in one pattern The first arm will match any point that lies on the `x` axis by specifying that the `y` field matches if its value matches the literal `0`. The pattern still @@ -707,7 +706,7 @@ value of the `y` field. The third arm doesn’t specify any literals, so it matches any other `Point` and creates variables for both the `x` and `y` fields. In this example, the value `p` matches the second arm by virtue of `x` -containing a `0`, so this code will print `On the y axis at 7`. +containing a 0, so this code will print `On the y axis at 7`. Remember that a `match` expression stops checking arms once it has found the first matching pattern, so even though `Point { x: 0, y: 0}` is on the `x` axis @@ -715,13 +714,13 @@ and the `y` axis, this code would only print `On the x axis at 0`. #### Destructuring Enums -We’ve destructured enums in this book (for example, Listing 6-5), but we -haven’t yet explicitly discussed that the pattern to destructure an enum +We’ve destructured enums in this book (for example, Listing 6-5 in Chapter 6), +but haven’t yet explicitly discussed that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined. As an -example, in Listing 18-15 we use the `Message` enum from Listing 6-2 and write +example, in Listing 19-15 we use the `Message` enum from Listing 6-2 and write a `match` with patterns that will destructure each inner value. -Filename: src/main.rs +src/main.rs ``` enum Message { @@ -732,58 +731,53 @@ enum Message { } fn main() { - 1 let msg = Message::ChangeColor(0, 160, 255); + let msg = Message::ChangeColor(0, 160, 255); match msg { - 2 Message::Quit => { - println!( - "The Quit variant has no data to destructure." - ); + Message::Quit => { + println!("The Quit variant has no data to destructure."); } - 3 Message::Move { x, y } => { - println!( - "Move in the x dir {x}, in the y dir {y}" - ); + Message::Move { x, y } => { + println!("Move in the x direction {x} and in the y direction {y}"); } - 4 Message::Write(text) => { + Message::Write(text) => { println!("Text message: {text}"); } - 5 Message::ChangeColor(r, g, b) => println!( - "Change color to red {r}, green {g}, and blue {b}" - ), + Message::ChangeColor(r, g, b) => { + println!("Change the color to red {r}, green {g}, and blue {b}"); + } } } ``` -Listing 18-15: Destructuring enum variants that hold different kinds of values +Listing 19-15: Destructuring enum variants that hold different kinds of values -This code will print `Change color to red 0, green 160, and blue 255`. Try -changing the value of `msg` [1] to see the code from the other arms run. +This code will print `Change the color to red 0, green 160, and blue 255`. Try +changing the value of `msg` to see the code from the other arms run. -For enum variants without any data, like `Message::Quit` [2], we can’t -destructure the value any further. We can only match on the literal -`Message::Quit` value, and no variables are in that pattern. +For enum variants without any data, like `Message::Quit`, we can’t destructure +the value any further. We can only match on the literal `Message::Quit` value, +and no variables are in that pattern. -For struct-like enum variants, such as `Message::Move` [3], we can use a -pattern similar to the pattern we specify to match structs. After the variant -name, we place curly brackets and then list the fields with variables so we -break apart the pieces to use in the code for this arm. Here we use the -shorthand form as we did in Listing 18-13. +For struct-like enum variants, such as `Message::Move`, we can use a pattern +similar to the pattern we specify to match structs. After the variant name, we +place curly brackets and then list the fields with variables so we break apart +the pieces to use in the code for this arm. Here we use the shorthand form as +we did in Listing 19-13. For tuple-like enum variants, like `Message::Write` that holds a tuple with one -element [4] and `Message::ChangeColor` that holds a tuple with three elements -[5], the pattern is similar to the pattern we specify to match tuples. The -number of variables in the pattern must match the number of elements in the -variant we’re matching. +element and `Message::ChangeColor` that holds a tuple with three elements, the +pattern is similar to the pattern we specify to match tuples. The number of +variables in the pattern must match the number of elements in the variant we’re +matching. #### Destructuring Nested Structs and Enums So far, our examples have all been matching structs or enums one level deep, but matching can work on nested items too! For example, we can refactor the -code in Listing 18-15 to support RGB and HSV colors in the `ChangeColor` -message, as shown in Listing 18-16. +code in Listing 19-15 to support RGB and HSV colors in the `ChangeColor` +message, as shown in Listing 19-16. -Filename: src/main.rs ``` enum Color { @@ -802,18 +796,18 @@ fn main() { let msg = Message::ChangeColor(Color::Hsv(0, 160, 255)); match msg { - Message::ChangeColor(Color::Rgb(r, g, b)) => println!( - "Change color to red {r}, green {g}, and blue {b}" - ), - Message::ChangeColor(Color::Hsv(h, s, v)) => println!( - "Change color to hue {h}, saturation {s}, value {v}" - ), + Message::ChangeColor(Color::Rgb(r, g, b)) => { + println!("Change color to red {r}, green {g}, and blue {b}"); + } + Message::ChangeColor(Color::Hsv(h, s, v)) => { + println!("Change color to hue {h}, saturation {s}, value {v}"); + } _ => (), } } ``` -Listing 18-16: Matching on nested enums +Listing 19-16: Matching on nested enums The pattern of the first arm in the `match` expression matches a `Message::ChangeColor` enum variant that contains a `Color::Rgb` variant; then @@ -829,8 +823,7 @@ The following example shows a complicated destructure where we nest structs and tuples inside a tuple and destructure all the primitive values out: ``` -let ((feet, inches), Point { x, y }) = - ((3, 10), Point { x: 3, y: -10 }); + let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); ``` This code lets us break complex types into their component parts so we can use @@ -842,21 +835,21 @@ as the value from each field in a struct, separately from each other. ### Ignoring Values in a Pattern You’ve seen that it’s sometimes useful to ignore values in a pattern, such as -in the last arm of a `match`, to get a catchall that doesn’t actually do +in the last arm of a `match`, to get a catch-all that doesn’t actually do anything but does account for all remaining possible values. There are a few ways to ignore entire values or parts of values in a pattern: using the `_` pattern (which you’ve seen), using the `_` pattern within another pattern, using a name that starts with an underscore, or using `..` to ignore remaining parts of a value. Let’s explore how and why to use each of these patterns. -#### An Entire Value with _ +#### Ignoring an Entire Value with \_ We’ve used the underscore as a wildcard pattern that will match any value but not bind to the value. This is especially useful as the last arm in a `match` expression, but we can also use it in any pattern, including function -parameters, as shown in Listing 18-17. +parameters, as shown in Listing 19-17. -Filename: src/main.rs +src/main.rs ``` fn foo(_: i32, y: i32) { @@ -868,7 +861,7 @@ fn main() { } ``` -Listing 18-17: Using `_` in a function signature +Listing 19-17: Using `_` in a function signature This code will completely ignore the value `3` passed as the first argument, and will print `This code only uses the y parameter: 4`. @@ -881,35 +874,33 @@ function body in your implementation doesn’t need one of the parameters. You then avoid getting a compiler warning about unused function parameters, as you would if you used a name instead. -#### Parts of a Value with a Nested _ +#### Ignoring Parts of a Value with a Nested \_ We can also use `_` inside another pattern to ignore just part of a value, for example, when we want to test for only part of a value but have no use for the -other parts in the corresponding code we want to run. Listing 18-18 shows code +other parts in the corresponding code we want to run. Listing 19-18 shows code responsible for managing a setting’s value. The business requirements are that the user should not be allowed to overwrite an existing customization of a setting but can unset the setting and give it a value if it is currently unset. -Filename: src/main.rs ``` -let mut setting_value = Some(5); -let new_setting_value = Some(10); + let mut setting_value = Some(5); + let new_setting_value = Some(10); -match (setting_value, new_setting_value) { - (Some(_), Some(_)) => { - println!("Can't overwrite an existing customized value"); - } - _ => { - setting_value = new_setting_value; + match (setting_value, new_setting_value) { + (Some(_), Some(_)) => { + println!("Can't overwrite an existing customized value"); + } + _ => { + setting_value = new_setting_value; + } } -} -println!("setting is {:?}", setting_value); + println!("setting is {setting_value:?}"); ``` -Listing 18-18: Using an underscore within patterns that match `Some` variants -when we don’t need to use the value inside the `Some` +Listing 19-18: Using an underscore within patterns that match `Some` variants when we don’t need to use the value inside the `Some` This code will print `Can't overwrite an existing customized value` and then `setting is Some(5)`. In the first match arm, we don’t need to match on or use @@ -918,42 +909,41 @@ when `setting_value` and `new_setting_value` are the `Some` variant. In that case, we print the reason for not changing `setting_value`, and it doesn’t get changed. -In all other cases (if either `setting_value` or `new_setting_value` is `None`) -expressed by the `_` pattern in the second arm, we want to allow +In all other cases (if either `setting_value` or `new_setting_value` are +`None`) expressed by the `_` pattern in the second arm, we want to allow `new_setting_value` to become `setting_value`. We can also use underscores in multiple places within one pattern to ignore -particular values. Listing 18-19 shows an example of ignoring the second and +particular values. Listing 19-19 shows an example of ignoring the second and fourth values in a tuple of five items. -Filename: src/main.rs ``` -let numbers = (2, 4, 8, 16, 32); + let numbers = (2, 4, 8, 16, 32); -match numbers { - (first, _, third, _, fifth) => { - println!("Some numbers: {first}, {third}, {fifth}"); + match numbers { + (first, _, third, _, fifth) => { + println!("Some numbers: {first}, {third}, {fifth}") + } } -} ``` -Listing 18-19: Ignoring multiple parts of a tuple +Listing 19-19: Ignoring multiple parts of a tuple -This code will print `Some numbers: 2, 8, 32`, and the values `4` and `16` will -be ignored. +This code will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be +ignored. -#### An Unused Variable by Starting Its Name with _ +#### Ignoring an Unused Variable by Starting Its Name with \_ If you create a variable but don’t use it anywhere, Rust will usually issue a warning because an unused variable could be a bug. However, sometimes it’s useful to be able to create a variable you won’t use yet, such as when you’re prototyping or just starting a project. In this situation, you can tell Rust not to warn you about the unused variable by starting the name of the variable -with an underscore. In Listing 18-20, we create two unused variables, but when +with an underscore. In Listing 19-20, we create two unused variables, but when we compile this code, we should only get a warning about one of them. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -962,90 +952,85 @@ fn main() { } ``` -Listing 18-20: Starting a variable name with an underscore to avoid getting -unused variable warnings +Listing 19-20: Starting a variable name with an underscore to avoid getting unused variable warnings -Here, we get a warning about not using the variable `y`, but we don’t get a +Here we get a warning about not using the variable `y`, but we don’t get a warning about not using `_x`. Note that there is a subtle difference between using only `_` and using a name that starts with an underscore. The syntax `_x` still binds the value to the variable, whereas `_` doesn’t bind at all. To show a case where this -distinction matters, Listing 18-21 will provide us with an error. +distinction matters, Listing 19-21 will provide us with an error. -Filename: src/main.rs ``` -let s = Some(String::from("Hello!")); + let s = Some(String::from("Hello!")); -if let Some(_s) = s { - println!("found a string"); -} + if let Some(_s) = s { + println!("found a string"); + } -println!("{:?}", s); + println!("{s:?}"); ``` -Listing 18-21: An unused variable starting with an underscore still binds the -value, which might take ownership of the value. +Listing 19-21: An unused variable starting with an underscore still binds the value, which might take ownership of the value We’ll receive an error because the `s` value will still be moved into `_s`, which prevents us from using `s` again. However, using the underscore by itself -doesn’t ever bind to the value. Listing 18-22 will compile without any errors +doesn’t ever bind to the value. Listing 19-22 will compile without any errors because `s` doesn’t get moved into `_`. -Filename: src/main.rs ``` -let s = Some(String::from("Hello!")); + let s = Some(String::from("Hello!")); -if let Some(_) = s { - println!("found a string"); -} + if let Some(_) = s { + println!("found a string"); + } -println!("{:?}", s); + println!("{s:?}"); ``` -Listing 18-22: Using an underscore does not bind the value. +Listing 19-22: Using an underscore does not bind the value This code works just fine because we never bind `s` to anything; it isn’t moved. -#### Remaining Parts of a Value with .. +#### Ignoring Remaining Parts of a Value with .. With values that have many parts, we can use the `..` syntax to use specific parts and ignore the rest, avoiding the need to list underscores for each ignored value. The `..` pattern ignores any parts of a value that we haven’t -explicitly matched in the rest of the pattern. In Listing 18-23, we have a +explicitly matched in the rest of the pattern. In Listing 19-23, we have a `Point` struct that holds a coordinate in three-dimensional space. In the `match` expression, we want to operate only on the `x` coordinate and ignore the values in the `y` and `z` fields. -Filename: src/main.rs ``` -struct Point { - x: i32, - y: i32, - z: i32, -} + struct Point { + x: i32, + y: i32, + z: i32, + } -let origin = Point { x: 0, y: 0, z: 0 }; + let origin = Point { x: 0, y: 0, z: 0 }; -match origin { - Point { x, .. } => println!("x is {x}"), -} + match origin { + Point { x, .. } => println!("x is {x}"), + } ``` -Listing 18-23: Ignoring all fields of a `Point` except for `x` by using `..` +Listing 19-23: Ignoring all fields of a `Point` except for `x` by using `..` We list the `x` value and then just include the `..` pattern. This is quicker than having to list `y: _` and `z: _`, particularly when we’re working with structs that have lots of fields in situations where only one or two fields are relevant. -The syntax `..` will expand to as many values as it needs to be. Listing 18-24 +The syntax `..` will expand to as many values as it needs to be. Listing 19-24 shows how to use `..` with a tuple. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1059,18 +1044,17 @@ fn main() { } ``` -Listing 18-24: Matching only the first and last values in a tuple and ignoring -all other values +Listing 19-24: Matching only the first and last values in a tuple and ignoring all other values -In this code, the first and last values are matched with `first` and `last`. -The `..` will match and ignore everything in the middle. +In this code, the first and last value are matched with `first` and `last`. The +`..` will match and ignore everything in the middle. However, using `..` must be unambiguous. If it is unclear which values are intended for matching and which should be ignored, Rust will give us an error. -Listing 18-25 shows an example of using `..` ambiguously, so it will not +Listing 19-25 shows an example of using `..` ambiguously, so it will not compile. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1078,17 +1062,19 @@ fn main() { match numbers { (.., second, ..) => { - println!("Some numbers: {second}"); + println!("Some numbers: {second}") }, } } ``` -Listing 18-25: An attempt to use `..` in an ambiguous way +Listing 19-25: An attempt to use `..` in an ambiguous way When we compile this example, we get this error: ``` +$ cargo run + Compiling patterns v0.1.0 (file:///projects/patterns) error: `..` can only be used once per tuple pattern --> src/main.rs:5:22 | @@ -1096,6 +1082,8 @@ error: `..` can only be used once per tuple pattern | -- ^^ can only be used once per tuple pattern | | | previously used here + +error: could not compile `patterns` (bin "patterns") due to 1 previous error ``` It’s impossible for Rust to determine how many values in the tuple to ignore @@ -1110,33 +1098,34 @@ compiler error because using `..` in two places like this is ambiguous. A *match guard* is an additional `if` condition, specified after the pattern in a `match` arm, that must also match for that arm to be chosen. Match guards are -useful for expressing more complex ideas than a pattern alone allows. +useful for expressing more complex ideas than a pattern alone allows. They are +only available in `match` expressions, not in `if let` or `while let` +expressions. -The condition can use variables created in the pattern. Listing 18-26 shows a +The condition can use variables created in the pattern. Listing 19-26 shows a `match` where the first arm has the pattern `Some(x)` and also has a match -guard of `if x % 2 == 0` (which will be `true` if the number is even). +guard of `if x % 2 == 0` (which will be true if the number is even). -Filename: src/main.rs ``` -let num = Some(4); + let num = Some(4); -match num { - Some(x) if x % 2 == 0 => println!("The number {x} is even"), - Some(x) => println!("The number {x} is odd"), - None => (), -} + match num { + Some(x) if x % 2 == 0 => println!("The number {x} is even"), + Some(x) => println!("The number {x} is odd"), + None => (), + } ``` -Listing 18-26: Adding a match guard to a pattern +Listing 19-26: Adding a match guard to a pattern This example will print `The number 4 is even`. When `num` is compared to the -pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then +pattern in the first arm, it matches, because `Some(4)` matches `Some(x)`. Then the match guard checks whether the remainder of dividing `x` by 2 is equal to 0, and because it is, the first arm is selected. If `num` had been `Some(5)` instead, the match guard in the first arm would -have been `false` because the remainder of 5 divided by 2 is 1, which is not +have been false because the remainder of 5 divided by 2 is 1, which is not equal to 0. Rust would then go to the second arm, which would match because the second arm doesn’t have a match guard and therefore matches any `Some` variant. @@ -1145,14 +1134,14 @@ the match guard gives us the ability to express this logic. The downside of this additional expressiveness is that the compiler doesn’t try to check for exhaustiveness when match guard expressions are involved. -In Listing 18-11, we mentioned that we could use match guards to solve our +In Listing 19-11, we mentioned that we could use match guards to solve our pattern-shadowing problem. Recall that we created a new variable inside the pattern in the `match` expression instead of using the variable outside the `match`. That new variable meant we couldn’t test against the value of the -outer variable. Listing 18-27 shows how we can use a match guard to fix this +outer variable. Listing 19-27 shows how we can use a match guard to fix this problem. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1162,14 +1151,14 @@ fn main() { match x { Some(50) => println!("Got 50"), Some(n) if n == y => println!("Matched, n = {n}"), - _ => println!("Default case, x = {:?}", x), + _ => println!("Default case, x = {x:?}"), } - println!("at the end: x = {:?}, y = {y}", x); + println!("at the end: x = {x:?}, y = {y}"); } ``` -Listing 18-27: Using a match guard to test for equality with an outer variable +Listing 19-27: Using a match guard to test for equality with an outer variable This code will now print `Default case, x = Some(5)`. The pattern in the second match arm doesn’t introduce a new variable `y` that would shadow the outer `y`, @@ -1178,40 +1167,39 @@ pattern as `Some(y)`, which would have shadowed the outer `y`, we specify `Some(n)`. This creates a new variable `n` that doesn’t shadow anything because there is no `n` variable outside the `match`. -The match guard `if n == y` is not a pattern and therefore doesn’t introduce -new variables. This `y` *is* the outer `y` rather than a new shadowed `y`, and +The match guard `if n == y` is not a pattern and therefore doesn’t introduce new +variables. This `y` *is* the outer `y` rather than a new `y` shadowing it, and we can look for a value that has the same value as the outer `y` by comparing `n` to `y`. You can also use the *or* operator `|` in a match guard to specify multiple patterns; the match guard condition will apply to all the patterns. Listing -18-28 shows the precedence when combining a pattern that uses `|` with a match +19-28 shows the precedence when combining a pattern that uses `|` with a match guard. The important part of this example is that the `if y` match guard applies to `4`, `5`, *and* `6`, even though it might look like `if y` only applies to `6`. -Filename: src/main.rs ``` -let x = 4; -let y = false; + let x = 4; + let y = false; -match x { - 4 | 5 | 6 if y => println!("yes"), - _ => println!("no"), -} + match x { + 4 | 5 | 6 if y => println!("yes"), + _ => println!("no"), + } ``` -Listing 18-28: Combining multiple patterns with a match guard +Listing 19-28: Combining multiple patterns with a match guard The match condition states that the arm only matches if the value of `x` is equal to `4`, `5`, or `6` *and* if `y` is `true`. When this code runs, the pattern of the first arm matches because `x` is `4`, but the match guard `if y` -is `false`, so the first arm is not chosen. The code moves on to the second -arm, which does match, and this program prints `no`. The reason is that the -`if` condition applies to the whole pattern `4 | 5 | 6`, not just to the last -value `6`. In other words, the precedence of a match guard in relation to a -pattern behaves like this: +is false, so the first arm is not chosen. The code moves on to the second arm, +which does match, and this program prints `no`. The reason is that the `if` +condition applies to the whole pattern `4 | 5 | 6`, not only to the last value +`6`. In other words, the precedence of a match guard in relation to a pattern +behaves like this: ``` (4 | 5 | 6) if y => ... @@ -1231,44 +1219,42 @@ were applied only to the final value in the list of values specified using the ### @ Bindings The *at* operator `@` lets us create a variable that holds a value at the same -time we’re testing that value for a pattern match. In Listing 18-29, we want to -test that a `Message::Hello` `id` field is within the range `3..=7`. We also +time as we’re testing that value for a pattern match. In Listing 19-29, we want +to test that a `Message::Hello` `id` field is within the range `3..=7`. We also want to bind the value to the variable `id_variable` so we can use it in the code associated with the arm. We could name this variable `id`, the same as the field, but for this example we’ll use a different name. -Filename: src/main.rs ``` -enum Message { - Hello { id: i32 }, -} + enum Message { + Hello { id: i32 }, + } -let msg = Message::Hello { id: 5 }; + let msg = Message::Hello { id: 5 }; -match msg { - Message::Hello { - id: id_variable @ 3..=7, - } => println!("Found an id in range: {id_variable}"), - Message::Hello { id: 10..=12 } => { - println!("Found an id in another range") + match msg { + Message::Hello { + id: id_variable @ 3..=7, + } => println!("Found an id in range: {id_variable}"), + Message::Hello { id: 10..=12 } => { + println!("Found an id in another range") + } + Message::Hello { id } => println!("Found some other id: {id}"), } - Message::Hello { id } => println!("Some other id: {id}"), -} ``` -Listing 18-29: Using `@` to bind to a value in a pattern while also testing it +Listing 19-29: Using `@` to bind to a value in a pattern while also testing it -This example will print `Found an id in range: 5`. By specifying `id_variable -@` before the range `3..=7`, we’re capturing whatever value matched the range +This example will print `Found an id in range: 5`. By specifying `id_variable @` before the range `3..=7`, we’re capturing whatever value matched the range while also testing that the value matched the range pattern. -In the second arm, where we only have a range specified in the pattern, the -code associated with the arm doesn’t have a variable that contains the actual -value of the `id` field. The `id` field’s value could have been 10, 11, or 12, -but the code that goes with that pattern doesn’t know which it is. The pattern -code isn’t able to use the value from the `id` field because we haven’t saved -the `id` value in a variable. +In the second arm, where we only have a range specified in the pattern, the code +associated with the arm doesn’t have a variable that contains the actual value +of the `id` field. The `id` field’s value could have been 10, 11, or 12, but +the code that goes with that pattern doesn’t know which it is. The pattern code +isn’t able to use the value from the `id` field, because we haven’t saved the +`id` value in a variable. In the last arm, where we’ve specified a variable without a range, we do have the value available to use in the arm’s code in a variable named `id`. The @@ -1284,9 +1270,8 @@ Rust’s patterns are very useful in distinguishing between different kinds of data. When used in `match` expressions, Rust ensures your patterns cover every possible value, or your program won’t compile. Patterns in `let` statements and function parameters make those constructs more useful, enabling the -destructuring of values into smaller parts at the same time as assigning to +destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs. Next, for the penultimate chapter of the book, we’ll look at some advanced aspects of a variety of Rust’s features. - diff --git a/nostarch/chapter20.md b/nostarch/chapter20.md index 410e7eb62d..7182bfc025 100644 --- a/nostarch/chapter20.md +++ b/nostarch/chapter20.md @@ -9,7 +9,7 @@ directory, so all fixes need to be made in `/src/`. # Advanced Features By now, you’ve learned the most commonly used parts of the Rust programming -language. Before we do one more project, in Chapter 20, we’ll look at a few +language. Before we do one more project in Chapter 21, we’ll look at a few aspects of the language you might run into every once in a while, but may not use every day. You can use this chapter as a reference for when you encounter any unknowns. The features covered here are useful in very specific situations. @@ -19,11 +19,11 @@ grasp of all the features Rust has to offer. In this chapter, we’ll cover: * Unsafe Rust: how to opt out of some of Rust’s guarantees and take -responsibility for manually upholding those guarantees + responsibility for manually upholding those guarantees * Advanced traits: associated types, default type parameters, fully qualified -syntax, supertraits, and the newtype pattern in relation to traits + syntax, supertraits, and the newtype pattern in relation to traits * Advanced types: more about the newtype pattern, type aliases, the never type, -and dynamically sized types + and dynamically sized types * Advanced functions and closures: function pointers and returning closures * Macros: ways to define code that defines more code at compile time @@ -61,30 +61,30 @@ that holds the unsafe code. You can take five actions in unsafe Rust that you can’t in safe Rust, which we call *unsafe superpowers*. Those superpowers include the ability to: -1. Dereference a raw pointer -1. Call an unsafe function or method -1. Access or modify a mutable static variable -1. Implement an unsafe trait -1. Access fields of `union`s +* Dereference a raw pointer +* Call an unsafe function or method +* Access or modify a mutable static variable +* Implement an unsafe trait +* Access fields of a `union` It’s important to understand that `unsafe` doesn’t turn off the borrow checker -or disable any of Rust’s other safety checks: if you use a reference in unsafe +or disable any other of Rust’s safety checks: if you use a reference in unsafe code, it will still be checked. The `unsafe` keyword only gives you access to these five features that are then not checked by the compiler for memory -safety. You’ll still get some degree of safety inside an unsafe block. +safety. You’ll still get some degree of safety inside of an unsafe block. In addition, `unsafe` does not mean the code inside the block is necessarily dangerous or that it will definitely have memory safety problems: the intent is that as the programmer, you’ll ensure the code inside an `unsafe` block will access memory in a valid way. -People are fallible and mistakes will happen, but by requiring these five -unsafe operations to be inside blocks annotated with `unsafe`, you’ll know that +People are fallible, and mistakes will happen, but by requiring these five +unsafe operations to be inside blocks annotated with `unsafe` you’ll know that any errors related to memory safety must be within an `unsafe` block. Keep `unsafe` blocks small; you’ll be thankful later when you investigate memory bugs. -To isolate unsafe code as much as possible, it’s best to enclose such code +To isolate unsafe code as much as possible, it’s best to enclose unsafe code within a safe abstraction and provide a safe API, which we’ll discuss later in the chapter when we examine unsafe functions and methods. Parts of the standard library are implemented as safe abstractions over unsafe code that has been @@ -98,18 +98,17 @@ some abstractions that provide a safe interface to unsafe code. ### Dereferencing a Raw Pointer -In “Dangling References” on page XX, we mentioned that the compiler ensures -references are always valid. Unsafe Rust has two new types called *raw -pointers* that are similar to references. As with references, raw pointers can -be immutable or mutable and are written as `*const T` and `*mut T`, -respectively. The asterisk isn’t the dereference operator; it’s part of the -type name. In the context of raw pointers, *immutable* means that the pointer -can’t be directly assigned to after being dereferenced. +In Chapter 4, in “Dangling References”, we +mentioned that the compiler ensures references are always valid. Unsafe Rust has +two new types called *raw pointers* that are similar to references. As with +references, raw pointers can be immutable or mutable and are written as `*const T` and `*mut T`, respectively. The asterisk isn’t the dereference operator; it’s +part of the type name. In the context of raw pointers, *immutable* means that +the pointer can’t be directly assigned to after being dereferenced. Different from references and smart pointers, raw pointers: * Are allowed to ignore the borrowing rules by having both immutable and -mutable pointers or multiple mutable pointers to the same location + mutable pointers or multiple mutable pointers to the same location * Aren’t guaranteed to point to valid memory * Are allowed to be null * Don’t implement any automatic cleanup @@ -118,66 +117,68 @@ By opting out of having Rust enforce these guarantees, you can give up guaranteed safety in exchange for greater performance or the ability to interface with another language or hardware where Rust’s guarantees don’t apply. -Listing 19-1 shows how to create an immutable and a mutable raw pointer from -references. +Listing 20-1 shows how to create an immutable and a mutable raw pointer. + ``` -let mut num = 5; + let mut num = 5; -let r1 = &num as *const i32; -let r2 = &mut num as *mut i32; + let r1 = &raw const num; + let r2 = &raw mut num; ``` -Listing 19-1: Creating raw pointers from references +Listing 20-1: Creating raw pointers with the raw borrow operators Notice that we don’t include the `unsafe` keyword in this code. We can create raw pointers in safe code; we just can’t dereference raw pointers outside an unsafe block, as you’ll see in a bit. -We’ve created raw pointers by using `as` to cast an immutable and a mutable -reference into their corresponding raw pointer types. Because we created them -directly from references guaranteed to be valid, we know these particular raw -pointers are valid, but we can’t make that assumption about just any raw -pointer. +We’ve created raw pointers by using the raw borrow operators: `&raw const num` +creates a `*const i32` immutable raw pointer, and `&raw mut num` creates a `*mut i32` mutable raw pointer. Because we created them directly from a local +variable, we know these particular raw pointers are valid, but we can’t make +that assumption about just any raw pointer. To demonstrate this, next we’ll create a raw pointer whose validity we can’t be -so certain of. Listing 19-2 shows how to create a raw pointer to an arbitrary +so certain of, using `as` to cast a value instead of using the raw reference +operators. Listing 20-2 shows how to create a raw pointer to an arbitrary location in memory. Trying to use arbitrary memory is undefined: there might be -data at that address or there might not, the compiler might optimize the code -so there is no memory access, or the program might terminate with a -segmentation fault. Usually, there is no good reason to write code like this, -but it is possible. +data at that address or there might not, the compiler might optimize the code so +there is no memory access, or the program might error with a segmentation fault. +Usually, there is no good reason to write code like this, especially in cases +where you can use a raw borrow operator instead, but it is possible. + ``` -let address = 0x012345usize; -let r = address as *const i32; + let address = 0x012345usize; + let r = address as *const i32; ``` -Listing 19-2: Creating a raw pointer to an arbitrary memory address +Listing 20-2: Creating a raw pointer to an arbitrary memory address Recall that we can create raw pointers in safe code, but we can’t *dereference* -raw pointers and read the data being pointed to. In Listing 19-3, we use the +raw pointers and read the data being pointed to. In Listing 20-3, we use the dereference operator `*` on a raw pointer that requires an `unsafe` block. + ``` -let mut num = 5; + let mut num = 5; -let r1 = &num as *const i32; -let r2 = &mut num as *mut i32; + let r1 = &raw const num; + let r2 = &raw mut num; -unsafe { - println!("r1 is: {}", *r1); - println!("r2 is: {}", *r2); -} + unsafe { + println!("r1 is: {}", *r1); + println!("r2 is: {}", *r2); + } ``` -Listing 19-3: Dereferencing raw pointers within an `unsafe` block +Listing 20-3: Dereferencing raw pointers within an `unsafe` block Creating a pointer does no harm; it’s only when we try to access the value that it points at that we might end up dealing with an invalid value. -Note also that in Listings 19-1 and 19-3, we created `*const i32` and `*mut -i32` raw pointers that both pointed to the same memory location, where `num` is +Note also that in Listing 20-1 and 20-3, we created `*const i32` and `*mut i32` +raw pointers that both pointed to the same memory location, where `num` is stored. If we instead tried to create an immutable and a mutable reference to `num`, the code would not have compiled because Rust’s ownership rules don’t allow a mutable reference at the same time as any immutable references. With @@ -186,11 +187,12 @@ same location and change data through the mutable pointer, potentially creating a data race. Be careful! With all of these dangers, why would you ever use raw pointers? One major use -case is when interfacing with C code, as you’ll see in “Calling an Unsafe -Function or Method” on page XX. Another case is when building up safe -abstractions that the borrow checker doesn’t understand. We’ll introduce unsafe -functions and then look at an example of a safe abstraction that uses unsafe -code. +case is when interfacing with C code, as you’ll see in the next section, +“Calling an Unsafe Function or +Method.” Another case is +when building up safe abstractions that the borrow checker doesn’t understand. +We’ll introduce unsafe functions and then look at an example of a safe +abstraction that uses unsafe code. ### Calling an Unsafe Function or Method @@ -201,41 +203,46 @@ definition. The `unsafe` keyword in this context indicates the function has requirements we need to uphold when we call this function, because Rust can’t guarantee we’ve met these requirements. By calling an unsafe function within an `unsafe` block, we’re saying that we’ve read this function’s documentation and -we take responsibility for upholding the function’s contracts. +take responsibility for upholding the function’s contracts. Here is an unsafe function named `dangerous` that doesn’t do anything in its body: ``` -unsafe fn dangerous() {} + unsafe fn dangerous() {} -unsafe { - dangerous(); -} + unsafe { + dangerous(); + } ``` We must call the `dangerous` function within a separate `unsafe` block. If we try to call `dangerous` without the `unsafe` block, we’ll get an error: ``` -error[E0133]: call to unsafe function is unsafe and requires -unsafe function or block +$ cargo run + Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) +error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe function or block --> src/main.rs:4:5 | 4 | dangerous(); | ^^^^^^^^^^^ call to unsafe function | - = note: consult the function's documentation for information on -how to avoid undefined behavior + = note: consult the function's documentation for information on how to avoid undefined behavior + +For more information about this error, try `rustc --explain E0133`. +error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error ``` With the `unsafe` block, we’re asserting to Rust that we’ve read the function’s documentation, we understand how to use it properly, and we’ve verified that we’re fulfilling the contract of the function. -Bodies of unsafe functions are effectively `unsafe` blocks, so to perform other -unsafe operations within an unsafe function, we don’t need to add another -`unsafe` block. +To perform unsafe operations in the body of an unsafe function, you still need +to use an `unsafe` block just as within a regular function, and the compiler +will warn you if you forget. This helps to keep `unsafe` blocks as small as +possible, as unsafe operations may not be needed across the whole function +body. #### Creating a Safe Abstraction over Unsafe Code @@ -245,31 +252,30 @@ a common abstraction. As an example, let’s study the `split_at_mut` function from the standard library, which requires some unsafe code. We’ll explore how we might implement it. This safe method is defined on mutable slices: it takes one slice and makes it two by splitting the slice at the index given as an -argument. Listing 19-4 shows how to use `split_at_mut`. +argument. Listing 20-4 shows how to use `split_at_mut`. + ``` -let mut v = vec![1, 2, 3, 4, 5, 6]; + let mut v = vec![1, 2, 3, 4, 5, 6]; -let r = &mut v[..]; + let r = &mut v[..]; -let (a, b) = r.split_at_mut(3); + let (a, b) = r.split_at_mut(3); -assert_eq!(a, &mut [1, 2, 3]); -assert_eq!(b, &mut [4, 5, 6]); + assert_eq!(a, &mut [1, 2, 3]); + assert_eq!(b, &mut [4, 5, 6]); ``` -Listing 19-4: Using the safe `split_at_mut` function +Listing 20-4: Using the safe `split_at_mut` function We can’t implement this function using only safe Rust. An attempt might look -something like Listing 19-5, which won’t compile. For simplicity, we’ll +something like Listing 20-5, which won’t compile. For simplicity, we’ll implement `split_at_mut` as a function rather than a method and only for slices of `i32` values rather than for a generic type `T`. + ``` -fn split_at_mut( - values: &mut [i32], - mid: usize, -) -> (&mut [i32], &mut [i32]) { +fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = values.len(); assert!(mid <= len); @@ -278,7 +284,7 @@ fn split_at_mut( } ``` -Listing 19-5: An attempted implementation of `split_at_mut` using only safe Rust +Listing 20-5: An attempted implementation of `split_at_mut` using only safe Rust This function first gets the total length of the slice. Then it asserts that the index given as a parameter is within the slice by checking whether it’s @@ -290,21 +296,28 @@ Then we return two mutable slices in a tuple: one from the start of the original slice to the `mid` index and another from `mid` to the end of the slice. -When we try to compile the code in Listing 19-5, we’ll get an error: +When we try to compile the code in Listing 20-5, we’ll get an error. ``` +$ cargo run + Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) error[E0499]: cannot borrow `*values` as mutable more than once at a time - --> src/main.rs:9:31 + --> src/main.rs:6:31 | -2 | values: &mut [i32], - | - let's call the lifetime of this reference `'1` +1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { + | - let's call the lifetime of this reference `'1` ... -9 | (&mut values[..mid], &mut values[mid..]) +6 | (&mut values[..mid], &mut values[mid..]) | --------------------------^^^^^^-------- | | | | | | | second mutable borrow occurs here | | first mutable borrow occurs here | returning this value requires that `*values` is borrowed for `'1` + | + = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices + +For more information about this error, try `rustc --explain E0499`. +error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error ``` Rust’s borrow checker can’t understand that we’re borrowing different parts of @@ -313,80 +326,76 @@ Borrowing different parts of a slice is fundamentally okay because the two slices aren’t overlapping, but Rust isn’t smart enough to know this. When we know code is okay, but Rust doesn’t, it’s time to reach for unsafe code. -Listing 19-6 shows how to use an `unsafe` block, a raw pointer, and some calls +Listing 20-6 shows how to use an `unsafe` block, a raw pointer, and some calls to unsafe functions to make the implementation of `split_at_mut` work. + ``` use std::slice; -fn split_at_mut( - values: &mut [i32], - mid: usize, -) -> (&mut [i32], &mut [i32]) { - 1 let len = values.len(); - 2 let ptr = values.as_mut_ptr(); +fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { + let len = values.len(); + let ptr = values.as_mut_ptr(); - 3 assert!(mid <= len); + assert!(mid <= len); - 4 unsafe { + unsafe { ( - 5 slice::from_raw_parts_mut(ptr, mid), - 6 slice::from_raw_parts_mut(ptr.add(mid), len - mid), + slice::from_raw_parts_mut(ptr, mid), + slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } } ``` -Listing 19-6: Using unsafe code in the implementation of the `split_at_mut` -function +Listing 20-6: Using unsafe code in the implementation of the `split_at_mut` function -Recall from “The Slice Type” on page XX that a slice is a pointer to some data -and the length of the slice. We use the `len` method to get the length of a -slice [1] and the `as_mut_ptr` method to access the raw pointer of a slice [2]. -In this case, because we have a mutable slice to `i32` values, `as_mut_ptr` -returns a raw pointer with the type `*mut i32`, which we’ve stored in the -variable `ptr`. +Recall from “The Slice Type” in Chapter 4 that +slices are a pointer to some data and the length of the slice. We use the `len` +method to get the length of a slice and the `as_mut_ptr` method to access the +raw pointer of a slice. In this case, because we have a mutable slice to `i32` +values, `as_mut_ptr` returns a raw pointer with the type `*mut i32`, which we’ve +stored in the variable `ptr`. -We keep the assertion that the `mid` index is within the slice [3]. Then we get -to the unsafe code [4]: the `slice::from_raw_parts_mut` function takes a raw -pointer and a length, and it creates a slice. We use it to create a slice that -starts from `ptr` and is `mid` items long [5]. Then we call the `add` method on -`ptr` with `mid` as an argument to get a raw pointer that starts at `mid`, and -we create a slice using that pointer and the remaining number of items after -`mid` as the length [6]. +We keep the assertion that the `mid` index is within the slice. Then we get to +the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer +and a length, and it creates a slice. We use this function to create a slice +that starts from `ptr` and is `mid` items long. Then we call the `add` +method on `ptr` with `mid` as an argument to get a raw pointer that starts at +`mid`, and we create a slice using that pointer and the remaining number of +items after `mid` as the length. The function `slice::from_raw_parts_mut` is unsafe because it takes a raw pointer and must trust that this pointer is valid. The `add` method on raw -pointers is also unsafe because it must trust that the offset location is also +pointers is also unsafe, because it must trust that the offset location is also a valid pointer. Therefore, we had to put an `unsafe` block around our calls to -`slice::from_raw_parts_mut` and `add` so we could call them. By looking at the -code and by adding the assertion that `mid` must be less than or equal to +`slice::from_raw_parts_mut` and `add` so we could call them. By looking at +the code and by adding the assertion that `mid` must be less than or equal to `len`, we can tell that all the raw pointers used within the `unsafe` block will be valid pointers to data within the slice. This is an acceptable and appropriate use of `unsafe`. -Note that we don’t need to mark the resultant `split_at_mut` function as +Note that we don’t need to mark the resulting `split_at_mut` function as `unsafe`, and we can call this function from safe Rust. We’ve created a safe abstraction to the unsafe code with an implementation of the function that uses `unsafe` code in a safe way, because it creates only valid pointers from the data this function has access to. -In contrast, the use of `slice::from_raw_parts_mut` in Listing 19-7 would +In contrast, the use of `slice::from_raw_parts_mut` in Listing 20-7 would likely crash when the slice is used. This code takes an arbitrary memory location and creates a slice 10,000 items long. + ``` -use std::slice; + use std::slice; -let address = 0x01234usize; -let r = address as *mut i32; + let address = 0x01234usize; + let r = address as *mut i32; -let values: &[i32] = unsafe { - slice::from_raw_parts_mut(r, 10000) -}; + let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) }; ``` -Listing 19-7: Creating a slice from an arbitrary memory location +Listing 20-7: Creating a slice from an arbitrary memory location We don’t own the memory at this arbitrary location, and there is no guarantee that the slice this code creates contains valid `i32` values. Attempting to use @@ -394,92 +403,118 @@ that the slice this code creates contains valid `i32` values. Attempting to use #### Using extern Functions to Call External Code -Sometimes your Rust code might need to interact with code written in another +Sometimes, your Rust code might need to interact with code written in another language. For this, Rust has the keyword `extern` that facilitates the creation -and use of a *Foreign Function Interface* *(FFI)*, which is a way for a +and use of a *Foreign Function Interface (FFI)*. An FFI is a way for a programming language to define functions and enable a different (foreign) programming language to call those functions. -Listing 19-8 demonstrates how to set up an integration with the `abs` function +Listing 20-8 demonstrates how to set up an integration with the `abs` function from the C standard library. Functions declared within `extern` blocks are -always unsafe to call from Rust code. The reason is that other languages don’t -enforce Rust’s rules and guarantees, and Rust can’t check them, so -responsibility falls on the programmer to ensure safety. +usually unsafe to call from Rust code, so they must also be marked `unsafe`. The +reason is that other languages don’t enforce Rust’s rules and guarantees, and +Rust can’t check them, so responsibility falls on the programmer to ensure +safety. -Filename: src/main.rs +src/main.rs ``` -extern "C" { +unsafe extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { - println!( - "Absolute value of -3 according to C: {}", - abs(-3) - ); + println!("Absolute value of -3 according to C: {}", abs(-3)); } } ``` -Listing 19-8: Declaring and calling an `extern` function defined in another -language +Listing 20-8: Declaring and calling an `extern` function defined in another language -Within the `extern "C"` block, we list the names and signatures of external -functions from another language we want to call. The `"C"` part defines which -*application binary interface* *(ABI)* the external function uses: the ABI +Within the `unsafe extern "C"` block, we list the names and signatures of +external functions from another language we want to call. The `"C"` part defines +which *application binary interface (ABI)* the external function uses: the ABI defines how to call the function at the assembly level. The `"C"` ABI is the most common and follows the C programming language’s ABI. -> ### Calling Rust Functions from Other Languages -> -> We can also use `extern` to create an interface that allows other languages -to call Rust functions. Instead of creating a whole `extern` block, we add the -`extern` keyword and specify the ABI to use just before the `fn` keyword for -the relevant function. We also need to add a `#[no_mangle]` annotation to tell -the Rust compiler not to mangle the name of this function. *Mangling* is when a -compiler changes the name we’ve given a function to a different name that -contains more information for other parts of the compilation process to consume -but is less human readable. Every programming language compiler mangles names -slightly differently, so for a Rust function to be nameable by other languages, -we must disable the Rust compiler’s name mangling. -> +This particular function does not have any memory safety considerations, though. +In fact, we know that any call to `abs` will always be safe for any `i32`, so we +can use the `safe` keyword to say that this specific function is safe to call +even though it is in an `unsafe extern` block. Once we make that change, calling +it no longer requires an `unsafe` block, as shown in Listing 20-9. + +src/main.rs + +``` +unsafe extern "C" { + safe fn abs(input: i32) -> i32; +} + +fn main() { + println!("Absolute value of -3 according to C: {}", abs(-3)); +} +``` + +Listing 20-9: Explicitly marking a function as `safe` within an `unsafe extern` block and calling it safely + +Marking a function as `safe` does not inherently make it safe! Instead, it is +like a promise you are making to Rust that it *is* safe. It is still your +responsibility to make sure that promise is kept! + +> #### Calling Rust Functions from Other Languages +> +> We can also use `extern` to create an interface that allows other languages to +> call Rust functions. Instead of creating a whole `extern` block, we add the +> `extern` keyword and specify the ABI to use just before the `fn` keyword for +> the relevant function. We also need to add a `#[unsafe(no_mangle)]` annotation +> to tell the Rust compiler not to mangle the name of this function. *Mangling* +> is when a compiler changes the name we’ve given a function to a different name +> that contains more information for other parts of the compilation process to +> consume but is less human readable. Every programming language compiler +> mangles names slightly differently, so for a Rust function to be nameable by +> other languages, we must disable the Rust compiler’s name mangling. This is +> unsafe because there might be name collisions across libraries without the +> built-in mangling, so it is our responsibility to make sure the name we have +> exported is safe to export without mangling. +> > In the following example, we make the `call_from_c` function accessible from -C code, after it’s compiled to a shared library and linked from C: -> -> ``` -> #[no_mangle] +> C code, after it’s compiled to a shared library and linked from C: +> +> ````rust +> #[unsafe(no_mangle)] > pub extern "C" fn call_from_c() { > println!("Just called a Rust function from C!"); > } -> ``` -> +> ```` +> > This usage of `extern` does not require `unsafe`. ### Accessing or Modifying a Mutable Static Variable -In this book, we’ve not yet talked about global variables, which Rust does +In this book, we’ve not yet talked about *global variables*, which Rust does support but can be problematic with Rust’s ownership rules. If two threads are accessing the same mutable global variable, it can cause a data race. -In Rust, global variables are called *static* variables. Listing 19-9 shows an -example declaration and use of a static variable with a string slice as a value. +In Rust, global variables are called *static* variables. Listing 20-10 shows an +example declaration and use of a static variable with a string slice as a +value. -Filename: src/main.rs +src/main.rs ``` static HELLO_WORLD: &str = "Hello, world!"; fn main() { - println!("value is: {HELLO_WORLD}"); + println!("name is: {HELLO_WORLD}"); } ``` -Listing 19-9: Defining and using an immutable static variable +Listing 20-10: Defining and using an immutable static variable -Static variables are similar to constants, which we discussed in “Constants” on -page XX. The names of static variables are in `SCREAMING_SNAKE_CASE` by +Static variables are similar to constants, which we discussed in +“Constants” in +Chapter 3. The names of static variables are in `SCREAMING_SNAKE_CASE` by convention. Static variables can only store references with the `'static` lifetime, which means the Rust compiler can figure out the lifetime and we aren’t required to annotate it explicitly. Accessing an immutable static @@ -490,42 +525,61 @@ values in a static variable have a fixed address in memory. Using the value will always access the same data. Constants, on the other hand, are allowed to duplicate their data whenever they’re used. Another difference is that static variables can be mutable. Accessing and modifying mutable static variables is -*unsafe*. Listing 19-10 shows how to declare, access, and modify a mutable +*unsafe*. Listing 20-11 shows how to declare, access, and modify a mutable static variable named `COUNTER`. -Filename: src/main.rs +src/main.rs ``` static mut COUNTER: u32 = 0; -fn add_to_count(inc: u32) { +/// SAFETY: Calling this from more than a single thread at a time is undefined +/// behavior, so you *must* guarantee you only call it from a single thread at +/// a time. +unsafe fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { - add_to_count(3); - unsafe { - println!("COUNTER: {COUNTER}"); + // SAFETY: This is only called from a single thread in `main`. + add_to_count(3); + println!("COUNTER: {}", *(&raw const COUNTER)); } } ``` -Listing 19-10: Reading from or writing to a mutable static variable is unsafe. +Listing 20-11: Reading from or writing to a mutable static variable is unsafe As with regular variables, we specify mutability using the `mut` keyword. Any -code that reads or writes from `COUNTER` must be within an `unsafe` block. This -code compiles and prints `COUNTER: 3` as we would expect because it’s single -threaded. Having multiple threads access `COUNTER` would likely result in data -races. +code that reads or writes from `COUNTER` must be within an `unsafe` block. The +code in Listing 20-11 compiles and prints `COUNTER: 3` as we would expect +because it’s single threaded. Having multiple threads access `COUNTER` would +likely result in data races, so it is undefined behavior. Therefore, we need to +mark the entire function as `unsafe`, and document the safety limitation, so +anyone calling the function knows what they are and are not allowed to do +safely. + +Whenever we write an unsafe function, it is idiomatic to write a comment +starting with `SAFETY` and explaining what the caller needs to do to call the +function safely. Likewise, whenever we perform an unsafe operation, it is +idiomatic to write a comment starting with `SAFETY` to explain how the safety +rules are upheld. + +Additionally, the compiler will not allow you to create references to a mutable +static variable. You can only access it via a raw pointer, created with one of +the raw borrow operators. That includes in cases where the reference is created +invisibly, as when it is used in the `println!` in this code listing. The +requirement that references to static mutable variables can only be created via +raw pointers helps make the safety requirements for using them more obvious. With mutable data that is globally accessible, it’s difficult to ensure there are no data races, which is why Rust considers mutable static variables to be unsafe. Where possible, it’s preferable to use the concurrency techniques and thread-safe smart pointers we discussed in Chapter 16 so the compiler checks -that data access from different threads is done safely. +that data accessed from different threads is done safely. ### Implementing an Unsafe Trait @@ -533,7 +587,8 @@ We can use `unsafe` to implement an unsafe trait. A trait is unsafe when at least one of its methods has some invariant that the compiler can’t verify. We declare that a trait is `unsafe` by adding the `unsafe` keyword before `trait` and marking the implementation of the trait as `unsafe` too, as shown in -Listing 19-11. +Listing 20-12. + ``` unsafe trait Foo { @@ -543,48 +598,109 @@ unsafe trait Foo { unsafe impl Foo for i32 { // method implementations go here } + +fn main() {} ``` -Listing 19-11: Defining and implementing an unsafe trait +Listing 20-12: Defining and implementing an unsafe trait By using `unsafe impl`, we’re promising that we’ll uphold the invariants that the compiler can’t verify. -As an example, recall the `Send` and `Sync` marker traits we discussed in -“Extensible Concurrency with the Send and Sync Traits” on page XX: the compiler -implements these traits automatically if our types are composed entirely of -`Send` and `Sync` types. If we implement a type that contains a type that is -not `Send` or `Sync`, such as raw pointers, and we want to mark that type as -`Send` or `Sync`, we must use `unsafe`. Rust can’t verify that our type upholds -the guarantees that it can be safely sent across threads or accessed from -multiple threads; therefore, we need to do those checks manually and indicate -as such with `unsafe`. +As an example, recall the `Sync` and `Send` marker traits we discussed in +“Extensible Concurrency with the `Sync` and `Send` +Traits” in +Chapter 16: the compiler implements these traits automatically if our types are +composed entirely of `Send` and `Sync` types. If we implement a type that +contains a type that is not `Send` or `Sync`, such as raw pointers, and we want +to mark that type as `Send` or `Sync`, we must use `unsafe`. Rust can’t verify +that our type upholds the guarantees that it can be safely sent across threads +or accessed from multiple threads; therefore, we need to do those checks +manually and indicate as such with `unsafe`. ### Accessing Fields of a Union -The final action that works only with `unsafe` is accessing fields of a union. -A `union` is similar to a `struct`, but only one declared field is used in a -particular instance at one time. Unions are primarily used to interface with -unions in C code. Accessing union fields is unsafe because Rust can’t guarantee -the type of the data currently being stored in the union instance. You can -learn more about unions in the Rust Reference at -*https://doc.rust-lang.org/reference/items/unions.html**.* +The final action that works only with `unsafe` is accessing fields of a +*union*. A `union` is similar to a `struct`, but only one declared field is +used in a particular instance at one time. Unions are primarily used to +interface with unions in C code. Accessing union fields is unsafe because Rust +can’t guarantee the type of the data currently being stored in the union +instance. You can learn more about unions in the Rust Reference at *../reference/items/unions.html*. + +### Using Miri to check unsafe code + +When writing unsafe code, you might want to check that what you have written +actually is safe and correct. One of the best ways to do that is to use +Miri at *https://github.com/rust-lang/miri*, an official Rust tool for detecting undefined behavior. Whereas +the borrow checker is a *static* tool which works at compile time, Miri is a +*dynamic* tool which works at runtime. It checks your code by running your +program, or its test suite, and detecting when you violate the rules it +understands about how Rust should work. + +Using Miri requires a nightly build of Rust (which we talk about more in +Appendix G: How Rust is Made and “Nightly Rust” at *appendix-07-nightly-rust.html*). You can install +both a nightly version of Rust and the Miri tool by typing `rustup +nightly component add miri`. This does not change what version of Rust your project +uses; it only adds the tool to your system so you can use it when you want to. +You can run Miri on a project by typing `cargo +nightly miri run` or `cargo +nightly miri test`. + +For an example of how helpful this can be, consider what happens when we run it +against Listing 20-11: + +``` +$ cargo +nightly miri run + Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s + Running `/Users/chris/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/unsafe-example` +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:14:33 + | +14 | println!("COUNTER: {}", COUNTER); + | ^^^^^^^ shared reference to mutable static + | + = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html> + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + = note: `#[warn(static_mut_refs)]` on by default + +COUNTER: 3 +``` + +It helpfully and correctly notices that we have shared references to mutable +data, and warns about it. In this case, it does not tell us how to fix the +problem, but it means that we know there is a possible issue and can think about +how to make sure it is safe. In other cases, it can actually tell us that some +code is *sure* to be wrong and make recommendations about how to fix it. + +Miri doesn’t catch *everything* you might get wrong when writing unsafe code. +For one thing, since it is a dynamic check, it only catches problems with code +that actually gets run. That means you will need to use it in conjunction with +good testing techniques to increase your confidence about the unsafe code you +have written. For another thing, it does not cover every possible way your code +can be unsound. If Miri *does* catch a problem, you know there’s a bug, but just +because Miri *doesn’t* catch a bug doesn’t mean there isn’t a problem. Miri can +catch a lot, though. Try running it on the other examples of unsafe code in this +chapter and see what it says! ### When to Use Unsafe Code -Using `unsafe` to use one of the five superpowers just discussed isn’t wrong or -even frowned upon, but it is trickier to get `unsafe` code correct because the -compiler can’t help uphold memory safety. When you have a reason to use -`unsafe` code, you can do so, and having the explicit `unsafe` annotation makes -it easier to track down the source of problems when they occur. +Using `unsafe` to take one of the five actions (superpowers) just discussed +isn’t wrong or even frowned upon. But it is trickier to get `unsafe` code +correct because the compiler can’t help uphold memory safety. When you have a +reason to use `unsafe` code, you can do so, and having the explicit `unsafe` +annotation makes it easier to track down the source of problems when they occur. +Whenever you write unsafe code, you can use Miri to help you be more confident +that the code you have written upholds Rust’s rules. + +For a much deeper exploration of how to work effectively with unsafe Rust, read +Rust’s official guide to the subject, the Rustonomicon at *https://doc.rust-lang.org/nomicon/*. ## Advanced Traits -We first covered traits in “Traits: Defining Shared Behavior” on page XX, but -we didn’t discuss the more advanced details. Now that you know more about Rust, -we can get into the nitty-gritty. +We first covered traits in “Traits: Defining Shared +Behavior” in Chapter 10, but we +didn’t discuss the more advanced details. Now that you know more about Rust, we +can get into the nitty-gritty. -### Associated Types +### Specifying Placeholder Types in Trait Definitions with Associated Types *Associated types* connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures. The @@ -602,7 +718,8 @@ One example of a trait with an associated type is the `Iterator` trait that the standard library provides. The associated type is named `Item` and stands in for the type of the values the type implementing the `Iterator` trait is iterating over. The definition of the `Iterator` trait is as shown in Listing -19-12. +20-13. + ``` pub trait Iterator { @@ -612,8 +729,7 @@ pub trait Iterator { } ``` -Listing 19-12: The definition of the `Iterator` trait that has an associated -type `Item` +Listing 20-13: The definition of the `Iterator` trait that has an associated type `Item` The type `Item` is a placeholder, and the `next` method’s definition shows that it will return values of type `Option<Self::Item>`. Implementors of the @@ -626,18 +742,21 @@ handle. To examine the difference between the two concepts, we’ll look at an implementation of the `Iterator` trait on a type named `Counter` that specifies the `Item` type is `u32`: -Filename: src/lib.rs +src/lib.rs ``` impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { - --snip-- + // --snip-- ``` + + This syntax seems comparable to that of generics. So why not just define the -`Iterator` trait with generics, as shown in Listing 19-13? +`Iterator` trait with generics, as shown in Listing 20-14? + ``` pub trait Iterator<T> { @@ -645,11 +764,11 @@ pub trait Iterator<T> { } ``` -Listing 19-13: A hypothetical definition of the `Iterator` trait using generics +Listing 20-14: A hypothetical definition of the `Iterator` trait using generics -The difference is that when using generics, as in Listing 19-13, we must +The difference is that when using generics, as in Listing 20-14, we must annotate the types in each implementation; because we can also implement -`Iterator<``String``> for Counter` or any other type, we could have multiple +`Iterator<String> for Counter` or any other type, we could have multiple implementations of `Iterator` for `Counter`. In other words, when a trait has a generic parameter, it can be implemented for a type multiple times, changing the concrete types of the generic type parameters each time. When we use the @@ -657,24 +776,23 @@ the concrete types of the generic type parameters each time. When we use the indicate which implementation of `Iterator` we want to use. With associated types, we don’t need to annotate types because we can’t -implement a trait on a type multiple times. In Listing 19-12 with the -definition that uses associated types, we can choose what the type of `Item` -will be only once because there can be only one `impl Iterator for Counter`. We -don’t have to specify that we want an iterator of `u32` values everywhere we -call `next` on `Counter`. +implement a trait on a type multiple times. In Listing 20-13 with the +definition that uses associated types, we can only choose what the type of +`Item` will be once, because there can only be one `impl Iterator for Counter`. +We don’t have to specify that we want an iterator of `u32` values everywhere +that we call `next` on `Counter`. Associated types also become part of the trait’s contract: implementors of the trait must provide a type to stand in for the associated type placeholder. Associated types often have a name that describes how the type will be used, -and documenting the associated type in the API documentation is a good practice. +and documenting the associated type in the API documentation is good practice. ### Default Generic Type Parameters and Operator Overloading When we use generic type parameters, we can specify a default concrete type for the generic type. This eliminates the need for implementors of the trait to specify a concrete type if the default type works. You specify a default type -when declaring a generic type with the `<`PlaceholderType`=`ConcreteType`>` -syntax. +when declaring a generic type with the `<PlaceholderType=ConcreteType>` syntax. A great example of a situation where this technique is useful is with *operator overloading*, in which you customize the behavior of an operator (such as `+`) @@ -683,11 +801,11 @@ in particular situations. Rust doesn’t allow you to create your own operators or overload arbitrary operators. But you can overload the operations and corresponding traits listed in `std::ops` by implementing the traits associated with the operator. For -example, in Listing 19-14 we overload the `+` operator to add two `Point` +example, in Listing 20-15 we overload the `+` operator to add two `Point` instances together. We do this by implementing the `Add` trait on a `Point` -struct. +struct: -Filename: src/main.rs +src/main.rs ``` use std::ops::Add; @@ -717,8 +835,7 @@ fn main() { } ``` -Listing 19-14: Implementing the `Add` trait to overload the `+` operator for -`Point` instances +Listing 20-15: Implementing the `Add` trait to overload the `+` operator for `Point` instances The `add` method adds the `x` values of two `Point` instances and the `y` values of two `Point` instances to create a new `Point`. The `Add` trait has an @@ -738,7 +855,7 @@ trait Add<Rhs=Self> { This code should look generally familiar: a trait with one method and an associated type. The new part is `Rhs=Self`: this syntax is called *default -type parameters*. The `Rhs` generic type parameter (short for “right-hand +type parameters*. The `Rhs` generic type parameter (short for “right hand side”) defines the type of the `rhs` parameter in the `add` method. If we don’t specify a concrete type for `Rhs` when we implement the `Add` trait, the type of `Rhs` will default to `Self`, which will be the type we’re implementing @@ -751,13 +868,12 @@ default. We have two structs, `Millimeters` and `Meters`, holding values in different units. This thin wrapping of an existing type in another struct is known as the -*newtype pattern*, which we describe in more detail in “Using the Newtype -Pattern to Implement External Traits on External Types” on page XX. We want to -add values in millimeters to values in meters and have the implementation of -`Add` do the conversion correctly. We can implement `Add` for `Millimeters` -with `Meters` as the `Rhs`, as shown in Listing 19-15. +*newtype pattern*, which we describe in more detail in the “Using the Newtype +Pattern to Implement External Traits on External Types” section. We want to add values in millimeters to values in meters and have +the implementation of `Add` do the conversion correctly. We can implement `Add` +for `Millimeters` with `Meters` as the `Rhs`, as shown in Listing 20-16. -Filename: src/lib.rs +src/lib.rs ``` use std::ops::Add; @@ -774,8 +890,7 @@ impl Add<Meters> for Millimeters { } ``` -Listing 19-15: Implementing the `Add` trait on `Millimeters` to add -`Millimeters` and `Meters` +Listing 20-16: Implementing the `Add` trait on `Millimeters` to add `Millimeters` to `Meters` To add `Millimeters` and `Meters`, we specify `impl Add<Meters>` to set the value of the `Rhs` type parameter instead of using the default of `Self`. @@ -797,7 +912,7 @@ type parameter to an existing trait, you can give it a default to allow extension of the functionality of the trait without breaking the existing implementation code. -### Disambiguating Between Methods with the Same Name +### Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name Nothing in Rust prevents a trait from having a method with the same name as another trait’s method, nor does Rust prevent you from implementing both traits @@ -805,12 +920,12 @@ on one type. It’s also possible to implement a method directly on the type wit the same name as methods from traits. When calling methods with the same name, you’ll need to tell Rust which one you -want to use. Consider the code in Listing 19-16 where we’ve defined two traits, +want to use. Consider the code in Listing 20-17 where we’ve defined two traits, `Pilot` and `Wizard`, that both have a method called `fly`. We then implement both traits on a type `Human` that already has a method named `fly` implemented on it. Each `fly` method does something different. -Filename: src/main.rs +src/main.rs ``` trait Pilot { @@ -842,14 +957,12 @@ impl Human { } ``` -Listing 19-16: Two traits are defined to have a `fly` method and are -implemented on the `Human` type, and a `fly` method is implemented on `Human` -directly. +Listing 20-17: Two traits are defined to have a ` method and are implemented on the `Human` type, and a `fly` method is implemented on `Human` directly When we call `fly` on an instance of `Human`, the compiler defaults to calling -the method that is directly implemented on the type, as shown in Listing 19-17. +the method that is directly implemented on the type, as shown in Listing 20-18. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -858,16 +971,16 @@ fn main() { } ``` -Listing 19-17: Calling `fly` on an instance of `Human` +Listing 20-18: Calling `fly` on an instance of `Human` Running this code will print `*waving arms furiously*`, showing that Rust called the `fly` method implemented on `Human` directly. To call the `fly` methods from either the `Pilot` trait or the `Wizard` trait, we need to use more explicit syntax to specify which `fly` method we mean. -Listing 19-18 demonstrates this syntax. +Listing 20-19 demonstrates this syntax. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -878,17 +991,21 @@ fn main() { } ``` -Listing 19-18: Specifying which trait’s `fly` method we want to call +Listing 20-19: Specifying which trait’s `fly` method we want to call Specifying the trait name before the method name clarifies to Rust which implementation of `fly` we want to call. We could also write `Human::fly(&person)`, which is equivalent to the `person.fly()` that we used -in Listing 19-18, but this is a bit longer to write if we don’t need to +in Listing 20-19, but this is a bit longer to write if we don’t need to disambiguate. Running this code prints the following: ``` +$ cargo run + Compiling traits-example v0.1.0 (file:///projects/traits-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s + Running `target/debug/traits-example` This is your captain speaking. Up! *waving arms furiously* @@ -901,13 +1018,13 @@ trait to use based on the type of `self`. However, associated functions that are not methods don’t have a `self` parameter. When there are multiple types or traits that define non-method functions with the same function name, Rust doesn’t always know which type you -mean unless you use fully qualified syntax. For example, in Listing 19-19 we -create a trait for an animal shelter that wants to name all baby dogs Spot. We -make an `Animal` trait with an associated non-method function `baby_name`. The -`Animal` trait is implemented for the struct `Dog`, on which we also provide an -associated non-method function `baby_name` directly. +mean unless you use *fully qualified syntax*. For example, in Listing 20-20 we +create a trait for an animal shelter that wants to name all baby dogs *Spot*. +We make an `Animal` trait with an associated non-method function `baby_name`. +The `Animal` trait is implemented for the struct `Dog`, on which we also +provide an associated non-method function `baby_name` directly. -Filename: src/main.rs +src/main.rs ``` trait Animal { @@ -933,8 +1050,7 @@ fn main() { } ``` -Listing 19-19: A trait with an associated function and a type with an -associated function of the same name that also implements the trait +Listing 20-20: A trait with an associated function and a type with an associated function of the same name that also implements the trait We implement the code for naming all puppies Spot in the `baby_name` associated function that is defined on `Dog`. The `Dog` type also implements the trait @@ -946,16 +1062,20 @@ In `main`, we call the `Dog::baby_name` function, which calls the associated function defined on `Dog` directly. This code prints the following: ``` +$ cargo run + Compiling traits-example v0.1.0 (file:///projects/traits-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s + Running `target/debug/traits-example` A baby dog is called a Spot ``` This output isn’t what we wanted. We want to call the `baby_name` function that is part of the `Animal` trait that we implemented on `Dog` so the code prints `A baby dog is called a puppy`. The technique of specifying the trait name that -we used in Listing 19-18 doesn’t help here; if we change `main` to the code in -Listing 19-20, we’ll get a compilation error. +we used in Listing 20-19 doesn’t help here; if we change `main` to the code in +Listing 20-21, we’ll get a compilation error. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -963,42 +1083,47 @@ fn main() { } ``` -Listing 19-20: Attempting to call the `baby_name` function from the `Animal` -trait, but Rust doesn’t know which implementation to use +Listing 20-21: Attempting to call the `baby_name` function from the `Animal` trait, but Rust doesn’t know which implementation to use Because `Animal::baby_name` doesn’t have a `self` parameter, and there could be other types that implement the `Animal` trait, Rust can’t figure out which implementation of `Animal::baby_name` we want. We’ll get this compiler error: ``` -error[E0283]: type annotations needed +$ cargo run + Compiling traits-example v0.1.0 (file:///projects/traits-example) +error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:20:43 | +2 | fn baby_name() -> String; + | ------------------------- `Animal::baby_name` defined here +... 20 | println!("A baby dog is called a {}", Animal::baby_name()); - | ^^^^^^^^^^^^^^^^^ cannot infer -type + | ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait + | +help: use the fully-qualified path to the only available implementation | - = note: cannot satisfy `_: Animal` +20 | println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); + | +++++++ + + +For more information about this error, try `rustc --explain E0790`. +error: could not compile `traits-example` (bin "traits-example") due to 1 previous error ``` To disambiguate and tell Rust that we want to use the implementation of `Animal` for `Dog` as opposed to the implementation of `Animal` for some other -type, we need to use fully qualified syntax. Listing 19-21 demonstrates how to +type, we need to use fully qualified syntax. Listing 20-22 demonstrates how to use fully qualified syntax. -Filename: src/main.rs +src/main.rs ``` fn main() { - println!( - "A baby dog is called a {}", - <Dog as Animal>::baby_name() - ); + println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); } ``` -Listing 19-21: Using fully qualified syntax to specify that we want to call the -`baby_name` function from the `Animal` trait as implemented on `Dog` +Listing 20-22: Using fully qualified syntax to specify that we want to call the `baby_name` function from the `Animal` trait as implemented on `Dog` We’re providing Rust with a type annotation within the angle brackets, which indicates we want to call the `baby_name` method from the `Animal` trait as @@ -1006,6 +1131,10 @@ implemented on `Dog` by saying that we want to treat the `Dog` type as an `Animal` for this function call. This code will now print what we want: ``` +$ cargo run + Compiling traits-example v0.1.0 (file:///projects/traits-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s + Running `target/debug/traits-example` A baby dog is called a puppy ``` @@ -1023,10 +1152,10 @@ in the program. You only need to use this more verbose syntax in cases where there are multiple implementations that use the same name and Rust needs help to identify which implementation you want to call. -### Using Supertraits +### Using Supertraits to Require One Trait’s Functionality Within Another Trait -Sometimes you might write a trait definition that depends on another trait: for -a type to implement the first trait, you want to require that type to also +Sometimes, you might write a trait definition that depends on another trait: +for a type to implement the first trait, you want to require that type to also implement the second trait. You would do this so that your trait definition can make use of the associated items of the second trait. The trait your trait definition is relying on is called a *supertrait* of your trait. @@ -1051,10 +1180,10 @@ In the implementation of the `outline_print` method, we want to use the `OutlinePrint` trait will work only for types that also implement `Display` and provide the functionality that `OutlinePrint` needs. We can do that in the trait definition by specifying `OutlinePrint: Display`. This technique is -similar to adding a trait bound to the trait. Listing 19-22 shows an +similar to adding a trait bound to the trait. Listing 20-23 shows an implementation of the `OutlinePrint` trait. -Filename: src/main.rs +src/main.rs ``` use std::fmt; @@ -1065,15 +1194,14 @@ trait OutlinePrint: fmt::Display { let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); - println!("* {} *", output); + println!("* {output} *"); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } } ``` -Listing 19-22: Implementing the `OutlinePrint` trait that requires the -functionality from `Display` +Listing 20-23: Implementing the `OutlinePrint` trait that requires the functionality from `Display` Because we’ve specified that `OutlinePrint` requires the `Display` trait, we can use the `to_string` function that is automatically implemented for any type @@ -1085,7 +1213,7 @@ the current scope. Let’s see what happens when we try to implement `OutlinePrint` on a type that doesn’t implement `Display`, such as the `Point` struct: -Filename: src/main.rs +src/main.rs ``` struct Point { @@ -1096,29 +1224,51 @@ struct Point { impl OutlinePrint for Point {} ``` + + We get an error saying that `Display` is required but not implemented: ``` +$ cargo run + Compiling traits-example v0.1.0 (file:///projects/traits-example) error[E0277]: `Point` doesn't implement `std::fmt::Display` - --> src/main.rs:20:6 + --> src/main.rs:20:23 | 20 | impl OutlinePrint for Point {} - | ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter + | ^^^^^ `Point` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `Point` - = note: in format strings you may be able to use `{:?}` (or {:#?} for -pretty-print) instead + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead note: required by a bound in `OutlinePrint` --> src/main.rs:3:21 | 3 | trait OutlinePrint: fmt::Display { | ^^^^^^^^^^^^ required by this bound in `OutlinePrint` + +error[E0277]: `Point` doesn't implement `std::fmt::Display` + --> src/main.rs:24:7 + | +24 | p.outline_print(); + | ^^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `Point` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead +note: required by a bound in `OutlinePrint::outline_print` + --> src/main.rs:3:21 + | +3 | trait OutlinePrint: fmt::Display { + | ^^^^^^^^^^^^ required by this bound in `OutlinePrint::outline_print` +4 | fn outline_print(&self) { + | ------------- required by a bound in this associated function + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `traits-example` (bin "traits-example") due to 2 previous errors ``` To fix this, we implement `Display` on `Point` and satisfy the constraint that `OutlinePrint` requires, like so: -Filename: src/main.rs +src/main.rs ``` use std::fmt; @@ -1130,31 +1280,33 @@ impl fmt::Display for Point { } ``` -Then, implementing the `OutlinePrint` trait on `Point` will compile + + +Then implementing the `OutlinePrint` trait on `Point` will compile successfully, and we can call `outline_print` on a `Point` instance to display it within an outline of asterisks. -### Using the Newtype Pattern to Implement External Traits +### Using the Newtype Pattern to Implement External Traits on External Types -In “Implementing a Trait on a Type” on page XX, we mentioned the orphan rule -that states we’re only allowed to implement a trait on a type if either the -trait or the type, or both, are local to our crate. It’s possible to get around -this restriction using the *newtype pattern*, which involves creating a new -type in a tuple struct. (We covered tuple structs in “Using Tuple Structs -Without Named Fields to Create Different Types” on page XX.) The tuple struct -will have one field and be a thin wrapper around the type for which we want to -implement a trait. Then the wrapper type is local to our crate, and we can -implement the trait on the wrapper. *Newtype* is a term that originates from -the Haskell programming language. There is no runtime performance penalty for -using this pattern, and the wrapper type is elided at compile time. +In “Implementing a Trait on a Type” in Chapter 10, we mentioned the orphan rule that states we’re only allowed +to implement a trait on a type if either the trait or the type are local to our +crate. It’s possible to get around this restriction using the *newtype pattern*, +which involves creating a new type in a tuple struct. (We covered tuple structs +in “Using Tuple Structs without Named Fields to Create Different +Types” in Chapter 5.) The tuple struct will have +one field and be a thin wrapper around the type we want to implement a trait +for. Then the wrapper type is local to our crate, and we can implement the trait +on the wrapper. *Newtype* is a term that originates from the Haskell programming +language. There is no runtime performance penalty for using this pattern, and +the wrapper type is elided at compile time. As an example, let’s say we want to implement `Display` on `Vec<T>`, which the orphan rule prevents us from doing directly because the `Display` trait and the `Vec<T>` type are defined outside our crate. We can make a `Wrapper` struct that holds an instance of `Vec<T>`; then we can implement `Display` on -`Wrapper` and use the `Vec<T>` value, as shown in Listing 19-23. +`Wrapper` and use the `Vec<T>` value, as shown in Listing 20-24. -Filename: src/main.rs +src/main.rs ``` use std::fmt; @@ -1168,29 +1320,25 @@ impl fmt::Display for Wrapper { } fn main() { - let w = Wrapper(vec![ - String::from("hello"), - String::from("world"), - ]); + let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {w}"); } ``` -Listing 19-23: Creating a `Wrapper` type around `Vec<String>` to implement -`Display` +Listing 20-24: Creating a `Wrapper` type around `Vec<String>` to implement `Display` -The implementation of `Display` uses `self.0` to access the inner `Vec<T>` +The implementation of `Display` uses `self.0` to access the inner `Vec<T>`, because `Wrapper` is a tuple struct and `Vec<T>` is the item at index 0 in the -tuple. Then we can use the functionality of the `Display` type on `Wrapper`. +tuple. Then we can use the functionality of the `Display` trait on `Wrapper`. The downside of using this technique is that `Wrapper` is a new type, so it doesn’t have the methods of the value it’s holding. We would have to implement -all the methods of `Vec<T>` directly on `Wrapper` such that the methods -delegate to `self.0`, which would allow us to treat `Wrapper` exactly like a -`Vec<T>`. If we wanted the new type to have every method the inner type has, -implementing the `Deref` trait on the `Wrapper` to return the inner type would -be a solution (we discussed implementing the `Deref` trait in “Treating Smart -Pointers Like Regular References with Deref” on page XX). If we didn’t want the +all the methods of `Vec<T>` directly on `Wrapper` such that the methods delegate +to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec<T>`. If +we wanted the new type to have every method the inner type has, implementing the +`Deref` trait (discussed in “Treating Smart Pointers Like Regular References +with the `Deref` Trait” in Chapter 15) on +the `Wrapper` to return the inner type would be a solution. If we don’t want the `Wrapper` type to have all the methods of the inner type—for example, to restrict the `Wrapper` type’s behavior—we would have to implement just the methods we do want manually. @@ -1208,17 +1356,14 @@ the `!` type and dynamically sized types. ### Using the Newtype Pattern for Type Safety and Abstraction -> Note: This section assumes you’ve read the earlier section “Using the Newtype -Pattern to Implement External Traits” on page XX. - -The newtype pattern is also useful for tasks beyond those we’ve discussed so -far, including statically enforcing that values are never confused and -indicating the units of a value. You saw an example of using newtypes to -indicate units in Listing 19-15: recall that the `Millimeters` and `Meters` +This section assumes you’ve read the earlier section “Using the Newtype Pattern +to Implement External Traits on External Types.” The newtype pattern is also useful for tasks beyond those we’ve +discussed so far, including statically enforcing that values are never confused +and indicating the units of a value. You saw an example of using newtypes to +indicate units in Listing 20-16: recall that the `Millimeters` and `Meters` structs wrapped `u32` values in a newtype. If we wrote a function with a -parameter of type `Millimeters`, we wouldn’t be able to compile a program that -accidentally tried to call that function with a value of type `Meters` or a -plain `u32`. +parameter of type `Millimeters`, we couldn’t compile a program that accidentally +tried to call that function with a value of type `Meters` or a plain `u32`. We can also use the newtype pattern to abstract away some implementation details of a type: the new type can expose a public API that is different from @@ -1229,9 +1374,9 @@ Newtypes can also hide internal implementation. For example, we could provide a associated with their name. Code using `People` would only interact with the public API we provide, such as a method to add a name string to the `People` collection; that code wouldn’t need to know that we assign an `i32` ID to names -internally. The newtype pattern is a lightweight way to achieve encapsulation -to hide implementation details, which we discussed in “Encapsulation That Hides -Implementation Details” on page XX. +internally. The newtype pattern is a lightweight way to achieve encapsulation to +hide implementation details, which we discussed in “Encapsulation that Hides +Implementation Details” in Chapter 18. ### Creating Type Synonyms with Type Aliases @@ -1240,26 +1385,26 @@ another name. For this we use the `type` keyword. For example, we can create the alias `Kilometers` to `i32` like so: ``` -type Kilometers = i32; + type Kilometers = i32; ``` -Now the alias `Kilometers` is a *synonym* for `i32`; unlike the `Millimeters` -and `Meters` types we created in Listing 19-15, `Kilometers` is not a separate, +Now, the alias `Kilometers` is a *synonym* for `i32`; unlike the `Millimeters` +and `Meters` types we created in Listing 20-16, `Kilometers` is not a separate, new type. Values that have the type `Kilometers` will be treated the same as values of type `i32`: ``` -type Kilometers = i32; + type Kilometers = i32; -let x: i32 = 5; -let y: Kilometers = 5; + let x: i32 = 5; + let y: Kilometers = 5; -println!("x + y = {}", x + y); + println!("x + y = {}", x + y); ``` Because `Kilometers` and `i32` are the same type, we can add values of both types and we can pass `Kilometers` values to functions that take `i32` -parameters. However, using this method, we don’t get the type-checking benefits +parameters. However, using this method, we don’t get the type checking benefits that we get from the newtype pattern discussed earlier. In other words, if we mix up `Kilometers` and `i32` values somewhere, the compiler will not give us an error. @@ -1273,43 +1418,43 @@ Box<dyn Fn() + Send + 'static> Writing this lengthy type in function signatures and as type annotations all over the code can be tiresome and error prone. Imagine having a project full of -code like that in Listing 19-24. +code like that in Listing 20-25. + ``` -let f: Box<dyn Fn() + Send + 'static> = Box::new(|| { - println!("hi"); -}); + let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi")); -fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) { - --snip-- -} + fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) { + // --snip-- + } -fn returns_long_type() -> Box<dyn Fn() + Send + 'static> { - --snip-- -} + fn returns_long_type() -> Box<dyn Fn() + Send + 'static> { + // --snip-- + } ``` -Listing 19-24: Using a long type in many places +Listing 20-25: Using a long type in many places A type alias makes this code more manageable by reducing the repetition. In -Listing 19-25, we’ve introduced an alias named `Thunk` for the verbose type and +Listing 20-26, we’ve introduced an alias named `Thunk` for the verbose type and can replace all uses of the type with the shorter alias `Thunk`. + ``` -type Thunk = Box<dyn Fn() + Send + 'static>; + type Thunk = Box<dyn Fn() + Send + 'static>; -let f: Thunk = Box::new(|| println!("hi")); + let f: Thunk = Box::new(|| println!("hi")); -fn takes_long_type(f: Thunk) { - --snip-- -} + fn takes_long_type(f: Thunk) { + // --snip-- + } -fn returns_long_type() -> Thunk { - --snip-- -} + fn returns_long_type() -> Thunk { + // --snip-- + } ``` -Listing 19-25: Introducing a type alias `Thunk` to reduce repetition +Listing 20-26: Introducing a type alias, `Thunk`, to reduce repetition This code is much easier to read and write! Choosing a meaningful name for a type alias can help communicate your intent as well (*thunk* is a word for code @@ -1333,10 +1478,7 @@ pub trait Write { fn flush(&mut self) -> Result<(), Error>; fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>; - fn write_fmt( - &mut self, - fmt: fmt::Arguments, - ) -> Result<(), Error>; + fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>; } ``` @@ -1367,7 +1509,7 @@ us a consistent interface across all of `std::io`. Because it’s an alias, it just another `Result<T, E>`, which means we can use any methods that work on `Result<T, E>` with it, as well as special syntax like the `?` operator. -### The Never Type That Never Returns +### The Never Type that Never Returns Rust has a special type named `!` that’s known in type theory lingo as the *empty type* because it has no values. We prefer to call it the *never type* @@ -1376,42 +1518,44 @@ return. Here is an example: ``` fn bar() -> ! { - --snip-- + // --snip-- } ``` This code is read as “the function `bar` returns never.” Functions that return -never are called *diverging functions*. We can’t create values of the type `!`, +never are called *diverging functions*. We can’t create values of the type `!` so `bar` can never possibly return. But what use is a type you can never create values for? Recall the code from -Listing 2-5, part of the number-guessing game; we’ve reproduced a bit of it -here in Listing 19-26. +Listing 2-5, part of the number guessing game; we’ve reproduced a bit of it +here in Listing 20-27. + ``` -let guess: u32 = match guess.trim().parse() { - Ok(num) => num, - Err(_) => continue, -}; + let guess: u32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; ``` -Listing 19-26: A `match` with an arm that ends in `continue` +Listing 20-27: A `match` with an arm that ends in `continue` -At the time, we skipped over some details in this code. In “The match Control -Flow Construct” on page XX, we discussed that `match` arms must all return the -same type. So, for example, the following code doesn’t work: +At the time, we skipped over some details in this code. In “The `match` Control +Flow Operator” in Chapter 6, we +discussed that `match` arms must all return the same type. So, for example, the +following code doesn’t work: ``` -let guess = match guess.trim().parse() { - Ok(_) => 5, - Err(_) => "hello", -}; + let guess = match guess.trim().parse() { + Ok(_) => 5, + Err(_) => "hello", + }; ``` The type of `guess` in this code would have to be an integer *and* a string, and Rust requires that `guess` have only one type. So what does `continue` return? How were we allowed to return a `u32` from one arm and have another arm -that ends with `continue` in Listing 19-26? +that ends with `continue` in Listing 20-27? As you might have guessed, `continue` has a `!` value. That is, when Rust computes the type of `guess`, it looks at both match arms, the former with a @@ -1433,15 +1577,13 @@ impl<T> Option<T> { pub fn unwrap(self) -> T { match self { Some(val) => val, - None => panic!( - "called `Option::unwrap()` on a `None` value" - ), + None => panic!("called `Option::unwrap()` on a `None` value"), } } } ``` -In this code, the same thing happens as in the `match` in Listing 19-26: Rust +In this code, the same thing happens as in the `match` in Listing 20-27: Rust sees that `val` has the type `T` and `panic!` has the type `!`, so the result of the overall `match` expression is `T`. This code works because `panic!` doesn’t produce a value; it ends the program. In the `None` case, we won’t be @@ -1450,11 +1592,11 @@ returning a value from `unwrap`, so this code is valid. One final expression that has the type `!` is a `loop`: ``` -print!("forever "); + print!("forever "); -loop { - print!("and ever "); -} + loop { + print!("and ever "); + } ``` Here, the loop never ends, so `!` is the value of the expression. However, this @@ -1476,8 +1618,8 @@ we can’t create a variable of type `str`, nor can we take an argument of type `str`. Consider the following code, which does not work: ``` -let s1: str = "Hello there!"; -let s2: str = "How's it going?"; + let s1: str = "Hello there!"; + let s2: str = "How's it going?"; ``` Rust needs to know how much memory to allocate for any value of a particular @@ -1488,25 +1630,27 @@ storage and `s2` needs 15. This is why it’s not possible to create a variable holding a dynamically sized type. So what do we do? In this case, you already know the answer: we make the types -of `s1` and `s2` a `&str` rather than a `str`. Recall from “String Slices” on -page XX that the slice data structure just stores the starting position and the -length of the slice. So, although a `&T` is a single value that stores the -memory address of where the `T` is located, a `&str` is *two* values: the -address of the `str` and its length. As such, we can know the size of a `&str` -value at compile time: it’s twice the length of a `usize`. That is, we always -know the size of a `&str`, no matter how long the string it refers to is. In -general, this is the way in which dynamically sized types are used in Rust: -they have an extra bit of metadata that stores the size of the dynamic -information. The golden rule of dynamically sized types is that we must always -put values of dynamically sized types behind a pointer of some kind. +of `s1` and `s2` a `&str` rather than a `str`. Recall from “String +Slices” in Chapter 4 that the slice data +structure just stores the starting position and the length of the slice. So +although a `&T` is a single value that stores the memory address of where the +`T` is located, a `&str` is *two* values: the address of the `str` and its +length. As such, we can know the size of a `&str` value at compile time: it’s +twice the length of a `usize`. That is, we always know the size of a `&str`, no +matter how long the string it refers to is. In general, this is the way in which +dynamically sized types are used in Rust: they have an extra bit of metadata +that stores the size of the dynamic information. The golden rule of dynamically +sized types is that we must always put values of dynamically sized types behind +a pointer of some kind. We can combine `str` with all kinds of pointers: for example, `Box<str>` or `Rc<str>`. In fact, you’ve seen this before but with a different dynamically sized type: traits. Every trait is a dynamically sized type we can refer to by using the name of the trait. In “Using Trait Objects That Allow for Values of -Different Types” on page XX, we mentioned that to use traits as trait objects, -we must put them behind a pointer, such as `&dyn Trait` or `Box<dyn Trait>` -(`Rc<dyn Trait>` would work too). +Different +Types” in Chapter 18, we mentioned that to use traits as trait objects, we must put +them behind a pointer, such as `&dyn Trait` or `Box<dyn Trait>` (`Rc<dyn Trait>` +would work too). To work with DSTs, Rust provides the `Sized` trait to determine whether or not a type’s size is known at compile time. This trait is automatically implemented @@ -1516,7 +1660,7 @@ generic function definition like this: ``` fn generic<T>(t: T) { - --snip-- + // --snip-- } ``` @@ -1524,7 +1668,7 @@ is actually treated as though we had written this: ``` fn generic<T: Sized>(t: T) { - --snip-- + // --snip-- } ``` @@ -1534,7 +1678,7 @@ restriction: ``` fn generic<T: ?Sized>(t: &T) { - --snip-- + // --snip-- } ``` @@ -1559,21 +1703,21 @@ including function pointers and returning closures. We’ve talked about how to pass closures to functions; you can also pass regular functions to functions! This technique is useful when you want to pass a function you’ve already defined rather than defining a new closure. Functions -coerce to the type `fn` (with a lowercase *f*), not to be confused with the -`Fn` closure trait. The `fn` type is called a *function pointer*. Passing -functions with function pointers will allow you to use functions as arguments -to other functions. +coerce to the type `fn` (with a lowercase f), not to be confused with the `Fn` +closure trait. The `fn` type is called a *function pointer*. Passing functions +with function pointers will allow you to use functions as arguments to other +functions. The syntax for specifying that a parameter is a function pointer is similar to -that of closures, as shown in Listing 19-27, where we’ve defined a function -`add_one` that adds 1 to its parameter. The function `do_twice` takes two +that of closures, as shown in Listing 20-28, where we’ve defined a function +`add_one` that adds one to its parameter. The function `do_twice` takes two parameters: a function pointer to any function that takes an `i32` parameter -and returns an `i32`, and one `i32 value`. The `do_twice` function calls the +and returns an `i32`, and one `i32` value. The `do_twice` function calls the function `f` twice, passing it the `arg` value, then adds the two function call results together. The `main` function calls `do_twice` with the arguments `add_one` and `5`. -Filename: src/main.rs +src/main.rs ``` fn add_one(x: i32) -> i32 { @@ -1591,7 +1735,7 @@ fn main() { } ``` -Listing 19-27: Using the `fn` type to accept a function pointer as an argument +Listing 20-28: Using the `fn` type to accept a function pointer as an argument This code prints `The answer is: 12`. We specify that the parameter `f` in `do_twice` is an `fn` that takes one parameter of type `i32` and returns an @@ -1618,52 +1762,44 @@ trait in the standard library. To use the `map` function to turn a vector of numbers into a vector of strings, we could use a closure, like this: ``` -let list_of_numbers = vec![1, 2, 3]; -let list_of_strings: Vec<String> = list_of_numbers - .iter() - .map(|i| i.to_string()) - .collect(); + let list_of_numbers = vec![1, 2, 3]; + let list_of_strings: Vec<String> = + list_of_numbers.iter().map(|i| i.to_string()).collect(); ``` Or we could name a function as the argument to `map` instead of the closure, like this: ``` -let list_of_numbers = vec![1, 2, 3]; -let list_of_strings: Vec<String> = list_of_numbers - .iter() - .map(ToString::to_string) - .collect(); + let list_of_numbers = vec![1, 2, 3]; + let list_of_strings: Vec<String> = + list_of_numbers.iter().map(ToString::to_string).collect(); ``` Note that we must use the fully qualified syntax that we talked about in -“Advanced Traits” on page XX because there are multiple functions available -named `to_string`. - -Here, we’re using the `to_string` function defined in the `ToString` trait, -which the standard library has implemented for any type that implements -`Display`. +“Advanced Traits” because there are multiple +functions available named `to_string`. Here, we’re using the `to_string` +function defined in the `ToString` trait, which the standard library has +implemented for any type that implements `Display`. -Recall from “Enum Values” on page XX that the name of each enum variant that we -define also becomes an initializer function. We can use these initializer -functions as function pointers that implement the closure traits, which means -we can specify the initializer functions as arguments for methods that take -closures, like so: +Recall from “Enum values” in Chapter 6 that the +name of each enum variant that we define also becomes an initializer function. +We can use these initializer functions as function pointers that implement the +closure traits, which means we can specify the initializer functions as +arguments for methods that take closures, like so: ``` -enum Status { - Value(u32), - Stop, -} + enum Status { + Value(u32), + Stop, + } -let list_of_statuses: Vec<Status> = (0u32..20) - .map(Status::Value) - .collect(); + let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect(); ``` -Here, we create `Status::Value` instances using each `u32` value in the range +Here we create `Status::Value` instances using each `u32` value in the range that `map` is called on by using the initializer function of `Status::Value`. -Some people prefer this style and some people prefer to use closures. They +Some people prefer this style, and some people prefer to use closures. They compile to the same code, so use whichever style is clearer to you. ### Returning Closures @@ -1675,46 +1811,44 @@ function. However, you can’t do that with closures because they don’t have a concrete type that is returnable; you’re not allowed to use the function pointer `fn` as a return type, for example. -The following code tries to return a closure directly, but it won’t compile: +Instead, you will normally use the `impl Trait` syntax we learned about in +Chapter 10. You can return any function type, using `Fn`, `FnOnce` and `FnMut`. +For example, this code will work just fine: ``` -fn returns_closure() -> dyn Fn(i32) -> i32 { +fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } ``` -The compiler error is as follows: +However, as we noted in the “Closure Type Inference and +Annotation” section in Chapter 13, each closure +is also its own distinct type. If you need to work with multiple functions that +have the same signature but different implementations, you will need to use a +trait object for them: ``` -error[E0746]: return type cannot have an unboxed trait object - --> src/lib.rs:1:25 - | -1 | fn returns_closure() -> dyn Fn(i32) -> i32 { - | ^^^^^^^^^^^^^^^^^^ doesn't have a size known at -compile-time - | - = note: for information on `impl Trait`, see -<https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that- -implement-traits> -help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of -type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32` - | -1 | fn returns_closure() -> impl Fn(i32) -> i32 { - | ~~~~~~~~~~~~~~~~~~~ -``` - -The error references the `Sized` trait again! Rust doesn’t know how much space -it will need to store the closure. We saw a solution to this problem earlier. -We can use a trait object: +fn main() { + let handlers = vec![returns_closure(), returns_initialized_closure(123)]; + for handler in handlers { + let output = handler(5); + println!("{output}"); + } +} -``` fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } + +fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> { + Box::new(move |x| x + init) +} ``` -This code will compile just fine. For more about trait objects, refer to “Using -Trait Objects That Allow for Values of Different Types” on page XX. +This code will compile just fine—but it wouldn’t if we had tried to stick with +`impl Fn(i32) -> i32`. For more about trait objects, refer to the section +“Using Trait Objects That Allow for Values of Different +Types” in Chapter 18. Next, let’s look at macros! @@ -1726,10 +1860,10 @@ of features in Rust: *declarative* macros with `macro_rules!` and three kinds of *procedural* macros: * Custom `#[derive]` macros that specify code added with the `derive` attribute -used on structs and enums + used on structs and enums * Attribute-like macros that define custom attributes usable on any item * Function-like macros that look like function calls but operate on the tokens -specified as their argument + specified as their argument We’ll talk about each of these in turn, but first, let’s look at why we even need macros when we already have functions. @@ -1744,7 +1878,7 @@ macros *expand* to produce more code than the code you’ve written manually. Metaprogramming is useful for reducing the amount of code you have to write and maintain, which is also one of the roles of functions. However, macros have -some additional powers that functions don’t have. +some additional powers that functions don’t. A function signature must declare the number and type of parameters the function has. Macros, on the other hand, can take a variable number of @@ -1771,7 +1905,7 @@ are also sometimes referred to as “macros by example,” “`macro_rules!` mac or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust `match` expression. As discussed in Chapter 6, `match` expressions are control structures that take an expression, compare the -resultant value of the expression to patterns, and then run the code associated +resulting value of the expression to patterns, and then run the code associated with the matching pattern. Macros also compare a value to patterns that are associated with particular code: in this situation, the value is the literal Rust source code passed to the macro; the patterns are compared with the @@ -1793,54 +1927,54 @@ We could also use the `vec!` macro to make a vector of two integers or a vector of five string slices. We wouldn’t be able to use a function to do the same because we wouldn’t know the number or type of values up front. -Listing 19-28 shows a slightly simplified definition of the `vec!` macro. +Listing 20-29 shows a slightly simplified definition of the `vec!` macro. -Filename: src/lib.rs +src/lib.rs ``` -1 #[macro_export] -2 macro_rules! vec { - 3 ( $( $x:expr ),* ) => { +#[macro_export] +macro_rules! vec { + ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); - 4 $( - 5 temp_vec.push(6 $x); + $( + temp_vec.push($x); )* - 7 temp_vec + temp_vec } }; } ``` -Listing 19-28: A simplified version of the `vec!` macro definition +Listing 20-29: A simplified version of the `vec!` macro definition > Note: The actual definition of the `vec!` macro in the standard library -includes code to pre-allocate the correct amount of memory up front. That code -is an optimization that we don’t include here, to make the example simpler. +> includes code to preallocate the correct amount of memory up front. That code +> is an optimization that we don’t include here to make the example simpler. -The `#[macro_export]` annotation [1] indicates that this macro should be made +The `#[macro_export]` annotation indicates that this macro should be made available whenever the crate in which the macro is defined is brought into scope. Without this annotation, the macro can’t be brought into scope. We then start the macro definition with `macro_rules!` and the name of the -macro we’re defining *without* the exclamation mark [2]. The name, in this case +macro we’re defining *without* the exclamation mark. The name, in this case `vec`, is followed by curly brackets denoting the body of the macro definition. The structure in the `vec!` body is similar to the structure of a `match` expression. Here we have one arm with the pattern `( $( $x:expr ),* )`, -followed by `=>` and the block of code associated with this pattern [3]. If the +followed by `=>` and the block of code associated with this pattern. If the pattern matches, the associated block of code will be emitted. Given that this is the only pattern in this macro, there is only one valid way to match; any other pattern will result in an error. More complex macros will have more than one arm. -Valid pattern syntax in macro definitions is different from the pattern syntax -covered in Chapter 18 because macro patterns are matched against Rust code +Valid pattern syntax in macro definitions is different than the pattern syntax +covered in Chapter 19 because macro patterns are matched against Rust code structure rather than values. Let’s walk through what the pattern pieces in -Listing 19-28 mean; for the full macro pattern syntax, see the Rust Reference -at *https://doc.rust-lang.org/reference/macros-by-example.html*. +Listing 20-29 mean; for the full macro pattern syntax, see the Rust +Reference at *../reference/macros-by-example.html*. -First we use a set of parentheses to encompass the whole pattern. We use a +First, we use a set of parentheses to encompass the whole pattern. We use a dollar sign (`$`) to declare a variable in the macro system that will contain the Rust code matching the pattern. The dollar sign makes it clear this is a macro variable as opposed to a regular Rust variable. Next comes a set of @@ -1849,18 +1983,19 @@ for use in the replacement code. Within `$()` is `$x:expr`, which matches any Rust expression and gives the expression the name `$x`. The comma following `$()` indicates that a literal comma separator character -could optionally appear after the code that matches the code in `$()`. The `*` -specifies that the pattern matches zero or more of whatever precedes the `*`. +must appear between each instance of the code that matches the code within +`$()`. The `*` specifies that the pattern matches zero or more of whatever +precedes the `*`. When we call this macro with `vec![1, 2, 3];`, the `$x` pattern matches three times with the three expressions `1`, `2`, and `3`. Now let’s look at the pattern in the body of the code associated with this arm: -`temp_vec.push()` [5] within `$()* at [4] and [7] is generated for each part -that matches `$()` in the pattern zero or more times depending on how many -times the pattern matches. The `$x` [6] is replaced with each expression -matched. When we call this macro with `vec![1, 2, 3];`, the code generated that -replaces this macro call will be the following: +`temp_vec.push()` within `$()*` is generated for each part that matches `$()` +in the pattern zero or more times depending on how many times the pattern +matches. The `$x` is replaced with each expression matched. When we call this +macro with `vec![1, 2, 3];`, the code generated that replaces this macro call +will be the following: ``` { @@ -1876,36 +2011,35 @@ We’ve defined a macro that can take any number of arguments of any type and ca generate code to create a vector containing the specified elements. To learn more about how to write macros, consult the online documentation or -other resources, such as “The Little Book of Rust Macros” at -*https://veykril.github.io/tlborm* started by Daniel Keep and continued by -Lukas Wirth. +other resources, such as “The Little Book of Rust Macros” at *https://veykril.github.io/tlborm/* started by +Daniel Keep and continued by Lukas Wirth. ### Procedural Macros for Generating Code from Attributes -The second form of macros is the procedural macro, which acts more like a -function (and is a type of procedure). *Procedural macros* accept some code as -an input, operate on that code, and produce some code as an output rather than +The second form of macros is the *procedural macro*, which acts more like a +function (and is a type of procedure). Procedural macros accept some code as an +input, operate on that code, and produce some code as an output rather than matching against patterns and replacing the code with other code as declarative -macros do. The three kinds of procedural macros are custom `derive`, +macros do. The three kinds of procedural macros are custom derive, attribute-like, and function-like, and all work in a similar fashion. When creating procedural macros, the definitions must reside in their own crate with a special crate type. This is for complex technical reasons that we hope -to eliminate in the future. In Listing 19-29, we show how to define a +to eliminate in the future. In Listing 20-30, we show how to define a procedural macro, where `some_attribute` is a placeholder for using a specific macro variety. -Filename: src/lib.rs +src/lib.rs ``` -use proc_macro::TokenStream; +use proc_macro; #[some_attribute] pub fn some_name(input: TokenStream) -> TokenStream { } ``` -Listing 19-29: An example of defining a procedural macro +Listing 20-30: An example of defining a procedural macro The function that defines a procedural macro takes a `TokenStream` as an input and produces a `TokenStream` as an output. The `TokenStream` type is defined by @@ -1917,7 +2051,7 @@ that specifies which kind of procedural macro we’re creating. We can have multiple kinds of procedural macros in the same crate. Let’s look at the different kinds of procedural macros. We’ll start with a -custom `derive` macro and then explain the small dissimilarities that make the +custom derive macro and then explain the small dissimilarities that make the other forms different. ### How to Write a Custom derive Macro @@ -1927,12 +2061,11 @@ Let’s create a crate named `hello_macro` that defines a trait named making our users implement the `HelloMacro` trait for each of their types, we’ll provide a procedural macro so users can annotate their type with `#[derive(HelloMacro)]` to get a default implementation of the `hello_macro` -function. The default implementation will print `Hello, Macro! My name is` -TypeName`!` where TypeName is the name of the type on which this trait has been -defined. In other words, we’ll write a crate that enables another programmer to -write code like Listing 19-30 using our crate. +function. The default implementation will print `Hello, Macro! My name is TypeName!` where `TypeName` is the name of the type on which this trait has +been defined. In other words, we’ll write a crate that enables another +programmer to write code like Listing 20-31 using our crate. -Filename: src/main.rs +src/main.rs ``` use hello_macro::HelloMacro; @@ -1946,8 +2079,7 @@ fn main() { } ``` -Listing 19-30: The code a user of our crate will be able to write when using -our procedural macro +Listing 20-31: The code a user of our crate will be able to write when using our procedural macro This code will print `Hello, Macro! My name is Pancakes!` when we’re done. The first step is to make a new library crate, like this: @@ -1958,7 +2090,7 @@ $ cargo new hello_macro --lib Next, we’ll define the `HelloMacro` trait and its associated function: -Filename: src/lib.rs +src/lib.rs ``` pub trait HelloMacro { @@ -1966,6 +2098,8 @@ pub trait HelloMacro { } ``` + + We have a trait and its function. At this point, our crate user could implement the trait to achieve the desired functionality, like so: @@ -1997,8 +2131,8 @@ name at runtime. We need a macro to generate code at compile time. The next step is to define the procedural macro. At the time of this writing, procedural macros need to be in their own crate. Eventually, this restriction might be lifted. The convention for structuring crates and macro crates is as -follows: for a crate named foo, a custom `derive` procedural macro crate is -called foo`_derive`. Let’s start a new crate called `hello_macro_derive` inside +follows: for a crate named `foo`, a custom derive procedural macro crate is +called `foo_derive`. Let’s start a new crate called `hello_macro_derive` inside our `hello_macro` project: ``` @@ -2021,41 +2155,41 @@ We’ll also need functionality from the `syn` and `quote` crates, as you’ll s in a moment, so we need to add them as dependencies. Add the following to the *Cargo.toml* file for `hello_macro_derive`: -Filename: hello_macro_derive/Cargo.toml +hello_macro_derive/Cargo.toml ``` [lib] proc-macro = true [dependencies] -syn = "1.0" +syn = "2.0" quote = "1.0" ``` -To start defining the procedural macro, place the code in Listing 19-31 into + + +To start defining the procedural macro, place the code in Listing 20-32 into your *src/lib.rs* file for the `hello_macro_derive` crate. Note that this code won’t compile until we add a definition for the `impl_hello_macro` function. -Filename: hello_macro_derive/src/lib.rs +hello_macro_derive/src/lib.rs ``` use proc_macro::TokenStream; use quote::quote; -use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree - // that we can manipulate + // that we can manipulate. let ast = syn::parse(input).unwrap(); - // Build the trait implementation + // Build the trait implementation. impl_hello_macro(&ast) } ``` -Listing 19-31: Code that most procedural macro crates will require in order to -process Rust code +Listing 20-32: Code that most procedural macro crates will require in order to process Rust code Notice that we’ve split the code into the `hello_macro_derive` function, which is responsible for parsing the `TokenStream`, and the `impl_hello_macro` @@ -2066,12 +2200,10 @@ procedural macro crate you see or create. The code you specify in the body of the inner function (`impl_hello_macro` in this case) will be different depending on your procedural macro’s purpose. -We’ve introduced three new crates: `proc_macro`, `syn` (available from -*https://crates.io/crates/syn*), and `quote` (available from -*https://crates.io/crates/quote*). The `proc_macro` crate comes with Rust, so -we didn’t need to add that to the dependencies in *Cargo.toml*. The -`proc_macro` crate is the compiler’s API that allows us to read and manipulate -Rust code from our code. +We’ve introduced three new crates: `proc_macro`, [`syn`], and [`quote`]. The +`proc_macro` crate comes with Rust, so we didn’t need to add that to the +dependencies in *Cargo.toml*. The `proc_macro` crate is the compiler’s API that +allows us to read and manipulate Rust code from our code. The `syn` crate parses Rust code from a string into a data structure that we can perform operations on. The `quote` crate turns `syn` data structures back @@ -2089,12 +2221,13 @@ The `hello_macro_derive` function first converts the `input` from a `TokenStream` to a data structure that we can then interpret and perform operations on. This is where `syn` comes into play. The `parse` function in `syn` takes a `TokenStream` and returns a `DeriveInput` struct representing the -parsed Rust code. Listing 19-32 shows the relevant parts of the `DeriveInput` -struct we get from parsing the `struct Pancakes;` string. +parsed Rust code. Listing 20-33 shows the relevant parts of the `DeriveInput` +struct we get from parsing the `struct Pancakes;` string: + ``` DeriveInput { - --snip-- + // --snip-- ident: Ident { ident: "Pancakes", @@ -2112,18 +2245,16 @@ DeriveInput { } ``` -Listing 19-32: The `DeriveInput` instance we get when parsing the code that has -the macro’s attribute in Listing 19-30 +Listing 20-33: The `DeriveInput` instance we get when parsing the code that has the macro’s attribute in Listing 20-31 The fields of this struct show that the Rust code we’ve parsed is a unit struct -with the `ident` (*identifier*, meaning the name) of `Pancakes`. There are more +with the `ident` (identifier, meaning the name) of `Pancakes`. There are more fields on this struct for describing all sorts of Rust code; check the `syn` -documentation for `DeriveInput` at -*https://docs.rs/syn/1.0/syn/struct.DeriveInput.html* for more information. +documentation for `DeriveInput` at *https://docs.rs/syn/2.0/syn/struct.DeriveInput.html* for more information. Soon we’ll define the `impl_hello_macro` function, which is where we’ll build the new Rust code we want to include. But before we do, note that the output -for our `derive` macro is also a `TokenStream`. The returned `TokenStream` is +for our derive macro is also a `TokenStream`. The returned `TokenStream` is added to the code that our crate users write, so when they compile their crate, they’ll get the extra functionality that we provide in the modified `TokenStream`. @@ -2138,9 +2269,9 @@ about what went wrong by using `panic!` or `expect`. Now that we have the code to turn the annotated Rust code from a `TokenStream` into a `DeriveInput` instance, let’s generate the code that implements the -`HelloMacro` trait on the annotated type, as shown in Listing 19-33. +`HelloMacro` trait on the annotated type, as shown in Listing 20-34. -Filename: hello_macro_derive/src/lib.rs +hello_macro_derive/src/lib.rs ``` fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { @@ -2148,10 +2279,7 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { impl HelloMacro for #name { fn hello_macro() { - println!( - "Hello, Macro! My name is {}!", - stringify!(#name) - ); + println!("Hello, Macro! My name is {}!", stringify!(#name)); } } }; @@ -2159,15 +2287,15 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { } ``` -Listing 19-33: Implementing the `HelloMacro` trait using the parsed Rust code +Listing 20-34: Implementing the `HelloMacro` trait using the parsed Rust code We get an `Ident` struct instance containing the name (identifier) of the -annotated type using `ast.ident`. The struct in Listing 19-32 shows that when -we run the `impl_hello_macro` function on the code in Listing 19-30, the -`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus -the `name` variable in Listing 19-33 will contain an `Ident` struct instance +annotated type using `ast.ident`. The struct in Listing 20-33 shows that when +we run the `impl_hello_macro` function on the code in Listing 20-31, the +`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus, +the `name` variable in Listing 20-34 will contain an `Ident` struct instance that, when printed, will be the string `"Pancakes"`, the name of the struct in -Listing 19-30. +Listing 20-31. The `quote!` macro lets us define the Rust code that we want to return. The compiler expects something different to the direct result of the `quote!` @@ -2178,18 +2306,17 @@ returns a value of the required `TokenStream` type. The `quote!` macro also provides some very cool templating mechanics: we can enter `#name`, and `quote!` will replace it with the value in the variable `name`. You can even do some repetition similar to the way regular macros work. -Check out the `quote` crate’s docs at *https://docs.rs/quote* for a thorough -introduction. +Check out the `quote` crate’s docs at *https://docs.rs/quote* for a thorough introduction. We want our procedural macro to generate an implementation of our `HelloMacro` trait for the type the user annotated, which we can get by using `#name`. The -trait implementation has the one function `hello_macro`, whose body contains -the functionality we want to provide: printing `Hello, Macro! My name is` and -then the name of the annotated type. +trait implementation has the one function `hello_macro`, whose body contains the +functionality we want to provide: printing `Hello, Macro! My name is` and then +the name of the annotated type. The `stringify!` macro used here is built into Rust. It takes a Rust expression, such as `1 + 2`, and at compile time turns the expression into a -string literal, such as `"1 + 2"`. This is different from `format!` or +string literal, such as `"1 + 2"`. This is different than `format!` or `println!`, macros which evaluate the expression and then turn the result into a `String`. There is a possibility that the `#name` input might be an expression to print literally, so we use `stringify!`. Using `stringify!` also @@ -2197,35 +2324,34 @@ saves an allocation by converting `#name` to a string literal at compile time. At this point, `cargo build` should complete successfully in both `hello_macro` and `hello_macro_derive`. Let’s hook up these crates to the code in Listing -19-30 to see the procedural macro in action! Create a new binary project in +20-31 to see the procedural macro in action! Create a new binary project in your *projects* directory using `cargo new pancakes`. We need to add `hello_macro` and `hello_macro_derive` as dependencies in the `pancakes` crate’s *Cargo.toml*. If you’re publishing your versions of `hello_macro` and -`hello_macro_derive` to *https://crates.io*, they would be regular +`hello_macro_derive` to crates.io at *https://crates.io/*, they would be regular dependencies; if not, you can specify them as `path` dependencies as follows: ``` -[dependencies] hello_macro = { path = "../hello_macro" } hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } ``` -Put the code in Listing 19-30 into *src/main.rs*, and run `cargo run`: it +Put the code in Listing 20-31 into *src/main.rs*, and run `cargo run`: it should print `Hello, Macro! My name is Pancakes!` The implementation of the `HelloMacro` trait from the procedural macro was included without the `pancakes` crate needing to implement it; the `#[derive(HelloMacro)]` added the trait implementation. Next, let’s explore how the other kinds of procedural macros differ from custom -`derive` macros. +derive macros. -### Attribute-like Macros +### Attribute-Like macros -Attribute-like macros are similar to custom `derive` macros, but instead of +Attribute-like macros are similar to custom derive macros, but instead of generating code for the `derive` attribute, they allow you to create new attributes. They’re also more flexible: `derive` only works for structs and enums; attributes can be applied to other items as well, such as functions. -Here’s an example of using an attribute-like macro. Say you have an attribute +Here’s an example of using an attribute-like macro: say you have an attribute named `route` that annotates functions when using a web application framework: ``` @@ -2238,10 +2364,7 @@ macro. The signature of the macro definition function would look like this: ``` #[proc_macro_attribute] -pub fn route( - attr: TokenStream, - item: TokenStream -) -> TokenStream { +pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { ``` Here, we have two parameters of type `TokenStream`. The first is for the @@ -2249,21 +2372,21 @@ contents of the attribute: the `GET, "/"` part. The second is the body of the item the attribute is attached to: in this case, `fn index() {}` and the rest of the function’s body. -Other than that, attribute-like macros work the same way as custom `derive` +Other than that, attribute-like macros work the same way as custom derive macros: you create a crate with the `proc-macro` crate type and implement a function that generates the code you want! -### Function-like Macros +### Function-Like macros Function-like macros define macros that look like function calls. Similarly to `macro_rules!` macros, they’re more flexible than functions; for example, they -can take an unknown number of arguments. However, `macro_rules!` macros can -only be defined using the match-like syntax we discussed in “Declarative Macros -with macro_rules! for General Metaprogramming” on page XX. Function-like macros -take a `TokenStream` parameter, and their definition manipulates that -`TokenStream` using Rust code as the other two types of procedural macros do. -An example of a function-like macro is an `sql!` macro that might be called -like so: +can take an unknown number of arguments. However, `macro_rules!` macros can only +be defined using the match-like syntax we discussed in “Declarative Macros with +`macro_rules!` for General Metaprogramming” earlier. +Function-like macros take a `TokenStream` parameter and their definition +manipulates that `TokenStream` using Rust code as the other two types of +procedural macros do. An example of a function-like macro is an `sql!` macro +that might be called like so: ``` let sql = sql!(SELECT * FROM posts WHERE id=1); @@ -2278,7 +2401,7 @@ syntactically correct, which is much more complex processing than a pub fn sql(input: TokenStream) -> TokenStream { ``` -This definition is similar to the custom `derive` macro’s signature: we receive +This definition is similar to the custom derive macro’s signature: we receive the tokens that are inside the parentheses and return the code we wanted to generate. @@ -2287,10 +2410,9 @@ generate. Whew! Now you have some Rust features in your toolbox that you likely won’t use often, but you’ll know they’re available in very particular circumstances. We’ve introduced several complex topics so that when you encounter them in -error message suggestions or in other people’s code, you’ll be able to +error message suggestions or in other peoples’ code, you’ll be able to recognize these concepts and syntax. Use this chapter as a reference to guide you to solutions. Next, we’ll put everything we’ve discussed throughout the book into practice and do one more project! - diff --git a/nostarch/chapter21.md b/nostarch/chapter21.md index 859a535a6c..d6a7e8ac64 100644 --- a/nostarch/chapter21.md +++ b/nostarch/chapter21.md @@ -84,7 +84,7 @@ Now enter the code in Listing 21-1 in *src/main.rs* to start. This code will listen at the local address `127.0.0.1:7878` for incoming TCP streams. When it gets an incoming stream, it will print `Connection established!`. -Filename: src/main.rs +src/main.rs ``` use std::net::TcpListener; @@ -117,7 +117,7 @@ to a port.” The `bind` function returns a `Result<T, E>`, which indicates that it’s possible for binding to fail. For example, connecting to port 80 requires -administrator privileges (nonadministrators can listen only on ports higher +administrator privileges (non-administrators can listen only on ports higher than 1023), so if we tried to connect to port 80 without being an administrator, binding wouldn’t work. Binding also wouldn’t work, for example, if we ran two instances of our program and so had two programs listening to the @@ -149,7 +149,7 @@ connections are closed. Let’s try running this code! Invoke `cargo run` in the terminal and then load *127.0.0.1:7878* in a web browser. The browser should show an error message -like “Connection reset,” because the server isn’t currently sending back any +like “Connection reset” because the server isn’t currently sending back any data. But when you look at your terminal, you should see several messages that were printed when the browser connected to the server! @@ -160,7 +160,7 @@ Connection established! Connection established! ``` -Sometimes, you’ll see multiple messages printed for one browser request; the +Sometimes you’ll see multiple messages printed for one browser request; the reason might be that the browser is making a request for the page as well as a request for other resources, like the *favicon.ico* icon that appears in the browser tab. @@ -186,7 +186,7 @@ this new `handle_connection` function, we’ll read data from the TCP stream and print it so we can see the data being sent from the browser. Change the code to look like Listing 21-2. -Filename: src/main.rs +src/main.rs ``` use std::{ @@ -299,17 +299,17 @@ being used, such as `GET` or `POST`, which describes how the client is making this request. Our client used a `GET` request, which means it is asking for information. -The next part of the request line is */*, which indicates the *Uniform Resource -Identifier* *(URI)* the client is requesting: a URI is almost, but not quite, -the same as a *Uniform Resource Locator* *(URL)*. The difference between URIs +The next part of the request line is */*, which indicates the *uniform resource +identifier* *(URI)* the client is requesting: a URI is almost, but not quite, +the same as a *uniform resource locator* *(URL)*. The difference between URIs and URLs isn’t important for our purposes in this chapter, but the HTTP spec -uses the term URI, so we can just mentally substitute URL for URI here. +uses the term URI, so we can just mentally substitute *URL* for *URI* here. The last part is the HTTP version the client uses, and then the request line -ends in a *CRLF sequence*. (CRLF stands for *carriage return* and *line feed*, +ends in a CRLF sequence. (CRLF stands for *carriage return* and *line feed*, which are terms from the typewriter days!) The CRLF sequence can also be written as `\r\n`, where `\r` is a carriage return and `\n` is a line feed. The -CRLF sequence separates the request line from the rest of the request data. +*CRLF sequence* separates the request line from the rest of the request data. Note that when the CRLF is printed, we see a new line start rather than `\r\n`. Looking at the request line data we received from running our program so far, @@ -341,7 +341,7 @@ a reason phrase that provides a text description of the status code. After the CRLF sequence are any headers, another CRLF sequence, and the body of the response. -Here is an example response that uses HTTP version 1.1, has a status code of +Here is an example response that uses HTTP version 1.1, and has a status code of 200, an OK reason phrase, no headers, and no body: ``` @@ -354,7 +354,7 @@ successful request! From the `handle_connection` function, remove the `println!` that was printing the request data and replace it with the code in Listing 21-3. -Filename: src/main.rs +src/main.rs ``` fn handle_connection(mut stream: TcpStream) { @@ -383,7 +383,7 @@ application you would add error handling here. With these changes, let’s run our code and make a request. We’re no longer printing any data to the terminal, so we won’t see any output other than the output from Cargo. When you load *127.0.0.1:7878* in a web browser, you should -get a blank page instead of an error. You’ve just hand-coded receiving an HTTP +get a blank page instead of an error. You’ve just handcoded receiving an HTTP request and sending a response! ### Returning Real HTML @@ -393,7 +393,7 @@ the new file *hello.html* in the root of your project directory, not in the *src* directory. You can input any HTML you want; Listing 21-4 shows one possibility. -Filename: hello.html +hello.html ``` <!DOCTYPE html> @@ -416,7 +416,7 @@ from the server when a request is received, we’ll modify `handle_connection` a shown in Listing 21-5 to read the HTML file, add it to the response as a body, and send it. -Filename: src/main.rs +src/main.rs ``` use std::{ @@ -449,8 +449,8 @@ Listing 21-5: Sending the contents of *hello.html* as the body of the response We’ve added `fs` to the `use` statement to bring the standard library’s filesystem module into scope. The code for reading the contents of a file to a -string should look familiar; we used it in Chapter 12 when we read the contents -of a file for our I/O project in Listing 12-4. +string should look familiar; we used it when we read the contents of a file for +our I/O project in Listing 12-4. Next, we use `format!` to add the file’s contents as the body of the success response. To ensure a valid HTTP response, we add the `Content-Length` header @@ -478,7 +478,7 @@ as shown in Listing 21-6. This new code checks the content of the request received against what we know a request for */* looks like and adds `if` and `else` blocks to treat requests differently. -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -530,7 +530,7 @@ with the status code 404, which signals that the content for the request was not found. We’ll also return some HTML for a page to render in the browser indicating the response to the end user. -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -555,7 +555,7 @@ You’ll need to create a *404.html* file next to *hello.html* for the error page; again feel free to use any HTML you want or use the example HTML in Listing 21-8. -Filename: 404.html +404.html ``` <!DOCTYPE html> @@ -579,16 +579,16 @@ return the contents of *hello.html*, and any other request, like ### A Touch of Refactoring -At the moment the `if` and `else` blocks have a lot of repetition: they’re both +At the moment, the `if` and `else` blocks have a lot of repetition: they’re both reading files and writing the contents of the files to the stream. The only differences are the status line and the filename. Let’s make the code more concise by pulling out those differences into separate `if` and `else` lines -that will assign the values of the status line and the filename to variables; -we can then use those variables unconditionally in the code to read the file -and write the response. Listing 21-9 shows the resulting code after replacing -the large `if` and `else` blocks. +that will assign the values of the status line and the filename to variables; we +can then use those variables unconditionally in the code to read the file and +write the response. Listing 21-9 shows the resultant code after replacing the +large `if` and `else` blocks. -Filename: src/main.rs +src/main.rs ``` // --snip-- @@ -643,16 +643,16 @@ server received more and more requests, this serial execution would be less and less optimal. If the server receives a request that takes a long time to process, subsequent requests will have to wait until the long request is finished, even if the new requests can be processed quickly. We’ll need to fix -this, but first, we’ll look at the problem in action. +this, but first we’ll look at the problem in action. ### Simulating a Slow Request in the Current Server Implementation We’ll look at how a slow-processing request can affect other requests made to our current server implementation. Listing 21-10 implements handling a request to */sleep* with a simulated slow response that will cause the server to sleep -for 5 seconds before responding. +for five seconds before responding. -Filename: src/main.rs +src/main.rs ``` use std::{ @@ -685,12 +685,12 @@ Listing 21-10: Simulating a slow request by sleeping for 5 seconds We switched from `if` to `match` now that we have three cases. We need to explicitly match on a slice of `request_line` to pattern match against the string literal values; `match` doesn’t do automatic referencing and -dereferencing like the equality method does. +dereferencing, like the equality method does. The first arm is the same as the `if` block from Listing 21-9. The second arm matches a request to */sleep*. When that request is received, the server will -sleep for 5 seconds before rendering the successful HTML page. The third arm is -the same as the `else` block from Listing 21-9. +sleep for five seconds before rendering the successful HTML page. The third arm +is the same as the `else` block from Listing 21-9. You can see how primitive our server is: real libraries would handle the recognition of multiple requests in a much less verbose way! @@ -699,7 +699,7 @@ Start the server using `cargo run`. Then open two browser windows: one for *http://127.0.0.1:7878/* and the other for *http://127.0.0.1:7878/sleep*. If you enter the */* URI a few times, as before, you’ll see it respond quickly. But if you enter */sleep* and then load */*, you’ll see that */* waits until -`sleep` has slept for its full 5 seconds before loading. +`sleep` has slept for its full five seconds before loading. There are multiple techniques we could use to avoid requests backing up behind a slow request, including using async as we did Chapter 17; the one we’ll @@ -717,24 +717,24 @@ a new task. A thread pool allows you to process connections concurrently, increasing the throughput of your server. We’ll limit the number of threads in the pool to a small number to protect us -from Denial of Service (DoS) attacks; if we had our program create a new thread -for each request as it came in, someone making 10 million requests to our -server could create havoc by using up all our server’s resources and grinding -the processing of requests to a halt. +from DoS attacks; if we had our program create a new thread for each request as +it came in, someone making 10 million requests to our server could create havoc +by using up all our server’s resources and grinding the processing of requests +to a halt. Rather than spawning unlimited threads, then, we’ll have a fixed number of threads waiting in the pool. Requests that come in are sent to the pool for processing. The pool will maintain a queue of incoming requests. Each of the threads in the pool will pop off a request from this queue, handle the request, and then ask the queue for another request. With this design, we can process up -to `N` requests concurrently, where `N` is the number of threads. If each +to *`N`* requests concurrently, where *`N`* is the number of threads. If each thread is responding to a long-running request, subsequent requests can still back up in the queue, but we’ve increased the number of long-running requests we can handle before reaching that point. This technique is just one of many ways to improve the throughput of a web -server. Other options you might explore are the *fork/join model*, the -*single-threaded async I/O model*, or the *multi-threaded async I/O model*. If +server. Other options you might explore are the fork/join model, the +single-threaded async I/O model, and the multi-threaded async I/O model. If you’re interested in this topic, you can read more about other solutions and try to implement them; with a low-level language like Rust, all of these options are possible. @@ -766,7 +766,7 @@ thread pool as an improvement, and contrasting the two solutions will be easier. Listing 21-11 shows the changes to make to `main` to spawn a new thread to handle each stream within the `for` loop. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -801,12 +801,12 @@ pool and think about how things would look different or the same with async. #### Creating a Finite Number of Threads -We want our thread pool to work in a similar, familiar way so switching from -threads to a thread pool doesn’t require large changes to the code that uses -our API. Listing 21-12 shows the hypothetical interface for a `ThreadPool` +We want our thread pool to work in a similar, familiar way so that switching +from threads to a thread pool doesn’t require large changes to the code that +uses our API. Listing 21-12 shows the hypothetical interface for a `ThreadPool` struct we want to use instead of `thread::spawn`. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -836,7 +836,7 @@ compile, but we’ll try so the compiler can guide us in how to fix it. <a id="building-the-threadpool-struct-using-compiler-driven-development"></a> -#### Building `ThreadPool` Using Compiler Driven Development +#### Building ThreadPool Using Compiler Driven Development Make the changes in Listing 21-12 to *src/main.rs*, and then let’s use the compiler errors from `cargo check` to drive our development. Here is the first @@ -857,16 +857,16 @@ error: could not compile `hello` (bin "hello") due to 1 previous error Great! This error tells us we need a `ThreadPool` type or module, so we’ll build one now. Our `ThreadPool` implementation will be independent of the kind -of work our web server is doing. So, let’s switch the `hello` crate from a +of work our web server is doing. So let’s switch the `hello` crate from a binary crate to a library crate to hold our `ThreadPool` implementation. After we change to a library crate, we could also use the separate thread pool library for any work we want to do using a thread pool, not just for serving web requests. -Create a *src/lib.rs* that contains the following, which is the simplest +Create a *src/lib.rs* file that contains the following, which is the simplest definition of a `ThreadPool` struct that we can have for now: -Filename: src/lib.rs +src/lib.rs ``` pub struct ThreadPool; @@ -877,7 +877,7 @@ pub struct ThreadPool; Then edit *main.rs* file to bring `ThreadPool` into scope from the library crate by adding the following code to the top of *src/main.rs*: -Filename: src/main.rs +src/main.rs ``` use hello::ThreadPool; @@ -907,7 +907,7 @@ that can accept `4` as an argument and should return a `ThreadPool` instance. Let’s implement the simplest `new` function that will have those characteristics: -Filename: src/lib.rs +src/lib.rs ``` pub struct ThreadPool; @@ -921,10 +921,10 @@ impl ThreadPool { -We chose `usize` as the type of the `size` parameter, because we know that a +We chose `usize` as the type of the `size` parameter because we know that a negative number of threads doesn’t make any sense. We also know we’ll use this -4 as the number of elements in a collection of threads, which is what the -`usize` type is for, as discussed in the “Integer Types” section of Chapter 3. +`4` as the number of elements in a collection of threads, which is what the +`usize` type is for, as discussed in “Integer Types” in Chapter 3. Let’s check the code again: @@ -942,20 +942,20 @@ error: could not compile `hello` (bin "hello") due to 1 previous error ``` Now the error occurs because we don’t have an `execute` method on `ThreadPool`. -Recall from the “Creating a Finite Number of -Threads” section that we -decided our thread pool should have an interface similar to `thread::spawn`. In +Recall from “Creating a Finite Number of +Threads” that we decided +our thread pool should have an interface similar to `thread::spawn`. In addition, we’ll implement the `execute` function so it takes the closure it’s given and gives it to an idle thread in the pool to run. We’ll define the `execute` method on `ThreadPool` to take a closure as a -parameter. Recall from the “Moving Captured Values Out of the Closure and the -`Fn` Traits” section in Chapter 13 that we can take -closures as parameters with three different traits: `Fn`, `FnMut`, and -`FnOnce`. We need to decide which kind of closure to use here. We know we’ll -end up doing something similar to the standard library `thread::spawn` -implementation, so we can look at what bounds the signature of `thread::spawn` -has on its parameter. The documentation shows us the following: +parameter. Recall from “Moving Captured Values Out of the Closure and the `Fn` +Traits” in Chapter 13 that we can take closures as +parameters with three different traits: `Fn`, `FnMut`, and `FnOnce`. We need to +decide which kind of closure to use here. We know we’ll end up doing something +similar to the standard library `thread::spawn` implementation, so we can look +at what bounds the signature of `thread::spawn` has on its parameter. The +documentation shows us the following: ``` pub fn spawn<F, T>(f: F) -> JoinHandle<T> @@ -979,7 +979,7 @@ closure from one thread to another and `'static` because we don’t know how lon the thread will take to execute. Let’s create an `execute` method on `ThreadPool` that will take a generic parameter of type `F` with these bounds: -Filename: src/lib.rs +src/lib.rs ``` impl ThreadPool { @@ -1000,7 +1000,7 @@ definitions, the return type can be omitted from the signature, but even if we have no parameters, we still need the parentheses. Again, this is the simplest implementation of the `execute` method: it does -nothing, but we’re trying only to make our code compile. Let’s check it again: +nothing, but we’re only trying to make our code compile. Let’s check it again: ``` $ cargo check @@ -1023,18 +1023,18 @@ yet! Consider: what would be different here if we were going to execute a *future* instead of a closure? -#### Validating the Number of Threads in `new` +#### Validating the Number of Threads in new We aren’t doing anything with the parameters to `new` and `execute`. Let’s implement the bodies of these functions with the behavior we want. To start, let’s think about `new`. Earlier we chose an unsigned type for the `size` -parameter, because a pool with a negative number of threads makes no sense. +parameter because a pool with a negative number of threads makes no sense. However, a pool with zero threads also makes no sense, yet zero is a perfectly valid `usize`. We’ll add code to check that `size` is greater than zero before we return a `ThreadPool` instance and have the program panic if it receives a zero by using the `assert!` macro, as shown in Listing 21-13. -Filename: src/lib.rs +src/lib.rs ``` impl ThreadPool { @@ -1100,7 +1100,7 @@ We’ve changed the definition of `ThreadPool` to hold a vector of `size`, set up a `for` loop that will run some code to create the threads, and returned a `ThreadPool` instance containing them. -Filename: src/lib.rs +src/lib.rs ``` use std::thread; @@ -1128,20 +1128,20 @@ impl ThreadPool { Listing 21-14: Creating a vector for `ThreadPool` to hold the threads -We’ve brought `std::thread` into scope in the library crate, because we’re +We’ve brought `std::thread` into scope in the library crate because we’re using `thread::JoinHandle` as the type of the items in the vector in `ThreadPool`. Once a valid size is received, our `ThreadPool` creates a new vector that can hold `size` items. The `with_capacity` function performs the same task as -`Vec::new` but with an important difference: it preallocates space in the +`Vec::new` but with an important difference: it pre-allocates space in the vector. Because we know we need to store `size` elements in the vector, doing this allocation up front is slightly more efficient than using `Vec::new`, which resizes itself as elements are inserted. When you run `cargo check` again, it should succeed. -#### A `Worker` Struct Responsible for Sending Code from the `ThreadPool` to a Thread +#### A Worker Struct Responsible for Sending Code from the ThreadPool to a Thread We left a comment in the `for` loop in Listing 21-14 regarding the creation of threads. Here, we’ll look at how we actually create threads. The standard @@ -1155,17 +1155,19 @@ implement it manually. We’ll implement this behavior by introducing a new data structure between the `ThreadPool` and the threads that will manage this new behavior. We’ll call this data structure *Worker*, which is a common term in pooling -implementations. The Worker picks up code that needs to be run and runs the -code in the Worker’s thread. Think of people working in the kitchen at a -restaurant: the workers wait until orders come in from customers, and then -they’re responsible for taking those orders and fulfilling them. +implementations. The `Worker` picks up code that needs to be run and runs the +code in the Worker’s thread. + +Think of people working in the kitchen at a restaurant: the workers wait until +orders come in from customers, and then they’re responsible for taking those +orders and fulfilling them. Instead of storing a vector of `JoinHandle<()>` instances in the thread pool, we’ll store instances of the `Worker` struct. Each `Worker` will store a single `JoinHandle<()>` instance. Then we’ll implement a method on `Worker` that will take a closure of code to run and send it to the already running thread for -execution. We’ll also give each worker an `id` so we can distinguish between -the different workers in the pool when logging or debugging. +execution. We’ll also give each `Worker` an `id` so we can distinguish between +the different instances of `Worker` in the pool when logging or debugging. Here is the new process that will happen when we create a `ThreadPool`. We’ll implement the code that sends the closure to the thread after we have `Worker` @@ -1184,7 +1186,7 @@ looking at the code in Listing 21-15. Ready? Here is Listing 21-15 with one way to make the preceding modifications. -Filename: src/lib.rs +src/lib.rs ``` use std::thread; @@ -1277,7 +1279,7 @@ in the `ThreadPool` instance, as shown in Listing 21-16. The `Job` struct doesn’t hold anything for now but will be the type of item we’re sending down the channel. -Filename: src/lib.rs +src/lib.rs ``` use std::{sync::mpsc, thread}; @@ -1313,12 +1315,12 @@ Listing 21-16: Modifying `ThreadPool` to store the sender of a channel that tran In `ThreadPool::new`, we create our new channel and have the pool hold the sender. This will successfully compile. -Let’s try passing a receiver of the channel into each worker as the thread pool -creates the channel. We know we want to use the receiver in the thread that the -workers spawn, so we’ll reference the `receiver` parameter in the closure. The -code in Listing 21-17 won’t quite compile yet. +Let’s try passing a receiver of the channel into each `Worker` as the thread +pool creates the channel. We know we want to use the receiver in the thread that +the `Worker` instances spawn, so we’ll reference the `receiver` parameter in the +closure. The code in Listing 21-17 won’t quite compile yet. -Filename: src/lib.rs +src/lib.rs ``` impl ThreadPool { @@ -1352,7 +1354,7 @@ impl Worker { } ``` -Listing 21-17: Passing the receiver to the workers +Listing 21-17: Passing the receiver to each `Worker` We’ve made some small and straightforward changes: we pass the receiver into `Worker::new`, and then we use it inside the closure. @@ -1394,7 +1396,8 @@ won’t work, as you’ll recall from Chapter 16: the channel implementation tha Rust provides is multiple *producer*, single *consumer*. This means we can’t just clone the consuming end of the channel to fix this code. We also don’t want to send a message multiple times to multiple consumers; we want one list -of messages with multiple workers such that each message gets processed once. +of messages with multiple `Worker` instances such that each message gets +processed once. Additionally, taking a job off the channel queue involves mutating the `receiver`, so the threads need a safe way to share and modify `receiver`; @@ -1402,11 +1405,11 @@ otherwise, we might get race conditions (as covered in Chapter 16). Recall the thread-safe smart pointers discussed in Chapter 16: to share ownership across multiple threads and allow the threads to mutate the value, we -need to use `Arc<Mutex<T>>`. The `Arc` type will let multiple workers own the -receiver, and `Mutex` will ensure that only one worker gets a job from the -receiver at a time. Listing 21-18 shows the changes we need to make. +need to use `Arc<Mutex<T>>`. The `Arc` type will let multiple `Worker` instances +own the receiver, and `Mutex` will ensure that only one `Worker` gets a job from +the receiver at a time. Listing 21-18 shows the changes we need to make. -Filename: src/lib.rs +src/lib.rs ``` use std::{ @@ -1445,24 +1448,24 @@ impl Worker { } ``` -Listing 21-18: Sharing the receiver among the workers using `Arc` and `Mutex` +Listing 21-18: Sharing the receiver among the `Worker` instances using `Arc` and `Mutex` In `ThreadPool::new`, we put the receiver in an `Arc` and a `Mutex`. For each -new worker, we clone the `Arc` to bump the reference count so the workers can -share ownership of the receiver. +new `Worker`, we clone the `Arc` to bump the reference count so the `Worker` +instances can share ownership of the receiver. With these changes, the code compiles! We’re getting there! -#### Implementing the `execute` Method +#### Implementing the execute Method Let’s finally implement the `execute` method on `ThreadPool`. We’ll also change `Job` from a struct to a type alias for a trait object that holds the type of -closure that `execute` receives. As discussed in the “Creating Type Synonyms -with Type Aliases” -section of Chapter 20, type aliases allow us to make long types shorter for -ease of use. Look at Listing 21-19. +closure that `execute` receives. As discussed in “Creating Type Synonyms with +Type Aliases” in +Chapter 20, type aliases allow us to make long types shorter for ease of use. +Look at Listing 21-19. -Filename: src/lib.rs +src/lib.rs ``` // --snip-- @@ -1496,13 +1499,13 @@ executing: our threads continue executing as long as the pool exists. The reason we use `unwrap` is that we know the failure case won’t happen, but the compiler doesn’t know that. -But we’re not quite done yet! In the worker, our closure being passed to +But we’re not quite done yet! In the `Worker`, our closure being passed to `thread::spawn` still only *references* the receiving end of the channel. Instead, we need the closure to loop forever, asking the receiving end of the channel for a job and running the job when it gets one. Let’s make the change shown in Listing 21-20 to `Worker::new`. -Filename: src/lib.rs +src/lib.rs ``` // --snip-- @@ -1522,7 +1525,7 @@ impl Worker { } ``` -Listing 21-20: Receiving and executing the jobs in the worker’s thread +Listing 21-20: Receiving and executing the jobs in the `Worker` instance’s thread Here, we first call `lock` on the `receiver` to acquire the mutex, and then we call `unwrap` to panic on any errors. Acquiring a lock might fail if the mutex @@ -1596,7 +1599,7 @@ overloaded if the server receives a lot of requests. If we make a request to thread run them. > Note: If you open */sleep* in multiple browser windows simultaneously, they -> might load one at a time in 5 second intervals. Some web browsers execute +> might load one at a time in five-second intervals. Some web browsers execute > multiple instances of the same request sequentially for caching reasons. This > limitation is not caused by our web server. @@ -1608,7 +1611,7 @@ different, if at all? What parts of the code would stay the same? After learning about the `while let` loop in Chapters 17 and 18, you might be wondering why we didn’t write the worker thread code as shown in Listing 21-21. -Filename: src/lib.rs +src/lib.rs ``` // --snip-- @@ -1642,10 +1645,10 @@ longer than intended if we aren’t mindful of the lifetime of the `MutexGuard<T>`. The code in Listing 21-20 that uses `let job = receiver.lock().unwrap().recv().unwrap();` works because with `let`, any -temporary values used in the expression on the right hand side of the equals +temporary values used in the expression on the right hand side of the equal sign are immediately dropped when the `let` statement ends. However, `while let` (and `if let` and `match`) does not drop temporary values until the end of the associated block. In Listing 21-21, the lock remains held for the duration -of the call to `job()`, meaning other workers cannot receive jobs. +of the call to `job()`, meaning other `Worker` instances cannot receive jobs. ## Graceful Shutdown and Cleanup @@ -1668,14 +1671,14 @@ One thing to notice as we go: none of this affects the parts of the code that handle executing the closures, so everything here would be just the same if we were using a thread pool for an async runtime. -### Implementing the `Drop` Trait on `ThreadPool` +### Implementing the Drop Trait on ThreadPool Let’s start with implementing `Drop` on our thread pool. When the pool is dropped, our threads should all join to make sure they finish their work. Listing 21-22 shows a first attempt at a `Drop` implementation; this code won’t quite work yet. -Filename: src/lib.rs +src/lib.rs ``` impl Drop for ThreadPool { @@ -1691,12 +1694,12 @@ impl Drop for ThreadPool { Listing 21-22: Joining each thread when the thread pool goes out of scope -First, we loop through each of the thread pool `workers`. We use `&mut` for -this because `self` is a mutable reference, and we also need to be able to -mutate `worker`. For each worker, we print a message saying that this -particular worker is shutting down, and then we call `join` on that worker’s -thread. If the call to `join` fails, we use `unwrap` to make Rust panic and go -into an ungraceful shutdown. +First, we loop through each of the thread pool `workers`. We use `&mut` for this +because `self` is a mutable reference, and we also need to be able to mutate +`worker`. For each worker, we print a message saying that this particular +`Worker` instance is shutting down, and then we call `join` on that `Worker` +instance’s thread. If the call to `join` fails, we use `unwrap` to make Rust +panic and go into an ungraceful shutdown. Here is the error we get when we compile this code: @@ -1729,24 +1732,24 @@ we did in Listing 18-15. If `Worker` held an `Option<thread::JoinHandle<()>>`, we could call the `take` method on the `Option` to move the value out of the `Some` variant and leave a `None` variant in its place. In other words, a `Worker` that is running would have a `Some` variant in `thread`, and when we -wanted to clean up a `Worker`, we would replace `Some` with `None` so the -`Worker` doesn’t have a thread to run. +wanted to clean up a `Worker`, we’d replace `Some` with `None` so the `Worker` +wouldn’t have a thread to run. However, the *only* time this would come up would be when dropping the `Worker`. -In exchange, we would have to deal with an `Option<thread::JoinHandle<()>>` -everywhere we access `worker.thread`. Idiomatic Rust uses `Option` quite a bit, -but when you find yourself wrapping something in `Option` as a workaround even -though you know the item will always be present, it is a good idea to look for -alternative approaches. They can make your code cleaner and less error-prone. +In exchange, we’d have to deal with an `Option<thread::JoinHandle<()>>` anywhere +we accessed `worker.thread`. Idiomatic Rust uses `Option` quite a bit, but when +you find yourself wrapping something you know will always be present in `Option` +as a workaround like this, it’s a good idea to look for alternative approaches. +They can make your code cleaner and less error-prone. -In this case, there is a better alternative: the `Vec::drain` method. It accepts +In this case, a better alternative exists: the `Vec::drain` method. It accepts a range parameter to specify which items to remove from the `Vec`, and returns an iterator of those items. Passing the `..` range syntax will remove *every* value from the `Vec`. So we need to update the `ThreadPool` `drop` implementation like this: -Filename: src/lib.rs +src/lib.rs ``` impl Drop for ThreadPool { @@ -1768,23 +1771,23 @@ code. ### Signaling to the Threads to Stop Listening for Jobs With all the changes we’ve made, our code compiles without any warnings. -However, the bad news is this code doesn’t function the way we want it to yet. -The key is the logic in the closures run by the threads of the `Worker` +However, the bad news is that this code doesn’t function the way we want it to +yet. The key is the logic in the closures run by the threads of the `Worker` instances: at the moment, we call `join`, but that won’t shut down the threads -because they `loop` forever looking for jobs. If we try to drop our -`ThreadPool` with our current implementation of `drop`, the main thread will -block forever waiting for the first thread to finish. +because they `loop` forever looking for jobs. If we try to drop our `ThreadPool` +with our current implementation of `drop`, the main thread will block forever, +waiting for the first thread to finish. To fix this problem, we’ll need a change in the `ThreadPool` `drop` implementation and then a change in the `Worker` loop. -First, we’ll change the `ThreadPool` `drop` implementation to explicitly drop +First we’ll change the `ThreadPool` `drop` implementation to explicitly drop the `sender` before waiting for the threads to finish. Listing 21-23 shows the -changes to `ThreadPool` to explicitly drop `sender`. Unlike with the `workers`, +changes to `ThreadPool` to explicitly drop `sender`. Unlike with the thread, here we *do* need to use an `Option` to be able to move `sender` out of `ThreadPool` with `Option::take`. -Filename: src/lib.rs +src/lib.rs ``` pub struct ThreadPool { @@ -1825,15 +1828,15 @@ impl Drop for ThreadPool { } ``` -Listing 21-23: Explicitly drop `sender` before joining the worker threads +Listing 21-23: Explicitly drop `sender` before joining the `Worker` threads Dropping `sender` closes the channel, which indicates no more messages will be -sent. When that happens, all the calls to `recv` that the workers do in the -infinite loop will return an error. In Listing 21-24, we change the `Worker` -loop to gracefully exit the loop in that case, which means the threads will -finish when the `ThreadPool` `drop` implementation calls `join` on them. +sent. When that happens, all the calls to `recv` that the `Worker` instances do +in the infinite loop will return an error. In Listing 21-24, we change the +`Worker` loop to gracefully exit the loop in that case, which means the threads +will finish when the `ThreadPool` `drop` implementation calls `join` on them. -Filename: src/lib.rs +src/lib.rs ``` impl Worker { @@ -1859,12 +1862,12 @@ impl Worker { } ``` -Listing 21-24: Explicitly break out of the loop when `recv` returns an error +Listing 21-24: Explicitly breaking out of the loop when `recv` returns an error To see this code in action, let’s modify `main` to accept only two requests before gracefully shutting down the server, as shown in Listing 21-25. -Filename: src/main.rs +src/main.rs ``` fn main() { @@ -1883,7 +1886,7 @@ fn main() { } ``` -Listing 21-25: Shut down the server after serving two requests by exiting the loop +Listing 21-25: Shutting down the server after serving two requests by exiting the loop You wouldn’t want a real-world web server to shut down after serving only two requests. This code just demonstrates that the graceful shutdown and cleanup is @@ -1925,21 +1928,22 @@ Shutting down worker 2 Shutting down worker 3 ``` -You might see a different ordering of workers and messages printed. We can see -how this code works from the messages: workers 0 and 3 got the first two -requests. The server stopped accepting connections after the second connection, -and the `Drop` implementation on `ThreadPool` starts executing before worker 3 -even starts its job. Dropping the `sender` disconnects all the workers and -tells them to shut down. The workers each print a message when they disconnect, -and then the thread pool calls `join` to wait for each worker thread to finish. +You might see a different ordering of `Worker` IDs and messages printed. We can +see how this code works from the messages: `Worker` instances 0 and 3 got the +first two requests. The server stopped accepting connections after the second +connection, and the `Drop` implementation on `ThreadPool` starts executing +before `Worker` 3 even starts its job. Dropping the `sender` disconnects all the +`Worker` instances and tells them to shut down. The `Worker` instances each +print a message when they disconnect, and then the thread pool calls `join` to +wait for each `Worker` thread to finish. Notice one interesting aspect of this particular execution: the `ThreadPool` -dropped the `sender`, and before any worker received an error, we tried to join -worker 0. Worker 0 had not yet gotten an error from `recv`, so the main thread -blocked waiting for worker 0 to finish. In the meantime, worker 3 received a -job and then all threads received an error. When worker 0 finished, the main -thread waited for the rest of the workers to finish. At that point, they had -all exited their loops and stopped. +dropped the `sender`, and before any `Worker` received an error, we tried to +join `Worker` 0. `Worker` 0 had not yet gotten an error from `recv`, so the main +thread blocked waiting for `Worker` 0 to finish. In the meantime, `Worker` 3 +received a job and then all threads received an error. When `Worker` 0 finished, +the main thread waited for the rest of the `Worker` instances to finish. At that +point, they had all exited their loops and stopped. Congrats! We’ve now completed our project; we have a basic web server that uses a thread pool to respond asynchronously. We’re able to perform a graceful @@ -1947,7 +1951,7 @@ shutdown of the server, which cleans up all the threads in the pool. Here’s the full code for reference: -Filename: src/main.rs +src/main.rs ``` use hello::ThreadPool; @@ -1999,7 +2003,7 @@ fn handle_connection(mut stream: TcpStream) { -Filename: src/lib.rs +src/lib.rs ``` use std::{ @@ -2113,6 +2117,6 @@ some ideas: Well done! You’ve made it to the end of the book! We want to thank you for joining us on this tour of Rust. You’re now ready to implement your own Rust -projects and help with other peoples’ projects. Keep in mind that there is a +projects and help with other people’s projects. Keep in mind that there is a welcoming community of other Rustaceans who would love to help you with any challenges you encounter on your Rust journey. diff --git a/nostarch/docx/appendix_a.docx b/nostarch/docx/appendix_a.docx index 7acdea45d2..769edd7253 100644 Binary files a/nostarch/docx/appendix_a.docx and b/nostarch/docx/appendix_a.docx differ diff --git a/nostarch/docx/appendix_b.docx b/nostarch/docx/appendix_b.docx index 5df5c20220..84bd34cfd2 100644 Binary files a/nostarch/docx/appendix_b.docx and b/nostarch/docx/appendix_b.docx differ diff --git a/nostarch/docx/appendix_c.docx b/nostarch/docx/appendix_c.docx index a48d87128c..fd82e3a6cf 100644 Binary files a/nostarch/docx/appendix_c.docx and b/nostarch/docx/appendix_c.docx differ diff --git a/nostarch/docx/appendix_d.docx b/nostarch/docx/appendix_d.docx index 94de2d332f..a5229f228f 100644 Binary files a/nostarch/docx/appendix_d.docx and b/nostarch/docx/appendix_d.docx differ diff --git a/nostarch/docx/appendix_e.docx b/nostarch/docx/appendix_e.docx index bfea271b66..a455df7cae 100644 Binary files a/nostarch/docx/appendix_e.docx and b/nostarch/docx/appendix_e.docx differ diff --git a/nostarch/docx/backports/Index.docx b/nostarch/docx/backports/Index.docx new file mode 100644 index 0000000000..360e24a87e Binary files /dev/null and b/nostarch/docx/backports/Index.docx differ diff --git a/nostarch/docx/chapter01.docx b/nostarch/docx/chapter01.docx index 11b97cc69f..2019fca789 100644 Binary files a/nostarch/docx/chapter01.docx and b/nostarch/docx/chapter01.docx differ diff --git a/nostarch/docx/chapter02.docx b/nostarch/docx/chapter02.docx index 4356804bd7..a6a22d6a9a 100644 Binary files a/nostarch/docx/chapter02.docx and b/nostarch/docx/chapter02.docx differ diff --git a/nostarch/docx/chapter03.docx b/nostarch/docx/chapter03.docx index 228ec53dbc..0d3442a81c 100644 Binary files a/nostarch/docx/chapter03.docx and b/nostarch/docx/chapter03.docx differ diff --git a/nostarch/docx/chapter04.docx b/nostarch/docx/chapter04.docx index 203e451c4a..6a8667c68c 100644 Binary files a/nostarch/docx/chapter04.docx and b/nostarch/docx/chapter04.docx differ diff --git a/nostarch/docx/chapter05.docx b/nostarch/docx/chapter05.docx index 16d68d10a1..a70e1c8f8e 100644 Binary files a/nostarch/docx/chapter05.docx and b/nostarch/docx/chapter05.docx differ diff --git a/nostarch/docx/chapter06.docx b/nostarch/docx/chapter06.docx index 7680ec924e..9b6f9f2e54 100644 Binary files a/nostarch/docx/chapter06.docx and b/nostarch/docx/chapter06.docx differ diff --git a/nostarch/docx/chapter07.docx b/nostarch/docx/chapter07.docx index e2955957a1..3db5717b8c 100644 Binary files a/nostarch/docx/chapter07.docx and b/nostarch/docx/chapter07.docx differ diff --git a/nostarch/docx/chapter08.docx b/nostarch/docx/chapter08.docx index 761b2e28ec..07c3e089d5 100644 Binary files a/nostarch/docx/chapter08.docx and b/nostarch/docx/chapter08.docx differ diff --git a/nostarch/docx/chapter09.docx b/nostarch/docx/chapter09.docx index a583322989..f963350945 100644 Binary files a/nostarch/docx/chapter09.docx and b/nostarch/docx/chapter09.docx differ diff --git a/nostarch/docx/chapter10.docx b/nostarch/docx/chapter10.docx index 6fba58f2d1..13e92a19d4 100644 Binary files a/nostarch/docx/chapter10.docx and b/nostarch/docx/chapter10.docx differ diff --git a/nostarch/docx/chapter11.docx b/nostarch/docx/chapter11.docx index a347392358..c768356968 100644 Binary files a/nostarch/docx/chapter11.docx and b/nostarch/docx/chapter11.docx differ diff --git a/nostarch/docx/chapter12.docx b/nostarch/docx/chapter12.docx index eba55b8665..7f662df2f0 100644 Binary files a/nostarch/docx/chapter12.docx and b/nostarch/docx/chapter12.docx differ diff --git a/nostarch/docx/chapter13.docx b/nostarch/docx/chapter13.docx index 39dae6b332..acd86e8391 100644 Binary files a/nostarch/docx/chapter13.docx and b/nostarch/docx/chapter13.docx differ diff --git a/nostarch/docx/chapter14.docx b/nostarch/docx/chapter14.docx index cad4de5726..c256f41730 100644 Binary files a/nostarch/docx/chapter14.docx and b/nostarch/docx/chapter14.docx differ diff --git a/nostarch/docx/chapter15.docx b/nostarch/docx/chapter15.docx index e0bda23962..444900c045 100644 Binary files a/nostarch/docx/chapter15.docx and b/nostarch/docx/chapter15.docx differ diff --git a/nostarch/docx/chapter16.docx b/nostarch/docx/chapter16.docx index 75b12f3fdb..b29d4bda6e 100644 Binary files a/nostarch/docx/chapter16.docx and b/nostarch/docx/chapter16.docx differ diff --git a/nostarch/docx/chapter17.docx b/nostarch/docx/chapter17.docx index 3a5bd65433..49475203a1 100644 Binary files a/nostarch/docx/chapter17.docx and b/nostarch/docx/chapter17.docx differ diff --git a/nostarch/docx/chapter18.docx b/nostarch/docx/chapter18.docx index a641976d78..b780301b9b 100644 Binary files a/nostarch/docx/chapter18.docx and b/nostarch/docx/chapter18.docx differ diff --git a/nostarch/docx/chapter19.docx b/nostarch/docx/chapter19.docx index d834fba1b3..25be8df090 100644 Binary files a/nostarch/docx/chapter19.docx and b/nostarch/docx/chapter19.docx differ diff --git a/nostarch/docx/chapter20.docx b/nostarch/docx/chapter20.docx index 36106943be..efb4a2b71f 100644 Binary files a/nostarch/docx/chapter20.docx and b/nostarch/docx/chapter20.docx differ diff --git a/nostarch/docx/chapter21.docx b/nostarch/docx/chapter21.docx new file mode 100644 index 0000000000..6766193610 Binary files /dev/null and b/nostarch/docx/chapter21.docx differ diff --git a/nostarch/docx/frontmatter.docx b/nostarch/docx/frontmatter.docx index 65d41f2519..e7e2e1a8c2 100644 Binary files a/nostarch/docx/frontmatter.docx and b/nostarch/docx/frontmatter.docx differ diff --git a/packages/tools/src/bin/link2print.rs b/packages/tools/src/bin/link2print.rs index 6212ca012d..46244a2df0 100644 --- a/packages/tools/src/bin/link2print.rs +++ b/packages/tools/src/bin/link2print.rs @@ -30,12 +30,11 @@ fn parse_references(buffer: String) -> (String, HashMap<String, String>) { .unwrap(); let output = re .replace_all(&buffer, |caps: &Captures<'_>| { - let key = caps.get(1).unwrap().as_str().to_uppercase(); + let key_def = caps.get(1).unwrap().as_str(); + let key = key_def.to_uppercase(); let val = caps.get(2).unwrap().as_str().to_string(); if ref_map.insert(key, val).is_some() { - panic!( - "Did not expect markdown page to have duplicate reference" - ); + panic!("unexpected page had duplicate reference for {key_def}",); } "".to_string() }) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 683b997667..286cb46db8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -89,7 +89,7 @@ - [Smart Pointers](ch15-00-smart-pointers.md) - [Using `Box<T>` to Point to Data on the Heap](ch15-01-box.md) - - [Treating Smart Pointers Like Regular References with the `Deref` Trait](ch15-02-deref.md) + - [Treating Smart Pointers Like Regular References with `Deref`](ch15-02-deref.md) - [Running Code on Cleanup with the `Drop` Trait](ch15-03-drop.md) - [`Rc<T>`, the Reference Counted Smart Pointer](ch15-04-rc.md) - [`RefCell<T>` and the Interior Mutability Pattern](ch15-05-interior-mutability.md) @@ -99,7 +99,7 @@ - [Using Threads to Run Code Simultaneously](ch16-01-threads.md) - [Using Message Passing to Transfer Data Between Threads](ch16-02-message-passing.md) - [Shared-State Concurrency](ch16-03-shared-state.md) - - [Extensible Concurrency with the `Sync` and `Send` Traits](ch16-04-extensible-concurrency-sync-and-send.md) + - [Extensible Concurrency with the `Send` and `Sync` Traits](ch16-04-extensible-concurrency-sync-and-send.md) - [Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams](ch17-00-async-await.md) - [Futures and the Async Syntax](ch17-01-futures-and-syntax.md) diff --git a/src/appendix-01-keywords.md b/src/appendix-01-keywords.md index 8592218f82..15f6f5b9ae 100644 --- a/src/appendix-01-keywords.md +++ b/src/appendix-01-keywords.md @@ -131,9 +131,9 @@ identifier names, as well as lets us integrate with programs written in a language where these words aren’t keywords. In addition, raw identifiers allow you to use libraries written in a different Rust edition than your crate uses. For example, `try` isn’t a keyword in the 2015 edition but is in the 2018, 2021, -and 2024 editions. If you depend on a library that’s written using the 2015 +and 2024 editions. If you depend on a library that is written using the 2015 edition and has a `try` function, you’ll need to use the raw identifier syntax, -`r#try` in this case, to call that function from your 2018 edition code. See -[Appendix E][appendix-e]<!-- ignore --> for more information on editions. +`r#try` in this case, to call that function from your code on later editions. +See [Appendix E][appendix-e]<!-- ignore --> for more information on editions. [appendix-e]: appendix-05-editions.html diff --git a/src/appendix-03-derivable-traits.md b/src/appendix-03-derivable-traits.md index 5cffa46cf8..8e74b948c2 100644 --- a/src/appendix-03-derivable-traits.md +++ b/src/appendix-03-derivable-traits.md @@ -16,9 +16,9 @@ library that you can use with `derive`. Each section covers: If you want different behavior from that provided by the `derive` attribute, consult the [standard library documentation](../std/index.html)<!-- ignore --> -for each trait for details of how to manually implement them. +for each trait for details on how to manually implement them. -These traits listed here are the only ones defined by the standard library that +The traits listed here are the only ones defined by the standard library that can be implemented on your types using `derive`. Other traits defined in the standard library don’t have sensible default behavior, so it’s up to you to implement them in the way that makes sense for what you’re trying to accomplish. @@ -45,9 +45,10 @@ The `Debug` trait allows you to print instances of a type for debugging purposes, so you and other programmers using your type can inspect an instance at a particular point in a program’s execution. -The `Debug` trait is required, for example, in using the `assert_eq!` macro. -This macro prints the values of instances given as arguments if the equality -assertion fails so programmers can see why the two instances weren’t equal. +The `Debug` trait is required, for example, in the use of the `assert_eq!` +macro. This macro prints the values of instances given as arguments if the +equality assertion fails so programmers can see why the two instances weren’t +equal. ### `PartialEq` and `Eq` for Equality Comparisons @@ -83,9 +84,9 @@ that also implement `PartialEq`. Deriving `PartialOrd` implements the `partial_cmp` method, which returns an `Option<Ordering>` that will be `None` when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though -most values of that type can be compared, is the `NaN` floating point value. -Calling `partial_cmp` with any floating point number and the `NaN` floating -point value will return `None`. +most values of that type can be compared, is the not-a-number (`NaN`) floating +point value. Calling `partial_cmp` with any floating-point number and the `NaN` +floating-point value will return `None`. When derived on structs, `PartialOrd` compares two instances by comparing the value in each field in the order in which the fields appear in the struct @@ -122,7 +123,7 @@ fields or values in the type must also implement `Clone` to derive `Clone`. An example of when `Clone` is required is when calling the `to_vec` method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from `to_vec` will need to own its instances, so `to_vec` calls -`clone` on each item. Thus, the type stored in the slice must implement `Clone`. +`clone` on each item. Thus the type stored in the slice must implement `Clone`. The `Copy` trait allows you to duplicate a value by only copying bits stored on the stack; no arbitrary code is necessary. See [“Stack-Only Data: @@ -166,7 +167,7 @@ meaning all fields or values in the type must also implement `Default` to derive `Default`. The `Default::default` function is commonly used in combination with the struct -update syntax discussed in [“Creating Instances From Other Instances With Struct +update syntax discussed in [“Creating Instances from Other Instances with Struct Update Syntax”][creating-instances-from-other-instances-with-struct-update-syntax]<!-- ignore --> in Chapter 5. You can customize a few fields of a struct and then set diff --git a/src/appendix-04-useful-development-tools.md b/src/appendix-04-useful-development-tools.md index 5ad9c6db7a..229ccd09e3 100644 --- a/src/appendix-04-useful-development-tools.md +++ b/src/appendix-04-useful-development-tools.md @@ -10,12 +10,19 @@ The `rustfmt` tool reformats your code according to the community code style. Many collaborative projects use `rustfmt` to prevent arguments about which style to use when writing Rust: everyone formats their code using the tool. -To install `rustfmt`, enter the following: +Rust installations include rustfmt by default, so you should already have the +programs `rustfmt` and `cargo-fmt` on your system. These two commands are +analogous to `rustc` and `cargo` in that `rustfmt` allows finer-grained control +and `cargo-fmt` understands conventions of a project that uses Cargo. To format +any Cargo project, enter the following: -```console -$ rustup component add rustfmt +```sh +$ cargo fmt ``` +Running this command reformats all the Rust code in the current crate. This +should only change the code style, not the code semantics. + This command gives you `rustfmt` and `cargo-fmt`, similar to how Rust gives you both `rustc` and `cargo`. To format any Cargo project, enter the following: @@ -31,7 +38,7 @@ on `rustfmt`, see [its documentation][rustfmt]. ### Fix Your Code with `rustfix` -The rustfix tool is included with Rust installations and can automatically fix +The `rustfix` tool is included with Rust installations and can automatically fix compiler warnings that have a clear way to correct the problem that’s likely what you want. It’s likely you’ve seen compiler warnings before. For example, consider this code: @@ -45,8 +52,8 @@ fn main() { } ``` -Here, we’re defining variable `x` as mutable, but we never actually mutate it. -Rust warns us about that: +Here, we’re defining the variable `x` as mutable, but we never actually mutate +it. Rust warns us about that: ```console $ cargo build @@ -93,13 +100,8 @@ different Rust editions. Editions are covered in [Appendix E][editions]. ### More Lints with Clippy The Clippy tool is a collection of lints to analyze your code so you can catch -common mistakes and improve your Rust code. - -To install Clippy, enter the following: - -```console -$ rustup component add clippy -``` +common mistakes and improve your Rust code. Clippy is included with standard +Rust installations. To run Clippy’s lints on any Cargo project, enter the following: @@ -110,7 +112,7 @@ $ cargo clippy For example, say you write a program that uses an approximation of a mathematical constant, such as pi, as this program does: -<span class="filename">Filename: src/main.rs</span> +<Listing file-name="src/main.rs"> ```rust fn main() { @@ -120,6 +122,8 @@ fn main() { } ``` +</Listing> + Running `cargo clippy` on this project results in this error: ```text @@ -139,7 +143,7 @@ defined, and that your program would be more correct if you used the constant instead. You would then change your code to use the `PI` constant. The following code doesn’t result in any errors or warnings from Clippy: -<span class="filename">Filename: src/main.rs</span> +<Listing file-name="src/main.rs"> ```rust fn main() { @@ -149,6 +153,8 @@ fn main() { } ``` +</Listing> + For more information on Clippy, see [its documentation][clippy]. [clippy]: https://github.com/rust-lang/rust-clippy diff --git a/src/appendix-05-editions.md b/src/appendix-05-editions.md index 9d3834043b..0b8c4a3a12 100644 --- a/src/appendix-05-editions.md +++ b/src/appendix-05-editions.md @@ -10,7 +10,7 @@ while, all of these tiny changes add up. But from release to release, it can be difficult to look back and say, “Wow, between Rust 1.10 and Rust 1.31, Rust has changed a lot!” -Every two or three years, the Rust team produces a new Rust _edition_. Each +Every three years or so, the Rust team produces a new Rust _edition_. Each edition brings together the features that have landed into a clear package with fully updated documentation and tooling. New editions ship as part of the usual six-week release process. diff --git a/src/ch00-00-introduction.md b/src/ch00-00-introduction.md index 4689d6acd3..f3916dceda 100644 --- a/src/ch00-00-introduction.md +++ b/src/ch00-00-introduction.md @@ -143,16 +143,16 @@ traits that enable their functionality. In Chapter 16, we’ll walk through different models of concurrent programming and talk about how Rust helps you to program in multiple threads fearlessly. In -Chapter 17, we will build on that by exploring Rust’s async and await syntax and -the lightweight concurrency model they support. +Chapter 17, we build on that by exploring Rust’s async and await syntax, along +with tasks, futures, and streams, and the lightweight concurrency model they +enable. Chapter 18 looks at how Rust idioms compare to object-oriented programming -principles you might be familiar with. - -Chapter 19 is a reference on patterns and pattern matching, which are powerful -ways of expressing ideas throughout Rust programs. Chapter 20 contains a -smorgasbord of advanced topics of interest, including unsafe Rust, macros, and -more about lifetimes, traits, types, functions, and closures. +principles you might be familiar with. Chapter 19 is a reference on patterns and +pattern matching, which are powerful ways of expressing ideas throughout Rust +programs. Chapter 20 contains a smorgasbord of advanced topics of interest, +including unsafe Rust, macros, and more about lifetimes, traits, types, +functions, and closures. In Chapter 21, we’ll complete a project in which we’ll implement a low-level multithreaded web server! diff --git a/src/ch03-05-control-flow.md b/src/ch03-05-control-flow.md index ac89726fde..c6c89304e9 100644 --- a/src/ch03-05-control-flow.md +++ b/src/ch03-05-control-flow.md @@ -283,7 +283,7 @@ that Rust has a built-in language construct for it, called a `while` loop. In Listing 3-3, we use `while` to loop the program three times, counting down each time, and then, after the loop, print a message and exit. -<Listing number="3-3" file-name="src/main.rs" caption="Using a `while` loop to run code while a condition holds true"> +<Listing number="3-3" file-name="src/main.rs" caption="Using a `while` loop to run code while a condition evaluates to `true`"> ```rust {{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-03/src/main.rs}} diff --git a/src/ch05-01-defining-structs.md b/src/ch05-01-defining-structs.md index 8fd30b2605..20e7b02ca1 100644 --- a/src/ch05-01-defining-structs.md +++ b/src/ch05-01-defining-structs.md @@ -23,8 +23,8 @@ struct that stores information about a user account. To use a struct after we’ve defined it, we create an _instance_ of that struct by specifying concrete values for each of the fields. We create an instance by -stating the name of the struct and then add curly brackets containing _key: -value_ pairs, where the keys are the names of the fields and the values are the +stating the name of the struct and then add curly brackets containing _`key: +value`_ pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill diff --git a/src/ch05-02-example-structs.md b/src/ch05-02-example-structs.md index 49a109c6cf..36aea3a6e0 100644 --- a/src/ch05-02-example-structs.md +++ b/src/ch05-02-example-structs.md @@ -78,7 +78,7 @@ parts, as shown in Listing 5-10. </Listing> -Here we’ve defined a struct and named it `Rectangle`. Inside the curly +Here, we’ve defined a struct and named it `Rectangle`. Inside the curly brackets, we defined the fields as `width` and `height`, both of which have type `u32`. Then, in `main`, we created a particular instance of `Rectangle` that has a width of `30` and a height of `50`. diff --git a/src/ch06-03-if-let.md b/src/ch06-03-if-let.md index 9d76c4af32..3a86e8c53c 100644 --- a/src/ch06-03-if-let.md +++ b/src/ch06-03-if-let.md @@ -62,7 +62,7 @@ Or we could use an `if let` and `else` expression, like this: {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-14-count-and-announce-if-let-else/src/main.rs:here}} ``` -## Staying on the “happy path” with `let else` +## Staying on the “Happy Path” with `let...else` One common pattern is to perform some computation when a value is present and return a default value otherwise. Continuing on with our example of coins with a @@ -77,7 +77,7 @@ age of a state, like so: Then we might use `if let` to match on the type of coin, introducing a `state` variable within the body of the condition, as in Listing 6-7. -<Listing number="6-7" caption="Using" file-name="src/main.rs"> +<Listing number="6-7" caption="Checking whether a state existing in 1900 by using conditionals nested inside an `if let`." file-name="src/main.rs"> ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-07/src/main.rs:describe}} @@ -90,7 +90,7 @@ statement, and if the work to be done is more complicated, it might be hard to follow exactly how the top-level branches relate. We could also take advantage of the fact that expressions produce a value either to produce the `state` from the `if let` or to return early, as in Listing 6-8. (You could do similar with a -`match`, of course!) +`match`, too.) <Listing number="6-8" caption="Using `if let` to produce a value or return early." file-name="src/main.rs"> @@ -103,19 +103,19 @@ the `if let` or to return early, as in Listing 6-8. (You could do similar with a This is a bit annoying to follow in its own way, though! One branch of the `if let` produces a value, and the other one returns from the function entirely. -To make this common pattern nicer to express, Rust has `let`-`else`. The -`let`-`else` syntax takes a pattern on the left side and an expression on the +To make this common pattern nicer to express, Rust has `let...else`. The +`let...else` syntax takes a pattern on the left side and an expression on the right, very similar to `if let`, but it does not have an `if` branch, only an `else` branch. If the pattern matches, it will bind the value from the pattern in the outer scope. If the pattern does _not_ match, the program will flow into the `else` arm, which must return from the function. -In Listing 6-9, you can see how Listing 6-8 looks when using `let`-`else` in +In Listing 6-9, you can see how Listing 6-8 looks when using `let...else` in place of `if let`. Notice that it stays “on the happy path” in the main body of the function this way, without having significantly different control flow for two branches the way the `if let` did. -<Listing number="6-9" caption="Using `let`-`else` to clarify the flow through the function." file-name="src/main.rs"> +<Listing number="6-9" caption="Using `let...else` to clarify the flow through the function." file-name="src/main.rs"> ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-09/src/main.rs:describe}} @@ -124,8 +124,8 @@ two branches the way the `if let` did. </Listing> If you have a situation in which your program has logic that is too verbose to -express using a `match`, remember that `if let` and `let else` are in your Rust -toolbox as well. +express using a `match`, remember that `if let` and `let...else` are in your +Rust toolbox as well. ## Summary diff --git a/src/ch07-01-packages-and-crates.md b/src/ch07-01-packages-and-crates.md index 1f91b69db6..5a6e35588a 100644 --- a/src/ch07-01-packages-and-crates.md +++ b/src/ch07-01-packages-and-crates.md @@ -32,9 +32,11 @@ build those crates. Cargo is actually a package that contains the binary crate for the command line tool you’ve been using to build your code. The Cargo package also contains a library crate that the binary crate depends on. Other projects can depend on the Cargo library crate to use the same logic the Cargo -command line tool uses. A package can contain as many binary crates as you -like, but at most only one library crate. A package must contain at least one -crate, whether that’s a library or binary crate. +command line tool uses. + +A package can contain as many binary crates as you like, but at most only one +library crate. A package must contain at least one crate, whether that’s a +library or binary crate. Let’s walk through what happens when we create a package. First we enter the command `cargo new my-project`: diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index e47a49d570..ddabaee7b4 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -429,11 +429,11 @@ _lifetime elision rules_. These aren’t rules for programmers to follow; they a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly. -The elision rules don’t provide full inference. If there is still ambiguity as -to what lifetimes the references have after Rust applies the rules, the +The elision rules don’t provide full inference. If there is still ambiguity +about what lifetimes the references have after Rust applies the rules, the compiler won’t guess what the lifetime of the remaining references should be. -Instead of guessing, the compiler will give you an error that you can resolve -by adding the lifetime annotations. +Instead of guessing, the compiler will give you an error that you can resolve by +adding the lifetime annotations. Lifetimes on function or method parameters are called _input lifetimes_, and lifetimes on return values are called _output lifetimes_. diff --git a/src/ch11-01-writing-tests.md b/src/ch11-01-writing-tests.md index 63afae0930..6a5fa0a031 100644 --- a/src/ch11-01-writing-tests.md +++ b/src/ch11-01-writing-tests.md @@ -327,7 +327,7 @@ assertion functions are called `expected` and `actual`, and the order in which we specify the arguments matters. However, in Rust, they’re called `left` and `right`, and the order in which we specify the value we expect and the value the code produces doesn’t matter. We could write the assertion in this test as -`assert_eq!(4, result)`, which would result in the same failure message that +`assert_eq!(add_two(2), result)`, which would result in the same failure message that displays `` assertion failed: `(left == right)` ``. The `assert_ne!` macro will pass if the two values we give it are not equal and diff --git a/src/ch12-05-working-with-environment-variables.md b/src/ch12-05-working-with-environment-variables.md index b91b00ba8a..df06d6cce9 100644 --- a/src/ch12-05-working-with-environment-variables.md +++ b/src/ch12-05-working-with-environment-variables.md @@ -56,8 +56,8 @@ they’ll be the same case when we check whether the line contains the query. </Listing> First we lowercase the `query` string and store it in a new variable with the -same name, shadowing the original. Calling `to_lowercase` on the query is -necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`, +same name, shadowing the original `query`. Calling `to_lowercase` on the query +is necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`, `"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be insensitive to the case. While `to_lowercase` will handle basic Unicode, it won’t be 100% accurate. If we were writing a real application, we’d want to do a diff --git a/src/ch13-00-functional-features.md b/src/ch13-00-functional-features.md index 8eeccf2b24..3332c4d986 100644 --- a/src/ch13-00-functional-features.md +++ b/src/ch13-00-functional-features.md @@ -15,7 +15,7 @@ More specifically, we’ll cover: - _Closures_, a function-like construct you can store in a variable - _Iterators_, a way of processing a series of elements - How to use closures and iterators to improve the I/O project in Chapter 12 -- The performance of closures and iterators (Spoiler alert: they’re faster than +- The performance of closures and iterators (spoiler alert: they’re faster than you might think!) We’ve already covered some other Rust features, such as pattern matching and diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 4aa336e1fe..263586aaf2 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -2,7 +2,7 @@ <a id="closures-anonymous-functions-that-can-capture-their-environment"></a> -## Closures: Anonymous Functions that Capture Their Environment +## Closures: Anonymous Functions That Capture Their Environment Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then @@ -50,7 +50,7 @@ to distribute for this limited-edition promotion. We call the `giveaway` method for a user with a preference for a red shirt and a user without any preference. Again, this code could be implemented in many ways, and here, to focus on -closures, we’ve stuck to concepts you’ve already learned except for the body of +closures, we’ve stuck to concepts you’ve already learned, except for the body of the `giveaway` method that uses a closure. In the `giveaway` method, we get the user preference as a parameter of type `Option<ShirtColor>` and call the `unwrap_or_else` method on `user_preference`. The [`unwrap_or_else` method on @@ -283,9 +283,9 @@ implement one, two, or all three of these `Fn` traits, in an additive fashion, depending on how the closure’s body handles the values: 1. `FnOnce` applies to closures that can be called once. All closures implement - at least this trait, because all closures can be called. A closure that - moves captured values out of its body will only implement `FnOnce` and none - of the other `Fn` traits, because it can only be called once. + at least this trait because all closures can be called. A closure that moves + captured values out of its body will only implement `FnOnce` and none of the + other `Fn` traits, because it can only be called once. 2. `FnMut` applies to closures that don’t move captured values out of their body, but that might mutate the captured values. These closures can be called more than once. @@ -380,7 +380,7 @@ compiler won’t let us use this closure with `sort_by_key`: This is a contrived, convoluted way (that doesn’t work) to try and count the number of times `sort_by_key` calls the closure when sorting `list`. This code attempts to do this counting by pushing `value`—a `String` from the closure’s -environment—into the `sort_operations` vector. The closure captures `value` +environment—into the `sort_operations` vector. The closure captures `value` and then moves `value` out of the closure by transferring ownership of `value` to the `sort_operations` vector. This closure can be called once; trying to call it a second time wouldn’t work because `value` would no longer be in the @@ -395,12 +395,11 @@ implement `FnMut`: The error points to the line in the closure body that moves `value` out of the environment. To fix this, we need to change the closure body so that it doesn’t -move values out of the environment. To count the number of times the closure -is called, keeping a counter in the environment and incrementing its value in -the closure body is a more straightforward way to calculate that. The closure -in Listing 13-9 works with `sort_by_key` because it is only capturing a mutable -reference to the `num_sort_operations` counter and can therefore be called more -than once: +move values out of the environment. Keeping a counter in the environment and +incrementing its value in the closure body is a more straightforward way to +count the number of times the closure is called. The closure in Listing 13-9 +works with `sort_by_key` because it is only capturing a mutable reference to the +`num_sort_operations` counter and can therefore be called more than once: <Listing number="13-9" file-name="src/main.rs" caption="Using an `FnMut` closure with `sort_by_key` is allowed"> diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 65573f114c..5abca98c98 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -44,7 +44,7 @@ you would likely write this same functionality by starting a variable at index incrementing the variable value in a loop until it reached the total number of items in the vector. -Iterators handle all that logic for you, cutting down on repetitive code you +Iterators handle all of that logic for you, cutting down on repetitive code you could potentially mess up. Iterators give you more flexibility to use the same logic with many different kinds of sequences, not just data structures you can index into, like vectors. Let’s examine how iterators do that. @@ -64,7 +64,7 @@ pub trait Iterator { } ``` -Notice this definition uses some new syntax: `type Item` and `Self::Item`, +Notice that this definition uses some new syntax: `type Item` and `Self::Item`, which are defining an _associated type_ with this trait. We’ll talk about associated types in depth in Chapter 20. For now, all you need to know is that this code says implementing the `Iterator` trait requires that you also define @@ -73,7 +73,7 @@ method. In other words, the `Item` type will be the type returned from the iterator. The `Iterator` trait only requires implementors to define one method: the -`next` method, which returns one item of the iterator at a time wrapped in +`next` method, which returns one item of the iterator at a time, wrapped in `Some` and, when iteration is over, returns `None`. We can call the `next` method on iterators directly; Listing 13-12 demonstrates @@ -102,7 +102,7 @@ ownership of `v1` and returns owned values, we can call `into_iter` instead of `iter`. Similarly, if we want to iterate over mutable references, we can call `iter_mut` instead of `iter`. -### Methods that Consume the Iterator +### Methods That Consume the Iterator The `Iterator` trait has a number of different methods with default implementations provided by the standard library; you can find out about these @@ -111,12 +111,12 @@ trait. Some of these methods call the `next` method in their definition, which is why you’re required to implement the `next` method when implementing the `Iterator` trait. -Methods that call `next` are called _consuming adapters_, because calling them +Methods that call `next` are called _consuming adapters_ because calling them uses up the iterator. One example is the `sum` method, which takes ownership of the iterator and iterates through the items by repeatedly calling `next`, thus consuming the iterator. As it iterates through, it adds each item to a running total and returns the total when iteration is complete. Listing 13-13 has a -test illustrating a use of the `sum` method: +test illustrating a use of the `sum` method. <Listing number="13-13" file-name="src/lib.rs" caption="Calling the `sum` method to get the total of all items in the iterator"> @@ -161,12 +161,12 @@ we need to consume the iterator here. To fix this warning and consume the iterator, we’ll use the `collect` method, which we used in Chapter 12 with `env::args` in Listing 12-1. This method -consumes the iterator and collects the resulting values into a collection data +consumes the iterator and collects the resultant values into a collection data type. In Listing 13-15, we collect the results of iterating over the iterator that’s returned from the call to `map` into a vector. This vector will end up -containing each item from the original vector incremented by 1. +containing each item from the original vector, incremented by 1. <Listing number="13-15" file-name="src/main.rs" caption="Calling the `map` method to create a new iterator and then calling the `collect` method to consume the new iterator and create a vector"> @@ -185,7 +185,7 @@ You can chain multiple calls to iterator adapters to perform complex actions in a readable way. But because all iterators are lazy, you have to call one of the consuming adapter methods to get results from calls to iterator adapters. -### Using Closures that Capture Their Environment +### Using Closures That Capture Their Environment Many iterator adapters take closures as arguments, and commonly the closures we’ll specify as arguments to iterator adapters will be closures that capture diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index 489bdc10fa..8a846db120 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -11,7 +11,7 @@ In Listing 12-6, we added code that took a slice of `String` values and created an instance of the `Config` struct by indexing into the slice and cloning the values, allowing the `Config` struct to own those values. In Listing 13-17, we’ve reproduced the implementation of the `Config::build` function as it was -in Listing 12-23: +in Listing 12-23. <Listing number="13-17" file-name="src/lib.rs" caption="Reproduction of the `Config::build` function from Listing 12-23"> @@ -98,7 +98,7 @@ iterating over it, we can add the `mut` keyword into the specification of the Next, we’ll fix the body of `Config::build`. Because `args` implements the `Iterator` trait, we know we can call the `next` method on it! Listing 13-20 -updates the code from Listing 12-23 to use the `next` method: +updates the code from Listing 12-23 to use the `next` method. <Listing number="13-20" file-name="src/lib.rs" caption="Changing the body of `Config::build` to use iterator methods"> @@ -110,11 +110,11 @@ updates the code from Listing 12-23 to use the `next` method: Remember that the first value in the return value of `env::args` is the name of the program. We want to ignore that and get to the next value, so first we call -`next` and do nothing with the return value. Second, we call `next` to get the -value we want to put in the `query` field of `Config`. If `next` returns a -`Some`, we use a `match` to extract the value. If it returns `None`, it means -not enough arguments were given and we return early with an `Err` value. We do -the same thing for the `file_path` value. +`next` and do nothing with the return value. Then we call `next` to get the +value we want to put in the `query` field of `Config`. If `next` returns `Some`, +we use a `match` to extract the value. If it returns `None`, it means not enough +arguments were given and we return early with an `Err` value. We do the same +thing for the `file_path` value. ### Making Code Clearer with Iterator Adapters @@ -146,7 +146,7 @@ concurrent access to the `results` vector. Listing 13-22 shows this change: Recall that the purpose of the `search` function is to return all lines in `contents` that contain the `query`. Similar to the `filter` example in Listing -13-16, this code uses the `filter` adapter to keep only the lines that +13-16, this code uses the `filter` adapter to keep only the lines for which `line.contains(query)` returns `true` for. We then collect the matching lines into another vector with `collect`. Much simpler! Feel free to make the same change to use iterator methods in the `search_case_insensitive` function as @@ -166,7 +166,6 @@ that are unique to this code, such as the filtering condition each element in the iterator must pass. But are the two implementations truly equivalent? The intuitive assumption -might be that the more low-level loop will be faster. Let’s talk about -performance. +might be that the lower-level loop will be faster. Let’s talk about performance. [impl-trait]: ch10-02-traits.html#traits-as-parameters diff --git a/src/ch13-04-performance.md b/src/ch13-04-performance.md index 3889204de6..a5a9720a45 100644 --- a/src/ch13-04-performance.md +++ b/src/ch13-04-performance.md @@ -24,7 +24,7 @@ various sizes as the `contents`, different words and words of different lengths as the `query`, and all kinds of other variations. The point is this: iterators, although a high-level abstraction, get compiled down to roughly the same code as if you’d written the lower-level code yourself. Iterators are one -of Rust’s _zero-cost abstractions_, by which we mean using the abstraction +of Rust’s _zero-cost abstractions_, by which we mean that using the abstraction imposes no additional runtime overhead. This is analogous to how Bjarne Stroustrup, the original designer and implementor of C++, defines _zero-overhead_ in “Foundations of C++” (2012): @@ -76,7 +76,7 @@ the loop. All of the coefficients get stored in registers, which means accessing the values is very fast. There are no bounds checks on the array access at runtime. -All these optimizations that Rust is able to apply make the resulting code +All these optimizations that Rust is able to apply make the resultant code extremely efficient. Now that you know this, you can use iterators and closures without fear! They make code seem like it’s higher level but don’t impose a runtime performance penalty for doing so. diff --git a/src/ch14-01-release-profiles.md b/src/ch14-01-release-profiles.md index 6dd52c6425..387298332e 100644 --- a/src/ch14-01-release-profiles.md +++ b/src/ch14-01-release-profiles.md @@ -47,7 +47,7 @@ opt-level = 3 The `opt-level` setting controls the number of optimizations Rust will apply to your code, with a range of 0 to 3. Applying more optimizations extends compiling time, so if you’re in development and compiling your code often, -you’ll want fewer optimizations to compile faster even if the resulting code +you’ll want fewer optimizations to compile faster even if the resultant code runs slower. The default `opt-level` for `dev` is therefore `0`. When you’re ready to release your code, it’s best to spend more time compiling. You’ll only compile in release mode once, but you’ll run the compiled program many times, diff --git a/src/ch14-02-publishing-to-crates-io.md b/src/ch14-02-publishing-to-crates-io.md index 2d8645ea59..3437e8e520 100644 --- a/src/ch14-02-publishing-to-crates-io.md +++ b/src/ch14-02-publishing-to-crates-io.md @@ -81,7 +81,8 @@ test` will run the code examples in your documentation as tests! Nothing is better than documentation with examples. But nothing is worse than examples that don’t work because the code has changed since the documentation was written. If we run `cargo test` with the documentation for the `add_one` -function from Listing 14-1, we will see a section in the test results like this: +function from Listing 14-1, we will see a section in the test results that looks +like this: <!-- manual-regeneration cd listings/ch14-more-about-cargo/listing-14-01/ @@ -98,7 +99,7 @@ test src/lib.rs - add_one (line 5) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s ``` -Now if we change either the function or the example so the `assert_eq!` in the +Now, if we change either the function or the example so the `assert_eq!` in the example panics and run `cargo test` again, we’ll see that the doc tests catch that the example and the code are out of sync with each other! @@ -130,7 +131,7 @@ comments describe the entire crate. When we run `cargo doc --open`, these comments will display on the front page of the documentation for `my_crate` above the list of public items in the -crate, as shown in Figure 14-2: +crate, as shown in Figure 14-2. <img alt="Rendered HTML documentation with a comment for the crate as a whole" src="img/trpl14-02.png" class="center" /> @@ -161,7 +162,7 @@ They might also be annoyed at having to enter `use` The good news is that if the structure _isn’t_ convenient for others to use from another library, you don’t have to rearrange your internal organization: instead, you can re-export items to make a public structure that’s different -from your private structure by using `pub use`. Re-exporting takes a public +from your private structure by using `pub use`. *Re-exporting* takes a public item in one location and makes it public in another location, as if it were defined in the other location instead. @@ -324,14 +325,14 @@ Caused by: the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields ``` -This errors because you’re missing some crucial information: a description and -license are required so people will know what your crate does and under what -terms they can use it. In _Cargo.toml_, add a description that's just a -sentence or two, because it will appear with your crate in search results. For -the `license` field, you need to give a _license identifier value_. The [Linux -Foundation’s Software Package Data Exchange (SPDX)][spdx] lists the identifiers -you can use for this value. For example, to specify that you’ve licensed your -crate using the MIT License, add the `MIT` identifier: +This results in an error because you’re missing some crucial information: a +description and license are required so people will know what your crate does +and under what terms they can use it. In _Cargo.toml_, add a description that's +just a sentence or two, because it will appear with your crate in search +results. For the `license` field, you need to give a _license identifier value_. +The [Linux Foundation’s Software Package Data Exchange (SPDX)][spdx] lists the +identifiers you can use for this value. For example, to specify that you’ve +licensed your crate using the MIT License, add the `MIT` identifier: <span class="filename">Filename: Cargo.toml</span> @@ -426,9 +427,9 @@ Then run `cargo publish` to upload the new version. Although you can’t remove previous versions of a crate, you can prevent any future projects from adding them as a new dependency. This is useful when a crate version is broken for one reason or another. In such situations, Cargo -supports _yanking_ a crate version. +supports yanking a crate version. -Yanking a version prevents new projects from depending on that version while +_Yanking_ a version prevents new projects from depending on that version while allowing all existing projects that depend on it to continue. Essentially, a yank means that all projects with a _Cargo.lock_ will not break, and any future _Cargo.lock_ files generated will not use the yanked version. diff --git a/src/ch14-03-cargo-workspaces.md b/src/ch14-03-cargo-workspaces.md index 6aa16e1ff1..51efd2ac77 100644 --- a/src/ch14-03-cargo-workspaces.md +++ b/src/ch14-03-cargo-workspaces.md @@ -14,7 +14,7 @@ can concentrate on the structure of the workspace. There are multiple ways to structure a workspace, so we'll just show one common way. We’ll have a workspace containing a binary and two libraries. The binary, which will provide the main functionality, will depend on the two libraries. One library will -provide an `add_one` function, and a second library an `add_two` function. +provide an `add_one` function and the other library an `add_two` function. These three crates will be part of the same workspace. We’ll start by creating a new directory for the workspace: @@ -28,7 +28,7 @@ configure the entire workspace. This file won’t have a `[package]` section. Instead, it will start with a `[workspace]` section that will allow us to add members to the workspace. We also make a point to use the latest and greatest version of Cargo’s resolver algorithm in our workspace by setting the -`resolver` to `"2"`. +`resolver` to `"3"`. <span class="filename">Filename: Cargo.toml</span> @@ -88,16 +88,7 @@ can avoid unnecessary rebuilding. ### Creating the Second Package in the Workspace Next, let’s create another member package in the workspace and call it -`add_one`. Change the top-level _Cargo.toml_ to specify the _add_one_ path in -the `members` list: - -<span class="filename">Filename: Cargo.toml</span> - -```toml -{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml}} -``` - -Then generate a new library crate named `add_one`: +`add_one`. Generate a new library crate named `add_one`: <!-- manual-regeneration cd listings/ch14-more-about-cargo/output-only-02-add-one/add @@ -113,6 +104,15 @@ $ cargo new add_one --lib Adding `add_one` as member of workspace at `file:///projects/add` ``` +The top-level _Cargo.toml_ will now include the _add_one_ path in the `members` +list: + +<span class="filename">Filename: Cargo.toml</span> + +```toml +{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml}} +``` + Your _add_ directory should now have these directories and files: ```text @@ -138,7 +138,7 @@ In the _add_one/src/lib.rs_ file, let’s add an `add_one` function: ``` Now we can have the `adder` package with our binary depend on the `add_one` -package that has our library. First, we’ll need to add a path dependency on +package that has our library. First we’ll need to add a path dependency on `add_one` to _adder/Cargo.toml_. <span class="filename">Filename: adder/Cargo.toml</span> @@ -279,10 +279,10 @@ To fix this, edit the _Cargo.toml_ file for the `adder` package and indicate that `rand` is a dependency for it as well. Building the `adder` package will add `rand` to the list of dependencies for `adder` in _Cargo.lock_, but no additional copies of `rand` will be downloaded. Cargo will ensure that every -crate in every package in the workspace using the `rand` package will be using -the same version as long as they specify compatible versions of `rand`, saving -us space and ensuring that the crates in the workspace will be compatible with -each other. +crate in every package in the workspace using the `rand` package will use the +same version as long as they specify compatible versions of `rand`, saving us +space and ensuring that the crates in the workspace will be compatible with each +other. If crates in the workspace specify incompatible versions of the same dependency, Cargo will resolve each of them, but will still try to resolve as few versions @@ -378,7 +378,7 @@ flag and specifying the name of the crate we want to publish. For additional practice, add an `add_two` crate to this workspace in a similar way as the `add_one` crate! -As your project grows, consider using a workspace: it’s easier to understand -smaller, individual components than one big blob of code. Furthermore, keeping -the crates in a workspace can make coordination between crates easier if they -are often changed at the same time. +As your project grows, consider using a workspace: it enables you to work with +smaller, easier-to-understand components than one big blob of code. Furthermore, +keeping the crates in a workspace can make coordination between crates easier if +they are often changed at the same time. diff --git a/src/ch14-04-installing-binaries.md b/src/ch14-04-installing-binaries.md index 7e2c720af6..600cbe3261 100644 --- a/src/ch14-04-installing-binaries.md +++ b/src/ch14-04-installing-binaries.md @@ -44,4 +44,4 @@ $ cargo install ripgrep The second-to-last line of the output shows the location and the name of the installed binary, which in the case of `ripgrep` is `rg`. As long as the installation directory is in your `$PATH`, as mentioned previously, you can -then run `rg --help` and start using a faster, rustier tool for searching files! +then run `rg --help` and start using a faster, Rustier tool for searching files! diff --git a/src/ch14-05-extending-cargo.md b/src/ch14-05-extending-cargo.md index bd228714ae..11d760d0bf 100644 --- a/src/ch14-05-extending-cargo.md +++ b/src/ch14-05-extending-cargo.md @@ -1,8 +1,8 @@ ## Extending Cargo with Custom Commands Cargo is designed so you can extend it with new subcommands without having to -modify Cargo. If a binary in your `$PATH` is named `cargo-something`, you can -run it as if it was a Cargo subcommand by running `cargo something`. Custom +modify it. If a binary in your `$PATH` is named `cargo-something`, you can run +it as if it were a Cargo subcommand by running `cargo something`. Custom commands like this are also listed when you run `cargo --list`. Being able to use `cargo install` to install extensions and then run them just like the built-in Cargo tools is a super convenient benefit of Cargo’s design! diff --git a/src/ch15-00-smart-pointers.md b/src/ch15-00-smart-pointers.md index 0565abd047..08ddcf8dc6 100644 --- a/src/ch15-00-smart-pointers.md +++ b/src/ch15-00-smart-pointers.md @@ -5,7 +5,7 @@ memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4. References are indicated by the `&` symbol and borrow the value they point to. They don’t have any special capabilities other than referring to -data, and have no overhead. +data, and they have no overhead. _Smart pointers_, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities. The concept of @@ -19,14 +19,14 @@ the number of owners and, when no owners remain, cleaning up the data. Rust, with its concept of ownership and borrowing, has an additional difference between references and smart pointers: while references only borrow data, in -many cases, smart pointers _own_ the data they point to. +many cases smart pointers _own_ the data they point to. Though we didn’t call them as such at the time, we’ve already encountered a few smart pointers in this book, including `String` and `Vec<T>` in Chapter 8. Both -these types count as smart pointers because they own some memory and allow you -to manipulate it. They also have metadata and extra capabilities or guarantees. -`String`, for example, stores its capacity as metadata and has the extra -ability to ensure its data will always be valid UTF-8. +of these types count as smart pointers because they own some memory and allow +you to manipulate it. They also have metadata and extra capabilities or +guarantees. `String`, for example, stores its capacity as metadata and has the +extra ability to ensure its data will always be valid UTF-8. Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the `Deref` and `Drop` traits. The `Deref` @@ -41,7 +41,7 @@ frequently in Rust, this chapter won’t cover every existing smart pointer. Man libraries have their own smart pointers, and you can even write your own. We’ll cover the most common smart pointers in the standard library: -- `Box<T>` for allocating values on the heap +- `Box<T>`, for allocating values on the heap - `Rc<T>`, a reference counting type that enables multiple ownership - `Ref<T>` and `RefMut<T>`, accessed through `RefCell<T>`, a type that enforces the borrowing rules at runtime instead of compile time diff --git a/src/ch15-01-box.md b/src/ch15-01-box.md index 4244519352..d9b7fb79c3 100644 --- a/src/ch15-01-box.md +++ b/src/ch15-01-box.md @@ -16,24 +16,23 @@ either. You’ll use them most often in these situations: - When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type -We’ll demonstrate the first situation in the [“Enabling Recursive Types with -Boxes”](#enabling-recursive-types-with-boxes)<!-- ignore --> section. In the -second case, transferring ownership of a large amount of data can take a long -time because the data is copied around on the stack. To improve performance in -this situation, we can store the large amount of data on the heap in a box. -Then, only the small amount of pointer data is copied around on the stack, -while the data it references stays in one place on the heap. The third case is -known as a _trait object_, and Chapter 18 devotes an entire section, [“Using -Trait Objects That Allow for Values of Different Types,”][trait-objects]<!-- -ignore --> just to that topic. So what you learn here you’ll apply again in -Chapter 18! - -### Using a `Box<T>` to Store Data on the Heap +We’ll demonstrate the first situation in [“Enabling Recursive Types with +Boxes”](#enabling-recursive-types-with-boxes)<!-- ignore -->. In the second +case, transferring ownership of a large amount of data can take a long time +because the data is copied around on the stack. To improve performance in this +situation, we can store the large amount of data on the heap in a box. Then, +only the small amount of pointer data is copied around on the stack, while the +data it references stays in one place on the heap. The third case is known as a +_trait object_, and [“Using Trait Objects That Allow for Values of Different +Types,”][trait-objects]<!-- ignore --> in Chapter 18 is devoted to that topic. +So what you learn here you’ll apply again in that section! + +### Using `Box<T>` to Store Data on the Heap Before we discuss the heap storage use case for `Box<T>`, we’ll cover the syntax and how to interact with values stored within a `Box<T>`. -Listing 15-1 shows how to use a box to store an `i32` value on the heap: +Listing 15-1 shows how to use a box to store an `i32` value on the heap. <Listing number="15-1" file-name="src/main.rs" caption="Storing an `i32` value on the heap using a box"> @@ -59,12 +58,12 @@ wouldn’t be allowed to if we didn’t have boxes. ### Enabling Recursive Types with Boxes -A value of _recursive type_ can have another value of the same type as part of -itself. Recursive types pose an issue because, at compile time, Rust needs to -know how much space a type takes up. However, the nesting of values of -recursive types could theoretically continue infinitely, so Rust can’t know how -much space the value needs. Because boxes have a known size, we can enable -recursive types by inserting a box in the recursive type definition. +A value of a _recursive type_ can have another value of the same type as part of +itself. Recursive types pose an issue because Rust needs to know at compile time +how much space a type takes up. However, the nesting of values of recursive +types could theoretically continue infinitely, so Rust can’t know how much space +the value needs. Because boxes have a known size, we can enable recursive types +by inserting a box in the recursive type definition. As an example of a recursive type, let’s explore the _cons list_. This is a data type commonly found in functional programming languages. The cons list type @@ -75,14 +74,14 @@ more complex situations involving recursive types. #### More Information About the Cons List A _cons list_ is a data structure that comes from the Lisp programming language -and its dialects and is made up of nested pairs, and is the Lisp version of a -linked list. Its name comes from the `cons` function (short for “construct -function”) in Lisp that constructs a new pair from its two arguments. By +and its dialects, is made up of nested pairs, and is the Lisp version of a +linked list. Its name comes from the `cons` function (short for _construct +function_) in Lisp that constructs a new pair from its two arguments. By calling `cons` on a pair consisting of a value and another pair, we can construct cons lists made up of recursive pairs. For example, here’s a pseudocode representation of a cons list containing the -list 1, 2, 3 with each pair in parentheses: +list `1, 2, 3` with each pair in parentheses: ```text (1, (2, (3, Nil))) @@ -119,7 +118,7 @@ we’ll demonstrate. > any type. Using the `List` type to store the list `1, 2, 3` would look like the code in -Listing 15-3: +Listing 15-3. <Listing number="15-3" file-name="src/main.rs" caption="Using the `List` enum to store the list `1, 2, 3`"> @@ -135,7 +134,7 @@ is one more `Cons` value that holds `3` and a `List` value, which is finally `Nil`, the non-recursive variant that signals the end of the list. If we try to compile the code in Listing 15-3, we get the error shown in -Listing 15-4: +Listing 15-4. <Listing number="15-4" file-name="output.txt" caption="The error we get when attempting to define a recursive enum"> @@ -148,7 +147,7 @@ Listing 15-4: The error shows this type “has infinite size.” The reason is that we’ve defined `List` with a variant that is recursive: it holds another value of itself directly. As a result, Rust can’t figure out how much space it needs to store a -`List` value. Let’s break down why we get this error. First, we’ll look at how +`List` value. Let’s break down why we get this error. First we’ll look at how Rust decides how much space it needs to store a value of a non-recursive type. #### Computing the Size of a Non-Recursive Type @@ -197,7 +196,7 @@ help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle | ++++ + ``` -In this suggestion, “indirection” means that instead of storing a value +In this suggestion, _indirection_ means that instead of storing a value directly, we should change the data structure to store the value indirectly by storing a pointer to the value instead. @@ -211,7 +210,7 @@ this implementation is now more like placing the items next to one another rather than inside one another. We can change the definition of the `List` enum in Listing 15-2 and the usage -of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile: +of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile. <Listing number="15-5" file-name="src/main.rs" caption="Definition of `List` that uses `Box<T>` in order to have a known size"> @@ -239,7 +238,7 @@ other special capabilities, like those we’ll see with the other smart pointer types. They also don’t have the performance overhead that these special capabilities incur, so they can be useful in cases like the cons list where the indirection is the only feature we need. We’ll look at more use cases for boxes -in Chapter 18, too. +in Chapter 18. The `Box<T>` type is a smart pointer because it implements the `Deref` trait, which allows `Box<T>` values to be treated like references. When a `Box<T>` diff --git a/src/ch15-02-deref.md b/src/ch15-02-deref.md index da79b04eaf..6c1ce33261 100644 --- a/src/ch15-02-deref.md +++ b/src/ch15-02-deref.md @@ -1,4 +1,8 @@ -## Treating Smart Pointers Like Regular References with the `Deref` Trait +## Treating Smart Pointers Like Regular References with `Deref` + +<!-- Old link, do not remove --> + +<a id="treating-smart-pointers-like-regular-references-with-the-deref-trait"></a> Implementing the `Deref` trait allows you to customize the behavior of the _dereference operator_ `*` (not to be confused with the multiplication or glob @@ -28,7 +32,7 @@ or smart pointers. A regular reference is a type of pointer, and one way to think of a pointer is as an arrow to a value stored somewhere else. In Listing 15-6, we create a reference to an `i32` value and then use the dereference operator to follow the -reference to the value: +reference to the value. <Listing number="15-6" file-name="src/main.rs" caption="Using the dereference operator to follow a reference to an `i32` value"> @@ -72,12 +76,11 @@ Listing 15-6: </Listing> The main difference between Listing 15-7 and Listing 15-6 is that here we set -`y` to be an instance of a `Box<T>` pointing to a copied value of `x` rather -than a reference pointing to the value of `x`. In the last assertion, we can -use the dereference operator to follow the pointer of the `Box<T>` in the same -way that we did when `y` was a reference. Next, we’ll explore what is special -about `Box<T>` that enables us to use the dereference operator by defining our -own type. +`y` to be an instance of a box pointing to a copied value of `x` rather than a +reference pointing to the value of `x`. In the last assertion, we can use the +dereference operator to follow the box’s pointer in the same way that we did +when `y` was a reference. Next, we’ll explore what is special about `Box<T>` +that enables us to use the dereference operator by defining our own type. ### Defining Our Own Smart Pointer @@ -98,7 +101,7 @@ Listing 15-8 defines a `MyBox<T>` type in the same way. We’ll also define a </Listing> -We define a struct named `MyBox` and declare a generic parameter `T`, because +We define a struct named `MyBox` and declare a generic parameter `T` because we want our type to hold values of any type. The `MyBox` type is a tuple struct with one element of type `T`. The `MyBox::new` function takes one parameter of type `T` and returns a `MyBox` instance that holds the value passed in. @@ -116,7 +119,7 @@ code in Listing 15-9 won’t compile because Rust doesn’t know how to derefere </Listing> -Here’s the resulting compilation error: +Here’s the resultant compilation error: ```console {{#include ../listings/ch15-smart-pointers/listing-15-09/output.txt}} @@ -137,7 +140,7 @@ Chapter 10, to implement a trait, we need to provide implementations for the trait’s required methods. The `Deref` trait, provided by the standard library, requires us to implement one method named `deref` that borrows `self` and returns a reference to the inner data. Listing 15-10 contains an implementation -of `Deref` to add to the definition of `MyBox<T>`: +of `Deref` to add to the definition of `MyBox<T>`. <Listing number="15-10" file-name="src/main.rs" caption="Implementing `Deref` on `MyBox<T>`"> @@ -153,11 +156,11 @@ generic parameter, but you don’t need to worry about them for now; we’ll cov them in more detail in Chapter 20. We fill in the body of the `deref` method with `&self.0` so `deref` returns a -reference to the value we want to access with the `*` operator; recall from the -[“Using Tuple Structs without Named Fields to Create Different -Types”][tuple-structs]<!-- ignore --> section of Chapter 5 that `.0` accesses -the first value in a tuple struct. The `main` function in Listing 15-9 that -calls `*` on the `MyBox<T>` value now compiles, and the assertions pass! +reference to the value we want to access with the `*` operator; recall from +[“Using Tuple Structs Without Named Fields to Create Different +Types”][tuple-structs]<!-- ignore --> in Chapter 5 that `.0` accesses the first +value in a tuple struct. The `main` function in Listing 15-9 that calls `*` on +the `MyBox<T>` value now compiles, and the assertions pass! Without the `Deref` trait, the compiler can only dereference `&` references. The `deref` method gives the compiler the ability to take a value of any type @@ -179,7 +182,7 @@ identically whether we have a regular reference or a type that implements The reason the `deref` method returns a reference to a value, and that the plain dereference outside the parentheses in `*(y.deref())` is still necessary, -is to do with the ownership system. If the `deref` method returned the value +has to do with the ownership system. If the `deref` method returned the value directly instead of a reference to the value, the value would be moved out of `self`. We don’t want to take ownership of the inner value inside `MyBox<T>` in this case or in most cases where we use the dereference operator. @@ -210,7 +213,7 @@ can work for either references or smart pointers. To see deref coercion in action, let’s use the `MyBox<T>` type we defined in Listing 15-8 as well as the implementation of `Deref` that we added in Listing 15-10. Listing 15-11 shows the definition of a function that has a string slice -parameter: +parameter. <Listing number="15-11" file-name="src/main.rs" caption="A `hello` function that has the parameter `name` of type `&str`"> @@ -221,8 +224,8 @@ parameter: </Listing> We can call the `hello` function with a string slice as an argument, such as -`hello("Rust");` for example. Deref coercion makes it possible to call `hello` -with a reference to a value of type `MyBox<String>`, as shown in Listing 15-12: +`hello("Rust");`, for example. Deref coercion makes it possible to call `hello` +with a reference to a value of type `MyBox<String>`, as shown in Listing 15-12. <Listing number="15-12" file-name="src/main.rs" caption="Calling `hello` with a reference to a `MyBox<String>` value, which works because of deref coercion"> @@ -277,10 +280,10 @@ cases: 2. From `&mut T` to `&mut U` when `T: DerefMut<Target=U>` 3. From `&mut T` to `&U` when `T: Deref<Target=U>` -The first two cases are the same as each other except that the second -implements mutability. The first case states that if you have a `&T`, and `T` -implements `Deref` to some type `U`, you can get a `&U` transparently. The -second case states that the same deref coercion happens for mutable references. +The first two cases are the same except that the second implements mutability. +The first case states that if you have a `&T`, and `T` implements `Deref` to +some type `U`, you can get a `&U` transparently. The second case states that the +same deref coercion happens for mutable references. The third case is trickier: Rust will also coerce a mutable reference to an immutable one. But the reverse is _not_ possible: immutable references will diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md index 0608e2af07..53352f7ed2 100644 --- a/src/ch15-03-drop.md +++ b/src/ch15-03-drop.md @@ -7,12 +7,12 @@ be used to release resources like files or network connections. We’re introducing `Drop` in the context of smart pointers because the functionality of the `Drop` trait is almost always used when implementing a -smart pointer. For example, when a `Box<T>` is dropped, it will deallocate the +smart pointer. For example, when a `Box<T>` is dropped it will deallocate the space on the heap that the box points to. In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance of those types. Examples -include file handles, sockets, or locks. If they forget, the system might +include file handles, sockets, and locks. If they forget, the system might become overloaded and crash. In Rust, you can specify that a particular bit of code be run whenever a value goes out of scope, and the compiler will insert this code automatically. As a result, you don’t need to be careful about @@ -26,7 +26,7 @@ let’s implement `drop` with `println!` statements for now. Listing 15-14 shows a `CustomSmartPointer` struct whose only custom functionality is that it will print `Dropping CustomSmartPointer!` when the -instance goes out of scope, to show when Rust runs the `drop` function. +instance goes out of scope, to show when Rust runs the `drop` method. <Listing number="15-14" file-name="src/main.rs" caption="A `CustomSmartPointer` struct that implements the `Drop` trait where we would put our cleanup code"> @@ -39,8 +39,8 @@ instance goes out of scope, to show when Rust runs the `drop` function. The `Drop` trait is included in the prelude, so we don’t need to bring it into scope. We implement the `Drop` trait on `CustomSmartPointer` and provide an implementation for the `drop` method that calls `println!`. The body of the -`drop` function is where you would place any logic that you wanted to run when -an instance of your type goes out of scope. We’re printing some text here to +`drop` method is where you would place any logic that you wanted to run when an +instance of your type goes out of scope. We’re printing some text here to demonstrate visually when Rust will call `drop`. In `main`, we create two instances of `CustomSmartPointer` and then print @@ -62,7 +62,9 @@ give you a visual guide to how the `drop` method works; usually you would specify the cleanup code that your type needs to run rather than a print message. -### Dropping a Value Early with `std::mem::drop` +<!-- Old link, do not remove --> + +<a id="dropping-a-value-early-with-std-mem-drop"></a> Unfortunately, it’s not straightforward to disable the automatic `drop` functionality. Disabling `drop` isn’t usually necessary; the whole point of the @@ -70,13 +72,13 @@ functionality. Disabling `drop` isn’t usually necessary; the whole point of th you might want to clean up a value early. One example is when using smart pointers that manage locks: you might want to force the `drop` method that releases the lock so that other code in the same scope can acquire the lock. -Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead +Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead, you have to call the `std::mem::drop` function provided by the standard library if you want to force a value to be dropped before the end of its scope. If we try to call the `Drop` trait’s `drop` method manually by modifying the `main` function from Listing 15-14, as shown in Listing 15-15, we’ll get a -compiler error: +compiler error. <Listing number="15-15" file-name="src/main.rs" caption="Attempting to call the `drop` method from the `Drop` trait manually to clean up early"> @@ -94,7 +96,7 @@ When we try to compile this code, we’ll get this error: This error message states that we’re not allowed to explicitly call `drop`. The error message uses the term _destructor_, which is the general programming term -for a function that cleans up an instance. A destructor is analogous to a +for a function that cleans up an instance. A _destructor_ is analogous to a _constructor_, which creates an instance. The `drop` function in Rust is one particular destructor. @@ -108,9 +110,9 @@ scope, and we can’t call the `drop` method explicitly. So, if we need to force a value to be cleaned up early, we use the `std::mem::drop` function. The `std::mem::drop` function is different from the `drop` method in the `Drop` -trait. We call it by passing as an argument the value we want to force drop. +trait. We call it by passing as an argument the value we want to force-drop. The function is in the prelude, so we can modify `main` in Listing 15-15 to -call the `drop` function, as shown in Listing 15-16: +call the `drop` function, as shown in Listing 15-16. <Listing number="15-16" file-name="src/main.rs" caption="Calling `std::mem::drop` to explicitly drop a value before it goes out of scope"> diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md index b1033aa3e3..23ffa46a89 100644 --- a/src/ch15-04-rc.md +++ b/src/ch15-04-rc.md @@ -17,7 +17,7 @@ Imagine `Rc<T>` as a TV in a family room. When one person enters to watch TV, they turn it on. Others can come into the room and watch the TV. When the last person leaves the room, they turn off the TV because it’s no longer being used. If someone turns off the TV while others are still watching it, there would be -uproar from the remaining TV watchers! +an uproar from the remaining TV watchers! We use the `Rc<T>` type when we want to allocate some data on the heap for multiple parts of our program to read and we can’t determine at compile time @@ -33,17 +33,17 @@ multithreaded programs. Let’s return to our cons list example in Listing 15-5. Recall that we defined it using `Box<T>`. This time, we’ll create two lists that both share ownership -of a third list. Conceptually, this looks similar to Figure 15-3: +of a third list. Conceptually, this looks similar to Figure 15-3. <img alt="Two lists that share ownership of a third list" src="img/trpl15-03.svg" class="center" /> <span class="caption">Figure 15-3: Two lists, `b` and `c`, sharing ownership of a third list, `a`</span> -We’ll create list `a` that contains 5 and then 10. Then we’ll make two more -lists: `b` that starts with 3 and `c` that starts with 4. Both `b` and `c` -lists will then continue on to the first `a` list containing 5 and 10. In other -words, both lists will share the first list containing 5 and 10. +We’ll create list `a` that contains `5` and then `10`. Then we’ll make two more +lists: `b` that starts with `3` and `c` that starts with `4`. Both `b` and `c` +lists will then continue on to the first `a` list containing `5` and `10`. In +other words, both lists will share the first list containing `5` and `10`. Trying to implement this scenario using our definition of `List` with `Box<T>` won’t work, as shown in Listing 15-17: @@ -128,8 +128,7 @@ At each point in the program where the reference count changes, we print the reference count, which we get by calling the `Rc::strong_count` function. This function is named `strong_count` rather than `count` because the `Rc<T>` type also has a `weak_count`; we’ll see what `weak_count` is used for in [“Preventing -Reference Cycles: Turning an `Rc<T>` into a -`Weak<T>`”][preventing-ref-cycles]<!-- ignore -->. +Reference Cycles Using `Weak<T>`”][preventing-ref-cycles]<!-- ignore -->. This code prints the following: diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md index 70c99577e0..e1f5b0225e 100644 --- a/src/ch15-05-interior-mutability.md +++ b/src/ch15-05-interior-mutability.md @@ -19,11 +19,11 @@ interior mutability pattern. ### Enforcing Borrowing Rules at Runtime with `RefCell<T>` Unlike `Rc<T>`, the `RefCell<T>` type represents single ownership over the data -it holds. So, what makes `RefCell<T>` different from a type like `Box<T>`? +it holds. So what makes `RefCell<T>` different from a type like `Box<T>`? Recall the borrowing rules you learned in Chapter 4: -- At any given time, you can have _either_ (but not both) one mutable reference - or any number of immutable references. +- At any given time, you can have _either_ one mutable reference or any number + of immutable references (but not both). - References must always be valid. With references and `Box<T>`, the borrowing rules’ invariants are enforced at @@ -103,13 +103,13 @@ an immutable value and see why that is useful. #### A Use Case for Interior Mutability: Mock Objects Sometimes during testing a programmer will use a type in place of another type, -in order to observe particular behavior and assert it’s implemented correctly. -This placeholder type is called a _test double_. Think of it in the sense of a -“stunt double” in filmmaking, where a person steps in and substitutes for an -actor to do a particular tricky scene. Test doubles stand in for other types -when we’re running tests. _Mock objects_ are specific types of test doubles -that record what happens during a test so you can assert that the correct -actions took place. +in order to observe particular behavior and assert that it’s implemented +correctly. This placeholder type is called a _test double_. Think of it in the +sense of a stunt double in filmmaking, where a person steps in and substitutes +for an actor to do a particularly tricky scene. Test doubles stand in for other +types when we’re running tests. _Mock objects_ are specific types of test +doubles that record what happens during a test so you can assert that the +correct actions took place. Rust doesn’t have objects in the same sense as other languages have objects, and Rust doesn’t have mock object functionality built into the standard library @@ -124,10 +124,10 @@ user’s quota for the number of API calls they’re allowed to make, for exampl Our library will only provide the functionality of tracking how close to the maximum a value is and what the messages should be at what times. Applications that use our library will be expected to provide the mechanism for sending the -messages: the application could put a message in the application, send an -email, send a text message, or something else. The library doesn’t need to know -that detail. All it needs is something that implements a trait we’ll provide -called `Messenger`. Listing 15-20 shows the library code: +messages: the application could put a message in the application, send an email, +send a text message, or do something else. The library doesn’t need to know that +detail. All it needs is something that implements a trait we’ll provide called +`Messenger`. Listing 15-20 shows the library code. <Listing number="15-20" file-name="src/lib.rs" caption="A library to keep track of how close a value is to a maximum value and warn when the value is at certain levels"> @@ -153,7 +153,7 @@ call `send`, will only keep track of the messages it’s told to send. We can create a new instance of the mock object, create a `LimitTracker` that uses the mock object, call the `set_value` method on `LimitTracker`, and then check that the mock object has the messages we expect. Listing 15-21 shows an attempt to -implement a mock object to do just that, but the borrow checker won’t allow it: +implement a mock object to do just that, but the borrow checker won’t allow it. <Listing number="15-21" file-name="src/lib.rs" caption="An attempt to implement a `MockMessenger` that isn’t allowed by the borrow checker"> @@ -176,10 +176,10 @@ In the test, we’re testing what happens when the `LimitTracker` is told to set `value` to something that is more than 75 percent of the `max` value. First, we create a new `MockMessenger`, which will start with an empty list of messages. Then we create a new `LimitTracker` and give it a reference to the new -`MockMessenger` and a `max` value of 100. We call the `set_value` method on the -`LimitTracker` with a value of 80, which is more than 75 percent of 100. Then -we assert that the list of messages that the `MockMessenger` is keeping track -of should now have one message in it. +`MockMessenger` and a `max` value of `100`. We call the `set_value` method on +the `LimitTracker` with a value of `80`, which is more than 75 percent of 100. +Then we assert that the list of messages that the `MockMessenger` is keeping +track of should now have one message in it. However, there’s one problem with this test, as shown here: @@ -197,7 +197,7 @@ work correctly with our existing design. This is a situation in which interior mutability can help! We’ll store the `sent_messages` within a `RefCell<T>`, and then the `send` method will be able to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 -shows what that looks like: +shows what that looks like. <Listing number="15-22" file-name="src/lib.rs" caption="Using `RefCell<T>` to mutate an inner value while the outer value is considered immutable"> @@ -236,7 +236,7 @@ can treat them like regular references. The `RefCell<T>` keeps track of how many `Ref<T>` and `RefMut<T>` smart pointers are currently active. Every time we call `borrow`, the `RefCell<T>` increases its count of how many immutable borrows are active. When a `Ref<T>` -value goes out of scope, the count of immutable borrows goes down by one. Just +value goes out of scope, the count of immutable borrows goes down by 1. Just like the compile-time borrowing rules, `RefCell<T>` lets us have many immutable borrows or one mutable borrow at any point in time. @@ -280,20 +280,23 @@ in a context where only immutable values are allowed. You can use `RefCell<T>` despite its trade-offs to get more functionality than regular references provide. -### Having Multiple Owners of Mutable Data by Combining `Rc<T>` and `RefCell<T>` +<!-- Old link, do not remove --> + +<a id="having-multiple-owners-of-mutable-data-by-combining-rc-t-and-ref-cell-t"></a> + +### Allowing Multiple Owners of Mutable Data with `Rc<T>` and `RefCell<T>` A common way to use `RefCell<T>` is in combination with `Rc<T>`. Recall that `Rc<T>` lets you have multiple owners of some data, but it only gives immutable access to that data. If you have an `Rc<T>` that holds a `RefCell<T>`, you can get a value that can have multiple owners _and_ that you can mutate! -For example, recall the cons list example in Listing 15-18 where we used -`Rc<T>` to allow multiple lists to share ownership of another list. Because -`Rc<T>` holds only immutable values, we can’t change any of the values in the -list once we’ve created them. Let’s add in `RefCell<T>` to gain the ability to -change the values in the lists. Listing 15-24 shows that by using a -`RefCell<T>` in the `Cons` definition, we can modify the value stored in all -the lists: +For example, recall the cons list example in Listing 15-18 where we used `Rc<T>` +to allow multiple lists to share ownership of another list. Because `Rc<T>` +holds only immutable values, we can’t change any of the values in the list once +we’ve created them. Let’s add in `RefCell<T>` for its ability to change the +values in the lists. Listing 15-24 shows that by using a `RefCell<T>` in the +`Cons` definition, we can modify the value stored in all the lists. <Listing number="15-24" file-name="src/main.rs" caption="Using `Rc<RefCell<i32>>` to create a `List` that we can mutate"> @@ -315,14 +318,14 @@ they can both refer to `a`, which is what we did in Listing 15-18. After we’ve created the lists in `a`, `b`, and `c`, we want to add 10 to the value in `value`. We do this by calling `borrow_mut` on `value`, which uses the -automatic dereferencing feature we discussed in Chapter 5 (see [“Where’s the -`->` Operator?”][wheres-the---operator]<!-- ignore -->) to dereference the -`Rc<T>` to the inner `RefCell<T>` value. The `borrow_mut` method returns a +automatic dereferencing feature we discussed in [“Where’s the `->` +Operator?”][wheres-the---operator]<!-- ignore -->) in Chapter 5 to dereference +the `Rc<T>` to the inner `RefCell<T>` value. The `borrow_mut` method returns a `RefMut<T>` smart pointer, and we use the dereference operator on it and change the inner value. When we print `a`, `b`, and `c`, we can see that they all have the modified -value of 15 rather than 5: +value of `15` rather than `5`: ```console {{#include ../listings/ch15-smart-pointers/listing-15-24/output.txt}} @@ -334,7 +337,7 @@ access to its interior mutability so we can modify our data when we need to. The runtime checks of the borrowing rules protect us from data races, and it’s sometimes worth trading a bit of speed for this flexibility in our data structures. Note that `RefCell<T>` does not work for multithreaded code! -`Mutex<T>` is the thread-safe version of `RefCell<T>` and we’ll discuss +`Mutex<T>` is the thread-safe version of `RefCell<T>`, and we’ll discuss `Mutex<T>` in Chapter 16. [wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator diff --git a/src/ch15-06-reference-cycles.md b/src/ch15-06-reference-cycles.md index afe056ffa0..ba4ec0b570 100644 --- a/src/ch15-06-reference-cycles.md +++ b/src/ch15-06-reference-cycles.md @@ -13,7 +13,7 @@ will never be dropped. Let’s look at how a reference cycle might happen and how to prevent it, starting with the definition of the `List` enum and a `tail` method in Listing -15-25: +15-25. <Listing number="15-25" file-name="src/main.rs" caption="A cons list definition that holds a `RefCell<T>` so we can modify what a `Cons` variant is referring to"> @@ -46,7 +46,7 @@ reference counts are at various points in this process. We create an `Rc<List>` instance holding a `List` value in the variable `a` with an initial list of `5, Nil`. We then create an `Rc<List>` instance holding -another `List` value in the variable `b` that contains the value 10 and points +another `List` value in the variable `b` that contains the value `10` and points to the list in `a`. We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We do @@ -62,7 +62,7 @@ moment, we’ll get this output: {{#include ../listings/ch15-smart-pointers/listing-15-26/output.txt}} ``` -The reference count of the `Rc<List>` instances in both `a` and `b` are 2 after +The reference count of the `Rc<List>` instances in both `a` and `b` is 2 after we change the list in `a` to point to `b`. At the end of `main`, Rust drops the variable `b`, which decreases the reference count of the `b` `Rc<List>` instance from 2 to 1. The memory that `Rc<List>` has on the heap won’t be dropped at @@ -78,9 +78,9 @@ diagram in Figure 15-4. <span class="caption">Figure 15-4: A reference cycle of lists `a` and `b` pointing to each other</span> -If you uncomment the last `println!` and run the program, Rust will try to -print this cycle with `a` pointing to `b` pointing to `a`, and so forth, until -it overflows the stack. +If you uncomment the last `println!` and run the program, Rust will try to print +this cycle with `a` pointing to `b` pointing to `a` and so forth until it +overflows the stack. Compared to a real-world program, the consequences of creating a reference cycle in this example aren’t very dire: right after we create the reference cycle, @@ -107,17 +107,21 @@ Let’s look at an example using graphs made up of parent nodes and child nodes to see when non-ownership relationships are an appropriate way to prevent reference cycles. -### Preventing Reference Cycles: Turning an `Rc<T>` into a `Weak<T>` +<!-- Old link, do not remove --> -So far, we’ve demonstrated that calling `Rc::clone` increases the -`strong_count` of an `Rc<T>` instance, and an `Rc<T>` instance is only cleaned -up if its `strong_count` is 0. You can also create a _weak reference_ to the -value within an `Rc<T>` instance by calling `Rc::downgrade` and passing a -reference to the `Rc<T>`. _Strong references_ are how you can share ownership of -an `Rc<T>` instance. _Weak references_ don’t express an ownership relationship, -and their count doesn’t affect when an `Rc<T>` instance is cleaned up. They -won’t cause a reference cycle because any cycle involving some weak references -will be broken once the strong reference count of values involved is 0. +<a id="preventing-reference-cycles-turning-an-rct-into-a-weakt"></a> + +### Preventing Reference Cycles Using `Weak<T>` + +So far, we’ve demonstrated that calling `Rc::clone` increases the `strong_count` +of an `Rc<T>` instance, and an `Rc<T>` instance is only cleaned up if its +`strong_count` is 0. You can also create a _weak reference_ to the value within +an `Rc<T>` instance by calling `Rc::downgrade` and passing a reference to the +`Rc<T>`. Strong references are how you can share ownership of an `Rc<T>` +instance. Weak references don’t express an ownership relationship, and their +count doesn’t affect when an `Rc<T>` instance is cleaned up. They won’t cause a +reference cycle because any cycle involving some weak references will be broken +once the strong reference count of values involved is 0. When you call `Rc::downgrade`, you get a smart pointer of type `Weak<T>`. Instead of increasing the `strong_count` in the `Rc<T>` instance by 1, calling @@ -127,7 +131,7 @@ Instead of increasing the `strong_count` in the `Rc<T>` instance by 1, calling `Rc<T>` instance to be cleaned up. Because the value that `Weak<T>` references might have been dropped, to do -anything with the value that a `Weak<T>` is pointing to, you must make sure the +anything with the value that a `Weak<T>` is pointing to you must make sure the value still exists. Do this by calling the `upgrade` method on a `Weak<T>` instance, which will return an `Option<Rc<T>>`. You’ll get a result of `Some` if the `Rc<T>` value has not been dropped yet and a result of `None` if the @@ -139,7 +143,7 @@ As an example, rather than using a list whose items know only about the next item, we’ll create a tree whose items know about their children items _and_ their parent items. -#### Creating a Tree Data Structure: a `Node` with Child Nodes +#### Creating a Tree Data Structure: A `Node` with Child Nodes To start, we’ll build a tree with nodes that know about their child nodes. We’ll create a struct named `Node` that holds its own `i32` value as well as @@ -158,8 +162,8 @@ modify which nodes are children of another node, so we have a `RefCell<T>` in `children` around the `Vec<Rc<Node>>`. Next, we’ll use our struct definition and create one `Node` instance named -`leaf` with the value 3 and no children, and another instance named `branch` -with the value 5 and `leaf` as one of its children, as shown in Listing 15-27: +`leaf` with the value `3` and no children, and another instance named `branch` +with the value `5` and `leaf` as one of its children, as shown in Listing 15-27. <Listing number="15-27" file-name="src/main.rs" caption="Creating a `leaf` node with no children and a `branch` node with `leaf` as one of its children"> @@ -180,7 +184,7 @@ parent. We’ll do that next. To make the child node aware of its parent, we need to add a `parent` field to our `Node` struct definition. The trouble is in deciding what the type of -`parent` should be. We know it can’t contain an `Rc<T>`, because that would +`parent` should be. We know it can’t contain an `Rc<T>` because that would create a reference cycle with `leaf.parent` pointing to `branch` and `branch.children` pointing to `leaf`, which would cause their `strong_count` values to never be 0. @@ -190,7 +194,7 @@ children: if a parent node is dropped, its child nodes should be dropped as well. However, a child should not own its parent: if we drop a child node, the parent should still exist. This is a case for weak references! -So instead of `Rc<T>`, we’ll make the type of `parent` use `Weak<T>`, +So, instead of `Rc<T>`, we’ll make the type of `parent` use `Weak<T>`, specifically a `RefCell<Weak<Node>>`. Now our `Node` struct definition looks like this: @@ -202,7 +206,7 @@ like this: A node will be able to refer to its parent node but doesn’t own its parent. In Listing 15-28, we update `main` to use this new definition so the `leaf` -node will have a way to refer to its parent, `branch`: +node will have a way to refer to its parent, `branch`. <Listing number="15-28" file-name="src/main.rs" caption="A `leaf` node with a weak reference to its parent node `branch`"> @@ -254,7 +258,7 @@ Let’s look at how the `strong_count` and `weak_count` values of the `Rc<Node>` instances change by creating a new inner scope and moving the creation of `branch` into that scope. By doing so, we can see what happens when `branch` is created and then dropped when it goes out of scope. The modifications are shown -in Listing 15-29: +in Listing 15-29. <Listing number="15-29" file-name="src/main.rs" caption="Creating `branch` in an inner scope and examining strong and weak reference counts"> @@ -266,10 +270,10 @@ in Listing 15-29: After `leaf` is created, its `Rc<Node>` has a strong count of 1 and a weak count of 0. In the inner scope, we create `branch` and associate it with -`leaf`, at which point, when we print the counts, the `Rc<Node>` in `branch` +`leaf`, at which point when we print the counts, the `Rc<Node>` in `branch` will have a strong count of 1 and a weak count of 1 (for `leaf.parent` pointing to `branch` with a `Weak<Node>`). When we print the counts in `leaf`, we’ll see -it will have a strong count of 2, because `branch` now has a clone of the +it will have a strong count of 2 because `branch` now has a clone of the `Rc<Node>` of `leaf` stored in `branch.children`, but will still have a weak count of 0. diff --git a/src/ch16-00-concurrency.md b/src/ch16-00-concurrency.md index 21511cd58f..dcfb83d100 100644 --- a/src/ch16-00-concurrency.md +++ b/src/ch16-00-concurrency.md @@ -24,9 +24,9 @@ subtle bugs and is easy to refactor without introducing new bugs. > Note: For simplicity’s sake, we’ll refer to many of the problems as > _concurrent_ rather than being more precise by saying _concurrent and/or -> parallel_. If this book were about concurrency and/or parallelism, we’d be -> more specific. For this chapter, please mentally substitute _concurrent -> and/or parallel_ whenever we use _concurrent_. +> parallel_. For this chapter, please mentally substitute _concurrent +> and/or parallel_ whenever we use _concurrent_. In the next chapter, where the +> distinction matters more, we’ll be more specific. Many languages are dogmatic about the solutions they offer for handling concurrent problems. For example, Erlang has elegant functionality for diff --git a/src/ch16-01-threads.md b/src/ch16-01-threads.md index bc72bbb546..0e5774566d 100644 --- a/src/ch16-01-threads.md +++ b/src/ch16-01-threads.md @@ -90,12 +90,13 @@ will get to run at all! We can fix the problem of the spawned thread not running or ending prematurely by saving the return value of `thread::spawn` in a variable. The return type of -`thread::spawn` is `JoinHandle`. A `JoinHandle` is an owned value that, when we -call the `join` method on it, will wait for its thread to finish. Listing 16-2 -shows how to use the `JoinHandle` of the thread we created in Listing 16-1 and -how to call `join` to make sure the spawned thread finishes before `main` exits: +`thread::spawn` is `JoinHandle<T>`. A `JoinHandle<T>` is an owned value that, +when we call the `join` method on it, will wait for its thread to finish. +Listing 16-2 shows how to use the `JoinHandle<T>` of the thread we created in +Listing 16-1 and how to call `join` to make sure the spawned thread finishes +before `main` exits. -<Listing number="16-2" file-name="src/main.rs" caption="Saving a `JoinHandle` from `thread::spawn` to guarantee the thread is run to completion"> +<Listing number="16-2" file-name="src/main.rs" caption="Saving a `JoinHandle<T>` from `thread::spawn` to guarantee the thread is run to completion"> ```rust {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-02/src/main.rs}} @@ -174,7 +175,7 @@ threads run at the same time. We'll often use the `move` keyword with closures passed to `thread::spawn` because the closure will then take ownership of the values it uses from the environment, thus transferring ownership of those values from one thread to -another. In [“Capturing References or Moving Ownership”][capture]<!-- ignore --> +another. In [“Capturing the Environment With Closures”][capture]<!-- ignore --> in Chapter 13, we discussed `move` in the context of closures. Now, we’ll concentrate more on the interaction between `move` and `thread::spawn`. @@ -183,7 +184,7 @@ arguments: we’re not using any data from the main thread in the spawned thread’s code. To use data from the main thread in the spawned thread, the spawned thread’s closure must capture the values it needs. Listing 16-3 shows an attempt to create a vector in the main thread and use it in the spawned -thread. However, this won’t yet work, as you’ll see in a moment. +thread. However, this won’t work yet, as you’ll see in a moment. <Listing number="16-3" file-name="src/main.rs" caption="Attempting to use a vector created by the main thread in another thread"> @@ -204,8 +205,8 @@ example, we get the following error: Rust _infers_ how to capture `v`, and because `println!` only needs a reference to `v`, the closure tries to borrow `v`. However, there’s a problem: Rust can’t -tell how long the spawned thread will run, so it doesn’t know if the reference -to `v` will always be valid. +tell how long the spawned thread will run, so it doesn’t know whether the +reference to `v` will always be valid. Listing 16-4 provides a scenario that’s more likely to have a reference to `v` that won’t be valid: @@ -218,10 +219,10 @@ that won’t be valid: </Listing> -If Rust allowed us to run this code, there’s a possibility the spawned thread -would be immediately put in the background without running at all. The spawned -thread has a reference to `v` inside, but the main thread immediately drops -`v`, using the `drop` function we discussed in Chapter 15. Then, when the +If Rust allowed us to run this code, there’s a possibility that the spawned +thread would be immediately put in the background without running at all. The +spawned thread has a reference to `v` inside, but the main thread immediately +drops `v`, using the `drop` function we discussed in Chapter 15. Then, when the spawned thread starts to execute, `v` is no longer valid, so a reference to it is also invalid. Oh no! @@ -242,7 +243,7 @@ help: to force the closure to take ownership of `v` (and any other referenced va By adding the `move` keyword before the closure, we force the closure to take ownership of the values it’s using rather than allowing Rust to infer that it should borrow the values. The modification to Listing 16-3 shown in Listing -16-5 will compile and run as we intend: +16-5 will compile and run as we intend. <Listing number="16-5" file-name="src/main.rs" caption="Using the `move` keyword to force a closure to take ownership of the values it uses"> @@ -273,7 +274,7 @@ rules when we try to use `v` in the main thread. The `move` keyword overrides Rust’s conservative default of borrowing; it doesn’t let us violate the ownership rules. -With a basic understanding of threads and the thread API, let’s look at what we -can _do_ with threads. +Now that we’ve covered what threads are and the methods supplied by the thread +API, let’s look at some situations in which we can use threads. -[capture]: ch13-01-closures.html#capturing-references-or-moving-ownership +[capture]: ch13-01-closures.html#capturing-the-environment-with-closures diff --git a/src/ch16-02-message-passing.md b/src/ch16-02-message-passing.md index 917a93dc15..7f723e7986 100644 --- a/src/ch16-02-message-passing.md +++ b/src/ch16-02-message-passing.md @@ -14,7 +14,7 @@ water, such as a stream or a river. If you put something like a rubber duck into a river, it will travel downstream to the end of the waterway. A channel has two halves: a transmitter and a receiver. The transmitter half is -the upstream location where you put rubber ducks into the river, and the +the upstream location where you put the rubber duck into the river, and the receiver half is where the rubber duck ends up downstream. One part of your code calls methods on the transmitter with the data you want to send, and another part checks the receiving end for arriving messages. A channel is said @@ -24,9 +24,9 @@ Here, we’ll work up to a program that has one thread to generate values and send them down a channel, and another thread that will receive the values and print them out. We’ll be sending simple values between threads using a channel to illustrate the feature. Once you’re familiar with the technique, you could -use channels for any threads that need to communicate between each other, such -as a chat system or a system where many threads perform parts of a calculation -and send the parts to one thread that aggregates the results. +use channels for any threads that need to communicate with each other, such as +a chat system or a system where many threads perform parts of a calculation and +send the parts to one thread that aggregates the results. First, in Listing 16-6, we’ll create a channel but not do anything with it. Note that this won’t compile yet because Rust can’t tell what type of values we @@ -51,21 +51,21 @@ producer for now, but we’ll add multiple producers when we get this example working. The `mpsc::channel` function returns a tuple, the first element of which is the -sending end—the transmitter—and the second element is the receiving end—the -receiver. The abbreviations `tx` and `rx` are traditionally used in many fields -for _transmitter_ and _receiver_ respectively, so we name our variables as such -to indicate each end. We’re using a `let` statement with a pattern that +sending end—the transmitter—and the second element of which is the receiving +end—the receiver. The abbreviations `tx` and `rx` are traditionally used in many +fields for _transmitter_ and _receiver_, respectively, so we name our variables +as such to indicate each end. We’re using a `let` statement with a pattern that destructures the tuples; we’ll discuss the use of patterns in `let` statements -and destructuring in Chapter 19. For now, know that using a `let` statement -this way is a convenient approach to extract the pieces of the tuple returned -by `mpsc::channel`. +and destructuring in Chapter 19. For now, know that using a `let` statement this +way is a convenient approach to extract the pieces of the tuple returned by +`mpsc::channel`. Let’s move the transmitting end into a spawned thread and have it send one -string so that the spawned thread is communicating with the main thread, as -shown in Listing 16-7. This is like putting a rubber duck in the river upstream -or sending a chat message from one thread to another. +string so the spawned thread is communicating with the main thread, as shown in +Listing 16-7. This is like putting a rubber duck in the river upstream or +sending a chat message from one thread to another. -<Listing number="16-7" file-name="src/main.rs" caption="Moving `tx` to a spawned thread and sending “hi”"> +<Listing number="16-7" file-name="src/main.rs" caption='Moving `tx` to a spawned thread and sending `"hi"`'> ```rust {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-07/src/main.rs}} @@ -74,7 +74,7 @@ or sending a chat message from one thread to another. </Listing> Again, we’re using `thread::spawn` to create a new thread and then using `move` -to move `tx` into the closure so that the spawned thread owns `tx`. The spawned +to move `tx` into the closure so the spawned thread owns `tx`. The spawned thread needs to own the transmitter to be able to send messages through the channel. @@ -89,7 +89,7 @@ In Listing 16-8, we’ll get the value from the receiver in the main thread. Thi is like retrieving the rubber duck from the water at the end of the river or receiving a chat message. -<Listing number="16-8" file-name="src/main.rs" caption="Receiving the value “hi” in the main thread and printing it"> +<Listing number="16-8" file-name="src/main.rs" caption='Receiving the value `"hi"` in the main thread and printing it'> ```rust {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-08/src/main.rs}} @@ -136,7 +136,7 @@ advantage of thinking about ownership throughout your Rust programs. Let’s do an experiment to show how channels and ownership work together to prevent problems: we’ll try to use a `val` value in the spawned thread _after_ we’ve sent it down the channel. Try compiling the code in Listing 16-9 to see why -this code isn’t allowed: +this code isn’t allowed. <Listing number="16-9" file-name="src/main.rs" caption="Attempting to use `val` after we’ve sent it down the channel"> @@ -170,7 +170,7 @@ two separate threads were talking to each other over the channel. In Listing running concurrently: the spawned thread will now send multiple messages and pause for a second between each message. -<Listing number="16-10" file-name="src/main.rs" caption="Sending multiple messages and pausing between each"> +<Listing number="16-10" file-name="src/main.rs" caption="Sending multiple messages and pausing between each one"> ```rust,noplayground {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-10/src/main.rs}} @@ -181,14 +181,14 @@ pause for a second between each message. This time, the spawned thread has a vector of strings that we want to send to the main thread. We iterate over them, sending each individually, and pause between each by calling the `thread::sleep` function with a `Duration` value of -1 second. +one second. In the main thread, we’re not calling the `recv` function explicitly anymore: instead, we’re treating `rx` as an iterator. For each value received, we’re printing it. When the channel is closed, iteration will end. When running the code in Listing 16-10, you should see the following output -with a 1-second pause in between each line: +with a one-second pause in between each line: <!-- Not extracting output because changes to this output aren't significant; the changes are likely to be due to the threads running differently rather than @@ -210,7 +210,7 @@ the spawned thread. Earlier we mentioned that `mpsc` was an acronym for _multiple producer, single consumer_. Let’s put `mpsc` to use and expand the code in Listing 16-10 to create multiple threads that all send values to the same receiver. We can do -so by cloning the transmitter, as shown in Listing 16-11: +so by cloning the transmitter, as shown in Listing 16-11. <Listing number="16-11" file-name="src/main.rs" caption="Sending multiple messages from multiple producers"> diff --git a/src/ch16-03-shared-state.md b/src/ch16-03-shared-state.md index 9f4936ce39..437d5650b7 100644 --- a/src/ch16-03-shared-state.md +++ b/src/ch16-03-shared-state.md @@ -1,16 +1,16 @@ ## Shared-State Concurrency -Message passing is a fine way of handling concurrency, but it’s not the only -one. Another method would be for multiple threads to access the same shared +Message passing is a fine way to handle concurrency, but it’s not the only +way. Another method would be for multiple threads to access the same shared data. Consider this part of the slogan from the Go language documentation -again: “do not communicate by sharing memory.” +again: “Do not communicate by sharing memory.” What would communicating by sharing memory look like? In addition, why would message-passing enthusiasts caution not to use memory sharing? In a way, channels in any programming language are similar to single ownership, because once you transfer a value down a channel, you should no longer use that -value. Shared memory concurrency is like multiple ownership: multiple threads +value. Shared-memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time. As you saw in Chapter 15, where smart pointers made multiple ownership possible, multiple ownership can add complexity because these different owners need managing. Rust’s type system @@ -20,7 +20,7 @@ for shared memory. ### Using Mutexes to Allow Access to Data from One Thread at a Time -_Mutex_ is an abbreviation for _mutual exclusion_, as in, a mutex allows only +_Mutex_ is an abbreviation for _mutual exclusion_, as in a mutex allows only one thread to access some data at any given time. To access the data in a mutex, a thread must first signal that it wants access by asking to acquire the mutex’s _lock_. The lock is a data structure that is part of the mutex that @@ -50,7 +50,7 @@ system and ownership rules, you can’t get locking and unlocking wrong. #### The API of `Mutex<T>` As an example of how to use a mutex, let’s start by using a mutex in a -single-threaded context, as shown in Listing 16-12: +single-threaded context, as shown in Listing 16-12. <Listing number="16-12" file-name="src/main.rs" caption="Exploring the API of `Mutex<T>` in a single-threaded context for simplicity"> @@ -91,13 +91,13 @@ to change the inner `i32` to 6. #### Sharing a `Mutex<T>` Between Multiple Threads -Now, let’s try to share a value between multiple threads using `Mutex<T>`. -We’ll spin up 10 threads and have them each increment a counter value by 1, so -the counter goes from 0 to 10. The next example in Listing 16-13 will have -a compiler error, and we’ll use that error to learn more about using -`Mutex<T>` and how Rust helps us use it correctly. +Now let’s try to share a value between multiple threads using `Mutex<T>`. We’ll +spin up 10 threads and have them each increment a counter value by 1, so the +counter goes from 0 to 10. The example in Listing 16-13 will have a compiler +error, and we’ll use that error to learn more about using `Mutex<T>` and how +Rust helps us use it correctly. -<Listing number="16-13" file-name="src/main.rs" caption="Ten threads each increment a counter guarded by a `Mutex<T>`"> +<Listing number="16-13" file-name="src/main.rs" caption="Ten threads, each incrementing a counter guarded by a `Mutex<T>`"> ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch16-fearless-concurrency/listing-16-13/src/main.rs}} @@ -126,12 +126,12 @@ We hinted that this example wouldn’t compile. Now let’s find out why! The error message states that the `counter` value was moved in the previous iteration of the loop. Rust is telling us that we can’t move the ownership -of `counter` into multiple threads. Let’s fix the compiler error with a +of lock `counter` into multiple threads. Let’s fix the compiler error with the multiple-ownership method we discussed in Chapter 15. #### Multiple Ownership with Multiple Threads -In Chapter 15, we gave a value multiple owners by using the smart pointer +In Chapter 15, we gave a value to multiple owners by using the smart pointer `Rc<T>` to create a reference counted value. Let’s do the same here and see what happens. We’ll wrap the `Mutex<T>` in `Rc<T>` in Listing 16-14 and clone the `Rc<T>` before moving ownership to the thread. @@ -186,7 +186,7 @@ guarantees atomics provide. Let’s return to our example: `Arc<T>` and `Rc<T>` have the same API, so we fix our program by changing the `use` line, the call to `new`, and the call to -`clone`. The code in Listing 16-15 will finally compile and run: +`clone`. The code in Listing 16-15 will finally compile and run. <Listing number="16-15" file-name="src/main.rs" caption="Using an `Arc<T>` to wrap the `Mutex<T>` to be able to share ownership across multiple threads"> @@ -221,7 +221,7 @@ type for this example so we could concentrate on how `Mutex<T>` works. ### Similarities Between `RefCell<T>`/`Rc<T>` and `Mutex<T>`/`Arc<T>` -You might have noticed that `counter` is immutable, but we could get a mutable +You might have noticed that `counter` is immutable but we could get a mutable reference to the value inside it; this means `Mutex<T>` provides interior mutability, as the `Cell` family does. In the same way we used `RefCell<T>` in Chapter 15 to allow us to mutate contents inside an `Rc<T>`, we use `Mutex<T>` diff --git a/src/ch16-04-extensible-concurrency-sync-and-send.md b/src/ch16-04-extensible-concurrency-sync-and-send.md index 22a909bed8..92a15b603e 100644 --- a/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -1,30 +1,35 @@ -## Extensible Concurrency with the `Sync` and `Send` Traits +## Extensible Concurrency with the `Send` and `Sync` Traits -Interestingly, the Rust language has _very_ few concurrency features. Almost -every concurrency feature we’ve talked about so far in this chapter has been -part of the standard library, not the language. Your options for handling -concurrency are not limited to the language or the standard library; you can -write your own concurrency features or use those written by others. +<!-- Old link, do not remove --> -However, two concurrency concepts are embedded in the language: the -`std::marker` traits `Sync` and `Send`. +<a id="extensible-concurrency-with-the-sync-and-send-traits"></a> + +Interestingly, almost every concurrency feature we’ve talked about so far in +this chapter has been part of the standard library, not the language. Your +options for handling concurrency are not limited to the language or the standard +library; you can write your own concurrency features or use those written by +others. + +However, among the key concurrency concepts that are embedded in the language +rather than the standard library are the `std::marker` traits `Send` and +`Sync`. ### Allowing Transference of Ownership Between Threads with `Send` The `Send` marker trait indicates that ownership of values of the type implementing `Send` can be transferred between threads. Almost every Rust type -is `Send`, but there are some exceptions, including `Rc<T>`: this cannot be -`Send` because if you cloned an `Rc<T>` value and tried to transfer ownership -of the clone to another thread, both threads might update the reference count -at the same time. For this reason, `Rc<T>` is implemented for use in -single-threaded situations where you don’t want to pay the thread-safe +is `Send`, but there are some exceptions, including `Rc<T>`: this cannot +implement `Send` because if you cloned an `Rc<T>` value and tried to transfer +ownership of the clone to another thread, both threads might update the +reference count at the same time. For this reason, `Rc<T>` is implemented for +use in single-threaded situations where you don’t want to pay the thread-safe performance penalty. Therefore, Rust’s type system and trait bounds ensure that you can never accidentally send an `Rc<T>` value across threads unsafely. When we tried to do this in Listing 16-14, we got the error `the trait Send is not implemented for -Rc<Mutex<i32>>`. When we switched to `Arc<T>`, which is `Send`, the code -compiled. +Rc<Mutex<i32>>`. When we switched to `Arc<T>`, which does implement `Send`, the +code compiled. Any type composed entirely of `Send` types is automatically marked as `Send` as well. Almost all primitive types are `Send`, aside from raw pointers, which @@ -33,25 +38,27 @@ we’ll discuss in Chapter 20. ### Allowing Access from Multiple Threads with `Sync` The `Sync` marker trait indicates that it is safe for the type implementing -`Sync` to be referenced from multiple threads. In other words, any type `T` is -`Sync` if `&T` (an immutable reference to `T`) is `Send`, meaning the reference -can be sent safely to another thread. Similar to `Send`, primitive types are -`Sync`, and types composed entirely of types that are `Sync` are also `Sync`. - -The smart pointer `Rc<T>` is also not `Sync` for the same reasons that it’s not -`Send`. The `RefCell<T>` type (which we talked about in Chapter 15) and the -family of related `Cell<T>` types are not `Sync`. The implementation of borrow -checking that `RefCell<T>` does at runtime is not thread-safe. The smart -pointer `Mutex<T>` is `Sync` and can be used to share access with multiple -threads as you saw in [“Sharing a `Mutex<T>` Between Multiple -Threads”][sharing-a-mutext-between-multiple-threads]<!-- ignore -->. +`Sync` to be referenced from multiple threads. In other words, any type `T` +implements `Sync` if `&T` (an immutable reference to `T`) implements `Send`, +meaning the reference can be sent safely to another thread. Similar to `Send`, +primitive types all implement `Sync`, and types composed entirely of types that +implement `Sync` also implement `Sync`. + +The smart pointer `Rc<T>` also doesn’t implement `Sync` for the same reasons +that it doesn’t implement `Send`. The `RefCell<T>` type (which we talked about +in Chapter 15) and the family of related `Cell<T>` types don’t implement `Sync`. +The implementation of borrow checking that `RefCell<T>` does at runtime is not +thread-safe. The smart pointer `Mutex<T>` implements `Sync` and can be used to +share access with multiple threads as you saw in [“Sharing a `Mutex<T>` Between +Multiple Threads”][sharing-a-mutext-between-multiple-threads]<!-- ignore -->. ### Implementing `Send` and `Sync` Manually Is Unsafe -Because types that are made up of `Send` and `Sync` traits are automatically -also `Send` and `Sync`, we don’t have to implement those traits manually. As -marker traits, they don’t even have any methods to implement. They’re just -useful for enforcing invariants related to concurrency. +Because types composed entirely of other types that implement the `Send` and +`Sync` traits also automatically implement `Send` and `Sync`, we don’t have to +implement those traits manually. As marker traits, they don’t even have any +methods to implement. They’re just useful for enforcing invariants related to +concurrency. Manually implementing these traits involves implementing unsafe Rust code. We’ll talk about using unsafe Rust code in Chapter 20; for now, the important @@ -62,8 +69,8 @@ uphold them. ## Summary -This isn’t the last you’ll see of concurrency in this book: the whole next -chapter focuses on async programming, and the project in Chapter 21 will use the +This isn’t the last you’ll see of concurrency in this book: the next chapter +focuses on async programming, and the project in Chapter 21 will use the concepts in this chapter in a more realistic situation than the smaller examples discussed here. diff --git a/src/ch17-03-more-futures.md b/src/ch17-03-more-futures.md index 34428c933a..d3c3aa87e2 100644 --- a/src/ch17-03-more-futures.md +++ b/src/ch17-03-more-futures.md @@ -124,72 +124,24 @@ the errors mentioning `Unpin`. Although there are three of them, their contents are very similar. <!-- manual-regeneration -cd listings/ch17-async-await/listing-17-16 +cd listings/ch17-async-await/listing-17-17 cargo build # copy *only* the errors # fix the paths --> ```text -error[E0308]: mismatched types - --> src/main.rs:46:46 - | -10 | let tx1_fut = async move { - | ---------- the expected `async` block -... -24 | let rx_fut = async { - | ----- the found `async` block -... -46 | vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; - | -------- ^^^^^^ expected `async` block, found a different `async` block - | | - | arguments to this function are incorrect - | - = note: expected `async` block `{async block@src/main.rs:10:23: 10:33}` - found `async` block `{async block@src/main.rs:24:22: 24:27}` - = note: no two async blocks, even if identical, have the same type - = help: consider pinning your async block and casting it to a trait object -note: associated function defined here - --> file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/boxed.rs:252:12 - | -252 | pub fn new(x: T) -> Self { - | ^^^ - -error[E0308]: mismatched types - --> src/main.rs:46:64 +error[E0277]: `dyn Future<Output = ()>` cannot be unpinned + --> src/main.rs:49:24 | -10 | let tx1_fut = async move { - | ---------- the expected `async` block -... -30 | let tx_fut = async move { - | ---------- the found `async` block -... -46 | vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)]; - | -------- ^^^^^^ expected `async` block, found a different `async` block - | | - | arguments to this function are incorrect - | - = note: expected `async` block `{async block@src/main.rs:10:23: 10:33}` - found `async` block `{async block@src/main.rs:30:22: 30:32}` - = note: no two async blocks, even if identical, have the same type - = help: consider pinning your async block and casting it to a trait object -note: associated function defined here - --> file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/boxed.rs:252:12 - | -252 | pub fn new(x: T) -> Self { - | ^^^ - -error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned - --> src/main.rs:48:24 - | -48 | trpl::join_all(futures).await; - | -------------- ^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}` +49 | trpl::join_all(futures).await; + | -------------- ^^^^^^^ the trait `Unpin` is not implemented for `dyn Future<Output = ()>` | | | required by a bound introduced by this call | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` + = note: required for `Box<dyn Future<Output = ()>>` to implement `Future` note: required by a bound in `join_all` --> file:///home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.30/src/future/join_all.rs:105:14 | @@ -199,15 +151,15 @@ note: required by a bound in `join_all` 105 | I::Item: Future, | ^^^^^^ required by this bound in `join_all` -error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned - --> src/main.rs:48:9 +error[E0277]: `dyn Future<Output = ()>` cannot be unpinned + --> src/main.rs:49:9 | -48 | trpl::join_all(futures).await; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}` +49 | trpl::join_all(futures).await; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `dyn Future<Output = ()>` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` + = note: required for `Box<dyn Future<Output = ()>>` to implement `Future` note: required by a bound in `futures_util::future::join_all::JoinAll` --> file:///home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.30/src/future/join_all.rs:29:8 | @@ -217,15 +169,15 @@ note: required by a bound in `futures_util::future::join_all::JoinAll` 29 | F: Future, | ^^^^^^ required by this bound in `JoinAll` -error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned - --> src/main.rs:48:33 +error[E0277]: `dyn Future<Output = ()>` cannot be unpinned + --> src/main.rs:49:33 | -48 | trpl::join_all(futures).await; - | ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}` +49 | trpl::join_all(futures).await; + | ^^^^^ the trait `Unpin` is not implemented for `dyn Future<Output = ()>` | = note: consider using the `pin!` macro consider using `Box::pin` if you need to access the pinned value outside of the current scope - = note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future` + = note: required for `Box<dyn Future<Output = ()>>` to implement `Future` note: required by a bound in `futures_util::future::join_all::JoinAll` --> file:///home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.30/src/future/join_all.rs:29:8 | @@ -234,6 +186,9 @@ note: required by a bound in `futures_util::future::join_all::JoinAll` 28 | where 29 | F: Future, | ^^^^^^ required by this bound in `JoinAll` + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `async_await` (bin "async_await") due to 3 previous errors ``` That is a _lot_ to digest, so let’s pull it apart. The first part of the message diff --git a/src/ch17-04-streams.md b/src/ch17-04-streams.md index e56464abb9..196da5ca1b 100644 --- a/src/ch17-04-streams.md +++ b/src/ch17-04-streams.md @@ -42,7 +42,8 @@ We start with an array of numbers, which we convert to an iterator and then call using the `trpl::stream_from_iter` function. Next, we loop over the items in the stream as they arrive with the `while let` loop. -Unfortunately, when we try to run the code, it doesn’t compile, but instead it reports that there’s no `next` method available: +Unfortunately, when we try to run the code, it doesn’t compile, but instead it +reports that there’s no `next` method available: <!-- manual-regeneration cd listings/ch17-async-await/listing-17-30 @@ -57,7 +58,7 @@ error[E0599]: no method named `next` found for struct `Iter` in the current scop 10 | while let Some(value) = stream.next().await { | ^^^^ | - = note: the full type name has been written to '/Users/chris/dev/rust-lang/book/main/listings/ch17-async-await/listing-17-30/target/debug/deps/async_await-575db3dd3197d257.long-type-14490787947592691573.txt' + = note: the full type name has been written to 'file:///projects/async-await/target/debug/deps/async_await-575db3dd3197d257.long-type-14490787947592691573.txt' = note: consider using `--verbose` to print the full type name to the console = help: items from traits can only be used if the trait is in scope help: the following traits which provide `next` are implemented but not in scope; perhaps you want to import one of them diff --git a/src/ch17-05-traits-for-async.md b/src/ch17-05-traits-for-async.md index f8706dba14..d4447a34b5 100644 --- a/src/ch17-05-traits-for-async.md +++ b/src/ch17-05-traits-for-async.md @@ -301,14 +301,15 @@ itself cannot move, because it is still pinned. However, most types are perfectly safe to move around, even if they happen to be behind a `Pin` wrapper. We only need to think about pinning when items have -internal references. Primitive values such as numbers and Booleans obviously -don’t have any internal references, so they’re safe. Neither do most types you -normally work with in Rust. You can move around a `Vec`, for example, without -worrying. Given only what we have seen so far, if you have a `Pin<Vec<String>>`, -you’d have to do everything via the safe but restrictive APIs provided by `Pin`, -even though a `Vec<String>` is always safe to move if there are no other -references to it. We need a way to tell the compiler that it’s fine to move -items around in cases like this—and there’s where `Unpin` comes into play. +internal references. Primitive values such as numbers and Booleans are safe +because they obviously don’t have any internal references. Neither do most types +you normally work with in Rust. You can move around a `Vec`, for example, +without worrying. Given only what we have seen so far, if you have a +`Pin<Vec<String>>`, you’d have to do everything via the safe but restrictive +APIs provided by `Pin`, even though a `Vec<String>` is always safe to move if +there are no other references to it. We need a way to tell the compiler that +it’s fine to move items around in cases like this—and that’s where `Unpin` comes +into play. `Unpin` is a marker trait, similar to the `Send` and `Sync` traits we saw in Chapter 16, and thus has no functionality of its own. Marker traits exist only diff --git a/src/ch18-00-oop.md b/src/ch18-00-oop.md index adcde4c5aa..5f56abb8d7 100644 --- a/src/ch18-00-oop.md +++ b/src/ch18-00-oop.md @@ -1,13 +1,17 @@ -# Object-Oriented Programming Features of Rust +# Object-Oriented Programming Features + +<!-- Old link, do not remove --> + +<a id="object-oriented-programming-features-of-rust"></a> Object-oriented programming (OOP) is a way of modeling programs. Objects as a programmatic concept were introduced in the programming language Simula in the 1960s. Those objects influenced Alan Kay’s programming architecture in which objects pass messages to each other. To describe this architecture, he coined the term _object-oriented programming_ in 1967. Many competing definitions -describe what OOP is, and by some of these definitions Rust is object-oriented, +describe what OOP is, and by some of these definitions Rust is object oriented but by others it is not. In this chapter, we’ll explore certain characteristics -that are commonly considered object-oriented and how those characteristics +that are commonly considered object oriented and how those characteristics translate to idiomatic Rust. We’ll then show you how to implement an object-oriented design pattern in Rust and discuss the trade-offs of doing so versus implementing a solution using some of Rust’s strengths instead. diff --git a/src/ch18-01-what-is-oo.md b/src/ch18-01-what-is-oo.md index ed7dbb5523..73023bb15c 100644 --- a/src/ch18-01-what-is-oo.md +++ b/src/ch18-01-what-is-oo.md @@ -1,7 +1,7 @@ ## Characteristics of Object-Oriented Languages There is no consensus in the programming community about what features a -language must have to be considered object-oriented. Rust is influenced by many +language must have to be considered object oriented. Rust is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages share certain common characteristics, namely objects, encapsulation, and @@ -11,20 +11,20 @@ Rust supports it. ### Objects Contain Data and Behavior The book _Design Patterns: Elements of Reusable Object-Oriented Software_ by -Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley -Professional, 1994), colloquially referred to as _The Gang of Four_ book, is a -catalog of object-oriented design patterns. It defines OOP this way: +Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, +1994), colloquially referred to as _The Gang of Four_ book, is a catalog of +object-oriented design patterns. It defines OOP in this way: > Object-oriented programs are made up of objects. An **object** packages both > data and the procedures that operate on that data. The procedures are > typically called **methods** or **operations**. -Using this definition, Rust is object-oriented: structs and enums have data, +Using this definition, Rust is object oriented: structs and enums have data, and `impl` blocks provide methods on structs and enums. Even though structs and enums with methods aren’t _called_ objects, they provide the same functionality, according to the Gang of Four’s definition of objects. -### Encapsulation that Hides Implementation Details +### Encapsulation That Hides Implementation Details Another aspect commonly associated with OOP is the idea of _encapsulation_, which means that the implementation details of an object aren’t accessible to @@ -82,14 +82,14 @@ Because we’ve encapsulated the implementation details of the struct `AveragedCollection`, we can easily change aspects, such as the data structure, in the future. For instance, we could use a `HashSet<i32>` instead of a `Vec<i32>` for the `list` field. As long as the signatures of the `add`, -`remove`, and `average` public methods stay the same, code using -`AveragedCollection` wouldn’t need to change in order to compile. If we made -`list` public instead, this wouldn’t necessarily be the case: `HashSet<i32>` and -`Vec<i32>` have different methods for adding and removing items, so the external -code would likely have to change if it were modifying `list` directly. +`remove`, and `average` public methods stayed the same, code using +`AveragedCollection` wouldn’t need to change. If we made `list` public instead, +this wouldn’t necessarily be the case: `HashSet<i32>` and `Vec<i32>` have +different methods for adding and removing items, so the external code would +likely have to change if it were modifying `list` directly. If encapsulation is a required aspect for a language to be considered -object-oriented, then Rust meets that requirement. The option to use `pub` or +object oriented, then Rust meets that requirement. The option to use `pub` or not for different parts of code enables encapsulation of implementation details. ### Inheritance as a Type System and as Code Sharing @@ -98,9 +98,9 @@ _Inheritance_ is a mechanism whereby an object can inherit elements from another object’s definition, thus gaining the parent object’s data and behavior without you having to define them again. -If a language must have inheritance to be an object-oriented language, then -Rust is not one. There is no way to define a struct that inherits the parent -struct’s fields and method implementations without using a macro. +If a language must have inheritance to be object oriented, then Rust is not such +a language. There is no way to define a struct that inherits the parent struct’s +fields and method implementations without using a macro. However, if you’re used to having inheritance in your programming toolbox, you can use other solutions in Rust, depending on your reason for reaching for @@ -134,15 +134,15 @@ each other at runtime if they share certain characteristics. > trait bounds to impose constraints on what those types must provide. This is > sometimes called _bounded parametric polymorphism_. -Inheritance has recently fallen out of favor as a programming design solution -in many programming languages because it’s often at risk of sharing more code -than necessary. Subclasses shouldn’t always share all characteristics of their -parent class but will do so with inheritance. This can make a program’s design -less flexible. It also introduces the possibility of calling methods on -subclasses that don’t make sense or that cause errors because the methods don’t -apply to the subclass. In addition, some languages will only allow _single -inheritance_ (meaning a subclass can only inherit from one class), further -restricting the flexibility of a program’s design. +Inheritance has recently fallen out of favor as a programming design solution in +many programming languages because it’s often at risk of sharing more code than +necessary. Subclasses shouldn’t always share all characteristics of their parent +class but will do so with inheritance. This can make a program’s design less +flexible. It also introduces the possibility of calling methods on subclasses +that don’t make sense or that cause errors because the methods don’t apply to +the subclass. In addition, some languages will only allow single inheritance +(meaning a subclass can only inherit from one class), further restricting the +flexibility of a program’s design. For these reasons, Rust takes the different approach of using trait objects instead of inheritance. Let’s look at how trait objects enable polymorphism in diff --git a/src/ch18-02-trait-objects.md b/src/ch18-02-trait-objects.md index c2fa2c621b..6602074853 100644 --- a/src/ch18-02-trait-objects.md +++ b/src/ch18-02-trait-objects.md @@ -19,7 +19,7 @@ some types for people to use, such as `Button` or `TextField`. In addition, instance, one programmer might add an `Image` and another might add a `SelectBox`. -We won’t implement a fully fledged GUI library for this example but will show +We won’t implement a full-fledged GUI library for this example but will show how the pieces would fit together. At the time of writing the library, we can’t know and define all the types other programmers might want to create. But we do know that `gui` needs to keep track of many values of different types, and it @@ -64,7 +64,7 @@ languages: their specific purpose is to allow abstraction across common behavior. Listing 18-3 shows how to define a trait named `Draw` with one method named -`draw`: +`draw`. <Listing number="18-3" file-name="src/lib.rs" caption="Definition of the `Draw` trait"> @@ -89,7 +89,7 @@ a `Box` that implements the `Draw` trait. </Listing> On the `Screen` struct, we’ll define a method named `run` that will call the -`draw` method on each of its `components`, as shown in Listing 18-5: +`draw` method on each of its `components`, as shown in Listing 18-5. <Listing number="18-5" file-name="src/lib.rs" caption="A `run` method on `Screen` that calls the `draw` method on each component"> @@ -151,8 +151,8 @@ happens when a user clicks the button. These kinds of methods won’t apply to types like `TextField`. If someone using our library decides to implement a `SelectBox` struct that has -`width`, `height`, and `options` fields, they implement the `Draw` trait on the -`SelectBox` type as well, as shown in Listing 18-8: +`width`, `height`, and `options` fields, they would implement the `Draw` trait +on the `SelectBox` type as well, as shown in Listing 18-8. <Listing number="18-8" file-name="src/main.rs" caption="Another crate using `gui` and implementing the `Draw` trait on a `SelectBox` struct"> @@ -199,7 +199,7 @@ if a value doesn’t implement a method but we call it anyway. Rust won’t comp our code if the values don’t implement the traits that the trait objects need. For example, Listing 18-10 shows what happens if we try to create a `Screen` -with a `String` as a component: +with a `String` as a component. <Listing number="18-10" file-name="src/main.rs" caption="Attempting to use a type that doesn’t implement the trait object’s trait"> @@ -215,8 +215,8 @@ We’ll get this error because `String` doesn’t implement the `Draw` trait: {{#include ../listings/ch18-oop/listing-18-10/output.txt}} ``` -This error lets us know that either we’re passing something to `Screen` we -didn’t mean to pass and so should pass a different type or we should implement +This error lets us know that either we’re passing something to `Screen` that we +didn’t mean to pass and so should pass a different type, or we should implement `Draw` on `String` so that `Screen` is able to call `draw` on it. ### Trait Objects Perform Dynamic Dispatch @@ -236,13 +236,14 @@ When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’ know all the types that might be used with the code that’s using trait objects, so it doesn’t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to -call. This lookup incurs a runtime cost that doesn’t occur with static -dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a -method’s code, which in turn prevents some optimizations, and Rust has some -rules about where you can and cannot use dynamic dispatch, called [_dyn -compatibility_][dyn-compatibility]. However, we did get extra flexibility in the code -that we wrote in Listing 18-5 and were able to support in Listing 18-9, so it’s -a trade-off to consider. +call. This lookup incurs a runtime cost that doesn’t occur with static dispatch. +Dynamic dispatch also prevents the compiler from choosing to inline a method’s +code, which in turn prevents some optimizations, and Rust has some rules, called +_dyn compatibility_, about where you can and cannot use dynamic dispatch. Those +rules are beyond the scope of this discussion, but you can read more about them +[in the reference][dyn-compatibility]. However, we did get extra flexibility in +the code that we wrote in Listing 18-5 and were able to support in Listing 18-9, +so it’s a trade-off to consider. [performance-of-code-using-generics]: ch10-01-syntax.html#performance-of-code-using-generics [dynamically-sized]: ch20-03-advanced-types.html#dynamically-sized-types-and-the-sized-trait diff --git a/src/ch18-03-oo-design-patterns.md b/src/ch18-03-oo-design-patterns.md index e46f34d82a..34fea1f017 100644 --- a/src/ch18-03-oo-design-patterns.md +++ b/src/ch18-03-oo-design-patterns.md @@ -5,7 +5,7 @@ pattern is that we define a set of states a value can have internally. The states are represented by a set of _state objects_, and the value’s behavior changes based on its state. We’re going to work through an example of a blog post struct that has a field to hold its state, which will be a state object -from the set "draft", "review", or "published". +from the set “draft”, “review”, or “published”. The state objects share functionality: in Rust, of course, we use structs and traits rather than objects and inheritance. Each state object is responsible @@ -19,9 +19,9 @@ value holding the state or the code that uses the value. We’ll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects. -First, we’re going to implement the state pattern in a more traditional +First we’re going to implement the state pattern in a more traditional object-oriented way, then we’ll use an approach that’s a bit more natural in -Rust. Let’s dig in to incrementally implementing a blog post workflow using the +Rust. Let’s dig in to incrementally implement a blog post workflow using the state pattern. The final functionality will look like this: @@ -64,11 +64,11 @@ be returned when `content` is called. Notice that the only type we’re interacting with from the crate is the `Post` type. This type will use the state pattern and will hold a value that will be one of three state objects representing the various states a post can be -in—draft, waiting for review, or published. Changing from one state to another -will be managed internally within the `Post` type. The states change in -response to the methods called by our library’s users on the `Post` instance, -but they don’t have to manage the state changes directly. Also, users can’t -make a mistake with the states, like publishing a post before it’s reviewed. +in—draft, review, or published. Changing from one state to another will be +managed internally within the `Post` type. The states change in response to the +methods called by our library’s users on the `Post` instance, but they don’t +have to manage the state changes directly. Also, users can’t make a mistake with +the states, such as publishing a post before it’s reviewed. ### Defining `Post` and Creating a New Instance in the Draft State @@ -98,11 +98,11 @@ we’ll start by defining just the `Draft` state because that is the state we want a post to start in. When we create a new `Post`, we set its `state` field to a `Some` value that -holds a `Box`. This `Box` points to a new instance of the `Draft` struct. -This ensures whenever we create a new instance of `Post`, it will start out as -a draft. Because the `state` field of `Post` is private, there is no way to -create a `Post` in any other state! In the `Post::new` function, we set the -`content` field to a new, empty `String`. +holds a `Box`. This `Box` points to a new instance of the `Draft` struct. This +ensures that whenever we create a new instance of `Post`, it will start out as a +draft. Because the `state` field of `Post` is private, there is no way to create +a `Post` in any other state! In the `Post::new` function, we set the `content` +field to a new, empty `String`. ### Storing the Text of the Post Content @@ -112,7 +112,7 @@ blog post. We implement this as a method, rather than exposing the `content` field as `pub`, so that later we can implement a method that will control how the `content` field’s data is read. The `add_text` method is pretty straightforward, so let’s add the implementation in Listing 18-13 to the `impl -Post` block: +Post` block. <Listing number="18-13" file-name="src/lib.rs" caption="Implementing the `add_text` method to add text to a post’s `content`"> @@ -122,7 +122,7 @@ Post` block: </Listing> -The `add_text` method takes a mutable reference to `self`, because we’re +The `add_text` method takes a mutable reference to `self` because we’re changing the `Post` instance that we’re calling `add_text` on. We then call `push_str` on the `String` in `content` and pass the `text` argument to add to the saved `content`. This behavior doesn’t depend on the state the post is in, @@ -139,7 +139,7 @@ implement the `content` method with the simplest thing that will fulfill this requirement: always returning an empty string slice. We’ll change this later once we implement the ability to change a post’s state so it can be published. So far, posts can only be in the draft state, so the post content should always -be empty. Listing 18-14 shows this placeholder implementation: +be empty. Listing 18-14 shows this placeholder implementation. <Listing number="18-14" file-name="src/lib.rs" caption="Adding a placeholder implementation for the `content` method on `Post` that always returns an empty string slice"> @@ -152,10 +152,14 @@ be empty. Listing 18-14 shows this placeholder implementation: With this added `content` method, everything in Listing 18-11 up to line 7 works as intended. -### Requesting a Review of the Post Changes Its State +<!-- Old link, do not remove --> + +<a id="requesting-a-review-of-the-post-changes-its-state"></a> + +### Requesting a Review Changes the Post’s State Next, we need to add functionality to request a review of a post, which should -change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code: +change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code. <Listing number="18-15" file-name="src/lib.rs" caption="Implementing `request_review` methods on `Post` and the `State` trait"> @@ -181,7 +185,7 @@ ownership of `Box<Self>`, invalidating the old state so the state value of the To consume the old state, the `request_review` method needs to take ownership of the state value. This is where the `Option` in the `state` field of `Post` comes in: we call the `take` method to take the `Some` value out of the `state` -field and leave a `None` in its place, because Rust doesn’t let us have +field and leave a `None` in its place because Rust doesn’t let us have unpopulated fields in structs. This lets us move the `state` value out of `Post` rather than borrowing it. Then we’ll set the post’s `state` value to the result of this operation. @@ -194,7 +198,7 @@ we’ve transformed it into a new state. The `request_review` method on `Draft` returns a new, boxed instance of a new `PendingReview` struct, which represents the state when a post is waiting for a review. The `PendingReview` struct also implements the `request_review` method -but doesn’t do any transformations. Rather, it returns itself, because when we +but doesn’t do any transformations. Rather, it returns itself because when we request a review on a post already in the `PendingReview` state, it should stay in the `PendingReview` state. @@ -249,7 +253,7 @@ as shown in Listing 18-17: </Listing> -Because the goal is to keep all these rules inside the structs that implement +Because the goal is to keep all of these rules inside the structs that implement `State`, we call a `content` method on the value in `state` and pass the post instance (that is, `self`) as an argument. Then we return the value that’s returned from using the `content` method on the `state` value. @@ -263,10 +267,10 @@ we can’t move `state` out of the borrowed `&self` of the function parameter. We then call the `unwrap` method, which we know will never panic, because we know the methods on `Post` ensure that `state` will always contain a `Some` value when those methods are done. This is one of the cases we talked about in -the [“Cases In Which You Have More Information Than the -Compiler”][more-info-than-rustc]<!-- ignore --> section of Chapter 9 when we -know that a `None` value is never possible, even though the compiler isn’t able -to understand that. +[“Cases in Which You Have More Information Than the +Compiler”][more-info-than-rustc]<!-- ignore --> in Chapter 9 when we know that a +`None` value is never possible, even though the compiler isn’t able to +understand that. At this point, when we call `content` on the `&Box<dyn State>`, deref coercion will take effect on the `&` and the `Box` so the `content` method will @@ -297,13 +301,13 @@ And we’re done—all of Listing 18-11 now works! We’ve implemented the state pattern with the rules of the blog post workflow. The logic related to the rules lives in the state objects rather than being scattered throughout `Post`. -> #### Why Not An Enum? +> ### Why Not An Enum? > > You may have been wondering why we didn’t use an `enum` with the different -> possible post states as variants. That’s certainly a possible solution, try +> possible post states as variants. That’s certainly a possible solution; try > it and compare the end results to see which you prefer! One disadvantage of -> using an enum is every place that checks the value of the enum will need a -> `match` expression or similar to handle every possible variant. This could +> using an enum is that every place that checks the value of the enum will need +> a `match` expression or similar to handle every possible variant. This could > get more repetitive than this trait object solution. ### Trade-offs of the State Pattern @@ -349,21 +353,22 @@ another design pattern. Another downside is that we’ve duplicated some logic. To eliminate some of the duplication, we might try to make default implementations for the `request_review` and `approve` methods on the `State` trait that return `self`; -however, this would not be dyn compatible, because the trait doesn’t know what -the concrete `self` will be exactly. We want to be able to use `State` as a -trait object, so we need its methods to be dyn compatible. +however, this wouldn’t work: when using `State` as a trait object, the trait +doesn’t know what the concrete `self` will be exactly, so the return type isn’t +known at compile time. (This is one of the dyn compatibility rules mentioned +earlier.) Other duplication includes the similar implementations of the `request_review` and `approve` methods on `Post`. Both methods delegate to the implementation of the same method on the value in the `state` field of `Option` and set the new value of the `state` field to the result. If we had a lot of methods on `Post` that followed this pattern, we might consider defining a macro to eliminate the -repetition (see the [“Macros”][macros]<!-- ignore --> section in Chapter 20). +repetition (see [“Macros”][macros]<!-- ignore --> in Chapter 20). By implementing the state pattern exactly as it’s defined for object-oriented languages, we’re not taking as full advantage of Rust’s strengths as we could. Let’s look at some changes we can make to the `blog` crate that can make -invalid states and transitions into compile time errors. +invalid states and transitions into compile-time errors. #### Encoding States and Behavior as Types @@ -389,9 +394,9 @@ and the ability to add text to the post’s content. But instead of having a draft posts don’t have the `content` method at all. That way, if we try to get a draft post’s content, we’ll get a compiler error telling us the method doesn’t exist. As a result, it will be impossible for us to accidentally -display draft post content in production, because that code won’t even compile. +display draft post content in production because that code won’t even compile. Listing 18-19 shows the definition of a `Post` struct and a `DraftPost` struct, -as well as methods on each: +as well as methods on each. <Listing number="18-19" file-name="src/lib.rs" caption="A `Post` with a `content` method and `DraftPost` without a `content` method"> @@ -424,9 +429,9 @@ So how do we get a published post? We want to enforce the rule that a draft post has to be reviewed and approved before it can be published. A post in the pending review state should still not display any content. Let’s implement these constraints by adding another struct, `PendingReviewPost`, defining the -`request_review` method on `DraftPost` to return a `PendingReviewPost`, and +`request_review` method on `DraftPost` to return a `PendingReviewPost` and defining an `approve` method on `PendingReviewPost` to return a `Post`, as -shown in Listing 18-20: +shown in Listing 18-20. <Listing number="18-20" file-name="src/lib.rs" caption="A `PendingReviewPost` that gets created by calling `request_review` on `DraftPost` and an `approve` method that turns a `PendingReviewPost` into a published `Post`"> @@ -454,7 +459,7 @@ called on, so we need to add more `let post =` shadowing assignments to save the returned instances. We also can’t have the assertions about the draft and pending review posts’ contents be empty strings, nor do we need them: we can’t compile code that tries to use the content of posts in those states any longer. -The updated code in `main` is shown in Listing 18-21: +The updated code in `main` is shown in Listing 18-21. <Listing number="18-21" file-name="src/main.rs" caption="Modifications to `main` to use the new implementation of the blog post workflow"> @@ -488,14 +493,14 @@ object-oriented languages don’t have. ## Summary -No matter whether or not you think Rust is an object-oriented language after +Regardless of whether you think Rust is an object-oriented language after reading this chapter, you now know that you can use trait objects to get some object-oriented features in Rust. Dynamic dispatch can give your code some flexibility in exchange for a bit of runtime performance. You can use this flexibility to implement object-oriented patterns that can help your code’s maintainability. Rust also has other features, like ownership, that -object-oriented languages don’t have. An object-oriented pattern won’t always -be the best way to take advantage of Rust’s strengths, but is an available +object-oriented languages don’t have. An object-oriented pattern won’t always be +the best way to take advantage of Rust’s strengths, but it is an available option. Next, we’ll look at patterns, which are another of Rust’s features that enable diff --git a/src/ch19-01-all-the-places-for-patterns.md b/src/ch19-01-all-the-places-for-patterns.md index 22d454d2bc..e95da8c5a9 100644 --- a/src/ch19-01-all-the-places-for-patterns.md +++ b/src/ch19-01-all-the-places-for-patterns.md @@ -11,13 +11,16 @@ Formally, `match` expressions are defined as the keyword `match`, a value to match on, and one or more match arms that consist of a pattern and an expression to run if the value matches that arm’s pattern, like this: -```text -match VALUE { - PATTERN => EXPRESSION, - PATTERN => EXPRESSION, - PATTERN => EXPRESSION, -} -``` +<!-- + Manually formatted rather than using Markdown intentionally: Markdown does not + support italicizing code in the body of a block like this! +--> + +<pre><code>match <em>VALUE</em> { + <em>PATTERN</em> => <em>EXPRESSION</em>, + <em>PATTERN</em> => <em>EXPRESSION</em>, + <em>PATTERN</em> => <em>EXPRESSION</em>, +}</code></pre> For example, here's the `match` expression from Listing 6-5 that matches on an `Option<i32>` value in the variable `x`: @@ -29,25 +32,24 @@ match x { } ``` -The patterns in this `match` expression are the `None` and `Some(i)` on the +The patterns in this `match` expression are the `None` and `Some(i)` to the left of each arrow. One requirement for `match` expressions is that they need to be _exhaustive_ in the sense that all possibilities for the value in the `match` expression must be accounted for. One way to ensure you’ve covered every possibility is to have -a catch-all pattern for the last arm: for example, a variable name matching any +a catchall pattern for the last arm: for example, a variable name matching any value can never fail and thus covers every remaining case. The particular pattern `_` will match anything, but it never binds to a variable, so it’s often used in the last match arm. The `_` pattern can be -useful when you want to ignore any value not specified, for example. We’ll -cover the `_` pattern in more detail in the [“Ignoring Values in a -Pattern”][ignoring-values-in-a-pattern]<!-- ignore --> section later in this -chapter. +useful when you want to ignore any value not specified, for example. We’ll cover +the `_` pattern in more detail in [“Ignoring Values in a +Pattern”][ignoring-values-in-a-pattern]<!-- ignore --> later in this chapter. ### Conditional `if let` Expressions -In Chapter 6 we discussed how to use `if let` expressions mainly as a shorter +In Chapter 6, we discussed how to use `if let` expressions mainly as a shorter way to write the equivalent of a `match` that only matches one case. Optionally, `if let` can have a corresponding `else` containing code to run if the pattern in the `if let` doesn’t match. @@ -82,7 +84,7 @@ This conditional structure lets us support complex requirements. With the hardcoded values we have here, this example will print `Using purple as the background color`. -You can see that `if let` can also introduce new variables which shadow existing +You can see that `if let` can also introduce new variables that shadow existing variables in the same way that `match` arms can: the line `if let Ok(age) = age` introduces a new `age` variable that contains the value inside the `Ok` variant, shadowing the existing `age` variable. This means we need to place the `if age > @@ -98,11 +100,10 @@ not alert us to the possible logic bug. ### `while let` Conditional Loops Similar in construction to `if let`, the `while let` conditional loop allows a -`while` loop to run for as long as a pattern continues to match. We first saw a -`while let` loop in Chapter 17, where we used it to keep looping as long as a -stream produced new values. Similarly, in Listing 19-2 we show a `while let` -loop that waits on messages sent between threads, but in this case checking a -`Result` instead of an `Option`. +`while` loop to run for as long as a pattern continues to match. In Listing 19-2 +we show a `while let` loop that waits on messages sent between threads, but in +this case checking a `Result` instead of an `Option`. + <Listing number="19-2" caption="Using a `while let` loop to print values for as long as `rx.recv()` returns `Ok`"> @@ -112,16 +113,18 @@ loop that waits on messages sent between threads, but in this case checking a </Listing> -This example prints 1, 2, and 3. When we saw `recv` back in Chapter 16, we -unwrapped the error directly, or interacted with it as an iterator using a `for` -loop. As Listing 19-2 shows, though, we can also use `while let`, because the -`recv` method returns `Ok` as long as the sender is producing messages, and then -produces an `Err` once the sender side disconnects. +This example prints `1`, `2`, and then `3`. The `recv` method takes the first +message out of the receiver side of the channel and returns an `Ok(value)`. When +we first saw `recv` back in Chapter 16, we unwrapped the error directly, or +interacted with it as an iterator using a `for` loop. As Listing 19-2 shows, +though, we can also use `while let`, because the `recv` method returns `Ok` each +time a message arrives, as long as the sender exists, and then produces an `Err` +once the sender side disconnects. ### `for` Loops In a `for` loop, the value that directly follows the keyword `for` is a -pattern. For example, in `for x in y` the `x` is the pattern. Listing 19-3 +pattern. For example, in `for x in y`, the `x` is the pattern. Listing 19-3 demonstrates how to use a pattern in a `for` loop to destructure, or break apart, a tuple as part of the `for` loop. @@ -160,18 +163,23 @@ Every time you've used a `let` statement like this you've been using patterns, although you might not have realized it! More formally, a `let` statement looks like this: -```text -let PATTERN = EXPRESSION; -``` +<!-- + Manually formatted rather than using Markdown intentionally: Markdown does not + support italicizing code in the body of a block like this! +--> + +<pre> +<code>let <em>PATTERN</em> = <em>EXPRESSION</em>;</code> +</pre> -In statements like `let x = 5;` with a variable name in the `PATTERN` slot, the -variable name is just a particularly simple form of a pattern. Rust compares -the expression against the pattern and assigns any names it finds. So in the -`let x = 5;` example, `x` is a pattern that means “bind what matches here to -the variable `x`.” Because the name `x` is the whole pattern, this pattern +In statements like `let x = 5;` with a variable name in the _`PATTERN`_ slot, +the variable name is just a particularly simple form of a pattern. Rust compares +the expression against the pattern and assigns any names it finds. So, in the +`let x = 5;` example, `x` is a pattern that means “bind what matches here to the +variable `x`.” Because the name `x` is the whole pattern, this pattern effectively means “bind everything to the variable `x`, whatever the value is.” -To see the pattern matching aspect of `let` more clearly, consider Listing +To see the pattern-matching aspect of `let` more clearly, consider Listing 19-4, which uses a pattern with `let` to destructure a tuple. <Listing number="19-4" caption="Using a pattern to destructure a tuple and create three variables at once"> @@ -182,10 +190,11 @@ To see the pattern matching aspect of `let` more clearly, consider Listing </Listing> -Here, we match a tuple against a pattern. Rust compares the value `(1, 2, 3)` -to the pattern `(x, y, z)` and sees that the value matches the pattern, so Rust -binds `1` to `x`, `2` to `y`, and `3` to `z`. You can think of this tuple -pattern as nesting three individual variable patterns inside it. +Here, we match a tuple against a pattern. Rust compares the value `(1, 2, 3)` to +the pattern `(x, y, z)` and sees that the value matches the pattern, in that the +number of elements is the same in both, so Rust binds `1` to `x`, `2` to `y`, +and `3` to `z`. You can think of this tuple pattern as nesting three individual +variable patterns inside it. If the number of elements in the pattern doesn’t match the number of elements in the tuple, the overall type won’t match and we’ll get a compiler error. For @@ -243,12 +252,12 @@ This code prints `Current location: (3, 5)`. The values `&(3, 5)` match the pattern `&(x, y)`, so `x` is the value `3` and `y` is the value `5`. We can also use patterns in closure parameter lists in the same way as in -function parameter lists, because closures are similar to functions, as -discussed in Chapter 13. +function parameter lists because closures are similar to functions, as discussed +in Chapter 13. -At this point, you’ve seen several ways of using patterns, but patterns don’t -work the same in every place we can use them. In some places, the patterns must -be irrefutable; in other circumstances, they can be refutable. We’ll discuss -these two concepts next. +At this point, you’ve seen several ways to use patterns, but patterns don’t work +the same in every place we can use them. In some places, the patterns must be +irrefutable; in other circumstances, they can be refutable. We’ll discuss these +two concepts next. [ignoring-values-in-a-pattern]: ch19-03-pattern-syntax.html#ignoring-values-in-a-pattern diff --git a/src/ch19-02-refutability.md b/src/ch19-02-refutability.md index b1ed3ae1ec..864d8e61dd 100644 --- a/src/ch19-02-refutability.md +++ b/src/ch19-02-refutability.md @@ -9,10 +9,10 @@ a_value` because if the value in the `a_value` variable is `None` rather than `Some`, the `Some(x)` pattern will not match. Function parameters, `let` statements, and `for` loops can only accept -irrefutable patterns, because the program cannot do anything meaningful when +irrefutable patterns because the program cannot do anything meaningful when values don’t match. The `if let` and `while let` expressions and the -`let`-`else` statement accept refutable and irrefutable patterns, but the -compiler warns against irrefutable patterns because by definition they’re +`let...else` statement accept refutable and irrefutable patterns, but the +compiler warns against irrefutable patterns because, by definition, they’re intended to handle possible failure: the functionality of a conditional is in its ability to perform differently depending on success or failure. @@ -24,7 +24,7 @@ using the pattern with, depending on the intended behavior of the code. Let’s look at an example of what happens when we try to use a refutable pattern where Rust requires an irrefutable pattern and vice versa. Listing 19-8 shows a -`let` statement, but for the pattern we’ve specified `Some(x)`, a refutable +`let` statement, but for the pattern, we’ve specified `Some(x)`, a refutable pattern. As you might expect, this code will not compile. <Listing number="19-8" caption="Attempting to use a refutable pattern with `let`"> @@ -35,7 +35,7 @@ pattern. As you might expect, this code will not compile. </Listing> -If `some_option_value` was a `None` value, it would fail to match the pattern +If `some_option_value` were a `None` value, it would fail to match the pattern `Some(x)`, meaning the pattern is refutable. However, the `let` statement can only accept an irrefutable pattern because there is nothing valid the code can do with a `None` value. At compile time, Rust will complain that we’ve tried to @@ -54,7 +54,7 @@ can use `if let`. Then if the pattern doesn’t match, the code will just skip the code in the curly brackets, giving it a way to continue validly. Listing 19-9 shows how to fix the code in Listing 19-8. -<Listing number="19-9" caption="Using `if let` and a block with refutable patterns instead of `let`"> +<Listing number="19-9" caption="Using `let...else` and a block with refutable patterns instead of `let`"> ```rust {{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-09/src/main.rs:here}} diff --git a/src/ch19-03-pattern-syntax.md b/src/ch19-03-pattern-syntax.md index ffb40dfcf8..619a0b1097 100644 --- a/src/ch19-03-pattern-syntax.md +++ b/src/ch19-03-pattern-syntax.md @@ -1,7 +1,7 @@ ## Pattern Syntax -In this section, we gather all the syntax valid in patterns and discuss why and -when you might want to use each one. +In this section, we gather all the syntax that is valid in patterns and discuss +why and when you might want to use each one. ### Matching Literals @@ -19,9 +19,9 @@ value. ### Matching Named Variables Named variables are irrefutable patterns that match any value, and we’ve used -them many times in the book. However, there is a complication when you use named -variables in `match`, `if let`, or `while let` expressions. Because each of -these kinds of expression starts a new scope, variables declared as part of a +them many times in this book. However, there is a complication when you use +named variables in `match`, `if let`, or `while let` expressions. Because each +of these kinds of expression starts a new scope, variables declared as part of a pattern inside the expression will shadow those with the same name outside, as is the case with all variables. In Listing 19-11, we declare a variable named `x` with the value `Some(5)` and a variable `y` with the value `10`. We then @@ -44,7 +44,7 @@ continues. The pattern in the second match arm introduces a new variable named `y` that will match any value inside a `Some` value. Because we’re in a new scope inside the `match` expression, this is a new `y` variable, not the `y` we declared at -the beginning with the value 10. This new `y` binding will match any value +the beginning with the value `10`. This new `y` binding will match any value inside a `Some`, which is what we have in `x`. Therefore, this new `y` binds to the inner value of the `Some` in `x`. That value is `5`, so the expression for that arm executes and prints `Matched, y = 5`. @@ -60,10 +60,10 @@ When the `match` expression is done, its scope ends, and so does the scope of the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. To create a `match` expression that compares the values of the outer `x` and -`y`, rather than introducing a new variable which shadows the existing `y` +`y`, rather than introducing a new variable that shadows the existing `y` variable, we would need to use a match guard conditional instead. We’ll talk -about match guards later in the [“Extra Conditionals with Match -Guards”](#extra-conditionals-with-match-guards)<!-- ignore --> section. +about match guards later in [“Extra Conditionals with Match +Guards”](#extra-conditionals-with-match-guards)<!-- ignore -->. ### Multiple Patterns @@ -88,10 +88,10 @@ range, that arm will execute: {{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-03-ranges/src/main.rs:here}} ``` -If `x` is 1, 2, 3, 4, or 5, the first arm will match. This syntax is more -convenient for multiple match values than using the `|` operator to express the -same idea; if we were to use `|` we would have to specify `1 | 2 | 3 | 4 | 5`. -Specifying a range is much shorter, especially if we want to match, say, any +If `x` is `1`, `2`, `3`, `4`, or `5`, the first arm will match. This syntax is +more convenient for multiple match values than using the `|` operator to express +the same idea; if we were to use `|` we would have to specify `1 | 2 | 3 | 4 | +5`. Specifying a range is much shorter, especially if we want to match, say, any number between 1 and 1,000! The compiler checks that the range isn’t empty at compile time, and because the @@ -156,7 +156,7 @@ destructure the other fields. In Listing 19-14, we have a `match` expression that separates `Point` values into three cases: points that lie directly on the `x` axis (which is true when -`y = 0`), on the `y` axis (`x = 0`), or neither. +`y = 0`), on the `y` axis (`x = 0`), or on neither axis. <Listing number="19-14" file-name="src/main.rs" caption="Destructuring and matching literal values in one pattern"> @@ -176,7 +176,7 @@ value of the `y` field. The third arm doesn’t specify any literals, so it matches any other `Point` and creates variables for both the `x` and `y` fields. In this example, the value `p` matches the second arm by virtue of `x` -containing a 0, so this code will print `On the y axis at 7`. +containing a `0`, so this code will print `On the y axis at 7`. Remember that a `match` expression stops checking arms once it has found the first matching pattern, so even though `Point { x: 0, y: 0}` is on the `x` axis @@ -184,11 +184,11 @@ and the `y` axis, this code would only print `On the x axis at 0`. #### Destructuring Enums -We've destructured enums in this book (for example, Listing 6-5 in Chapter 6), -but haven’t yet explicitly discussed that the pattern to destructure an enum -corresponds to the way the data stored within the enum is defined. As an -example, in Listing 19-15 we use the `Message` enum from Listing 6-2 and write -a `match` with patterns that will destructure each inner value. +We've destructured enums in this book (for example, Listing 6-5), but we haven’t +yet explicitly discussed that the pattern to destructure an enum corresponds to +the way the data stored within the enum is defined. As an example, in Listing +19-15 we use the `Message` enum from Listing 6-2 and write a `match` with +patterns that will destructure each inner value. <Listing number="19-15" file-name="src/main.rs" caption="Destructuring enum variants that hold different kinds of values"> @@ -198,7 +198,7 @@ a `match` with patterns that will destructure each inner value. </Listing> -This code will print `Change the color to red 0, green 160, and blue 255`. Try +This code will print `Change color to red 0, green 160, and blue 255`. Try changing the value of `msg` to see the code from the other arms run. For enum variants without any data, like `Message::Quit`, we can’t destructure @@ -258,14 +258,18 @@ as the value from each field in a struct, separately from each other. ### Ignoring Values in a Pattern You’ve seen that it’s sometimes useful to ignore values in a pattern, such as -in the last arm of a `match`, to get a catch-all that doesn’t actually do +in the last arm of a `match`, to get a catchall that doesn’t actually do anything but does account for all remaining possible values. There are a few ways to ignore entire values or parts of values in a pattern: using the `_` pattern (which you’ve seen), using the `_` pattern within another pattern, using a name that starts with an underscore, or using `..` to ignore remaining parts of a value. Let’s explore how and why to use each of these patterns. -#### Ignoring an Entire Value with `_` +<!-- Old link, do not remove --> + +<a id="ignoring-an-entire-value-with-_"></a> + +#### An Entire Value with `_` We’ve used the underscore as a wildcard pattern that will match any value but not bind to the value. This is especially useful as the last arm in a `match` @@ -291,7 +295,9 @@ function body in your implementation doesn’t need one of the parameters. You then avoid getting a compiler warning about unused function parameters, as you would if you used a name instead. -#### Ignoring Parts of a Value with a Nested `_` +<a id="ignoring-parts-of-a-value-with-a-nested-_"></a> + +#### Parts of a Value with a Nested `_` We can also use `_` inside another pattern to ignore just part of a value, for example, when we want to test for only part of a value but have no use for the @@ -315,8 +321,8 @@ when `setting_value` and `new_setting_value` are the `Some` variant. In that case, we print the reason for not changing `setting_value`, and it doesn’t get changed. -In all other cases (if either `setting_value` or `new_setting_value` are -`None`) expressed by the `_` pattern in the second arm, we want to allow +In all other cases (if either `setting_value` or `new_setting_value` is `None`) +expressed by the `_` pattern in the second arm, we want to allow `new_setting_value` to become `setting_value`. We can also use underscores in multiple places within one pattern to ignore @@ -331,10 +337,14 @@ fourth values in a tuple of five items. </Listing> -This code will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be -ignored. +This code will print `Some numbers: 2, 8, 32`, and the values `4` and `16` will +be ignored. -#### Ignoring an Unused Variable by Starting Its Name with `_` +<!-- Old link, do not remove --> + +<a id="ignoring-an-unused-variable-by-starting-its-name-with-_"></a> + +#### An Unused Variable by Starting Its Name with `_` If you create a variable but don’t use it anywhere, Rust will usually issue a warning because an unused variable could be a bug. However, sometimes it’s @@ -352,7 +362,7 @@ we compile this code, we should only get a warning about one of them. </Listing> -Here we get a warning about not using the variable `y`, but we don’t get a +Here, we get a warning about not using the variable `y`, but we don’t get a warning about not using `_x`. Note that there is a subtle difference between using only `_` and using a name @@ -383,7 +393,9 @@ because `s` doesn’t get moved into `_`. This code works just fine because we never bind `s` to anything; it isn’t moved. -#### Ignoring Remaining Parts of a Value with `..` +<a id="ignoring-remaining-parts-of-a-value-with-"></a> + +#### Remaining Parts of a Value with `..` With values that have many parts, we can use the `..` syntax to use specific parts and ignore the rest, avoiding the need to list underscores for each @@ -451,13 +463,13 @@ compiler error because using `..` in two places like this is ambiguous. A _match guard_ is an additional `if` condition, specified after the pattern in a `match` arm, that must also match for that arm to be chosen. Match guards are -useful for expressing more complex ideas than a pattern alone allows. They are -only available in `match` expressions, not in `if let` or `while let` -expressions. +useful for expressing more complex ideas than a pattern alone allows. Note, +however, that they are only available in `match` expressions, not in `if let` or +`while let` expressions. The condition can use variables created in the pattern. Listing 19-26 shows a `match` where the first arm has the pattern `Some(x)` and also has a match -guard of `if x % 2 == 0` (which will be true if the number is even). +guard of `if x % 2 == 0` (which will be `true` if the number is even). <Listing number="19-26" caption="Adding a match guard to a pattern"> @@ -468,12 +480,12 @@ guard of `if x % 2 == 0` (which will be true if the number is even). </Listing> This example will print `The number 4 is even`. When `num` is compared to the -pattern in the first arm, it matches, because `Some(4)` matches `Some(x)`. Then +pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then the match guard checks whether the remainder of dividing `x` by 2 is equal to 0, and because it is, the first arm is selected. If `num` had been `Some(5)` instead, the match guard in the first arm would -have been false because the remainder of 5 divided by 2 is 1, which is not +have been `false` because the remainder of 5 divided by 2 is 1, which is not equal to 0. Rust would then go to the second arm, which would match because the second arm doesn’t have a match guard and therefore matches any `Some` variant. @@ -527,9 +539,9 @@ applies to `6`. The match condition states that the arm only matches if the value of `x` is equal to `4`, `5`, or `6` _and_ if `y` is `true`. When this code runs, the pattern of the first arm matches because `x` is `4`, but the match guard `if y` -is false, so the first arm is not chosen. The code moves on to the second arm, +is `false`, so the first arm is not chosen. The code moves on to the second arm, which does match, and this program prints `no`. The reason is that the `if` -condition applies to the whole pattern `4 | 5 | 6`, not only to the last value +condition applies to the whole pattern `4 | 5 | 6`, not just to the last value `6`. In other words, the precedence of a match guard in relation to a pattern behaves like this: @@ -551,7 +563,7 @@ were applied only to the final value in the list of values specified using the ### `@` Bindings The _at_ operator `@` lets us create a variable that holds a value at the same -time as we’re testing that value for a pattern match. In Listing 19-29, we want +time we’re testing that value for a pattern match. In Listing 19-29, we want to test that a `Message::Hello` `id` field is within the range `3..=7`. We also want to bind the value to the variable `id_variable` so we can use it in the code associated with the arm. We could name this variable `id`, the same as the @@ -590,8 +602,8 @@ Rust’s patterns are very useful in distinguishing between different kinds of data. When used in `match` expressions, Rust ensures your patterns cover every possible value, or your program won’t compile. Patterns in `let` statements and function parameters make those constructs more useful, enabling the -destructuring of values into smaller parts and assigning those parts to -variables. We can create simple or complex patterns to suit our needs. +destructuring of values into smaller parts at the same time as assigning those +parts to variables. We can create simple or complex patterns to suit our needs. Next, for the penultimate chapter of the book, we’ll look at some advanced aspects of a variety of Rust’s features. diff --git a/src/ch20-01-unsafe-rust.md b/src/ch20-01-unsafe-rust.md index 51938b3cc0..f9de46a56a 100644 --- a/src/ch20-01-unsafe-rust.md +++ b/src/ch20-01-unsafe-rust.md @@ -37,7 +37,7 @@ include the ability to: - Access fields of a `union` It’s important to understand that `unsafe` doesn’t turn off the borrow checker -or disable any other of Rust’s safety checks: if you use a reference in unsafe +or disable any of Rust’s other safety checks: if you use a reference in unsafe code, it will still be checked. The `unsafe` keyword only gives you access to these five features that are then not checked by the compiler for memory safety. You’ll still get some degree of safety inside of an unsafe block. @@ -47,13 +47,13 @@ dangerous or that it will definitely have memory safety problems: the intent is that as the programmer, you’ll ensure the code inside an `unsafe` block will access memory in a valid way. -People are fallible, and mistakes will happen, but by requiring these five -unsafe operations to be inside blocks annotated with `unsafe` you’ll know that +People are fallible and mistakes will happen, but by requiring these five +unsafe operations to be inside blocks annotated with `unsafe`, you’ll know that any errors related to memory safety must be within an `unsafe` block. Keep `unsafe` blocks small; you’ll be thankful later when you investigate memory bugs. -To isolate unsafe code as much as possible, it’s best to enclose unsafe code +To isolate unsafe code as much as possible, it’s best to enclose such code within a safe abstraction and provide a safe API, which we’ll discuss later in the chapter when we examine unsafe functions and methods. Parts of the standard library are implemented as safe abstractions over unsafe code that has been @@ -67,7 +67,7 @@ some abstractions that provide a safe interface to unsafe code. ### Dereferencing a Raw Pointer -In Chapter 4, in [“Dangling References”][dangling-references]<!-- ignore -->, we +In [“Dangling References”][dangling-references]<!-- ignore --> in Chapter 4, we mentioned that the compiler ensures references are always valid. Unsafe Rust has two new types called _raw pointers_ that are similar to references. As with references, raw pointers can be immutable or mutable and are written as `*const @@ -108,13 +108,13 @@ variable, we know these particular raw pointers are valid, but we can’t make that assumption about just any raw pointer. To demonstrate this, next we’ll create a raw pointer whose validity we can’t be -so certain of, using `as` to cast a value instead of using the raw reference +so certain of, using `as` to cast a value instead of using the raw borrow operators. Listing 20-2 shows how to create a raw pointer to an arbitrary location in memory. Trying to use arbitrary memory is undefined: there might be data at that address or there might not, the compiler might optimize the code so -there is no memory access, or the program might error with a segmentation fault. -Usually, there is no good reason to write code like this, especially in cases -where you can use a raw borrow operator instead, but it is possible. +there is no memory access, or the program might terminate with a segmentation +fault. Usually, there is no good reason to write code like this, especially in +cases where you can use a raw borrow operator instead, but it is possible. <Listing number="20-2" caption="Creating a raw pointer to an arbitrary memory address"> @@ -165,7 +165,7 @@ definition. The `unsafe` keyword in this context indicates the function has requirements we need to uphold when we call this function, because Rust can’t guarantee we’ve met these requirements. By calling an unsafe function within an `unsafe` block, we’re saying that we’ve read this function’s documentation and -take responsibility for upholding the function’s contracts. +we take responsibility for upholding the function’s contracts. Here is an unsafe function named `dangerous` that doesn’t do anything in its body: @@ -186,7 +186,7 @@ documentation, we understand how to use it properly, and we’ve verified that we’re fulfilling the contract of the function. To perform unsafe operations in the body of an unsafe function, you still need -to use an `unsafe` block just as within a regular function, and the compiler +to use an `unsafe` block, just as within a regular function, and the compiler will warn you if you forget. This helps to keep `unsafe` blocks as small as possible, as unsafe operations may not be needed across the whole function body. @@ -264,15 +264,15 @@ stored in the variable `ptr`. We keep the assertion that the `mid` index is within the slice. Then we get to the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer -and a length, and it creates a slice. We use this function to create a slice -that starts from `ptr` and is `mid` items long. Then we call the `add` -method on `ptr` with `mid` as an argument to get a raw pointer that starts at -`mid`, and we create a slice using that pointer and the remaining number of -items after `mid` as the length. +and a length, and it creates a slice. We use it to create a slice that starts +from `ptr` and is `mid` items long. Then we call the `add` method on `ptr` with +`mid` as an argument to get a raw pointer that starts at `mid`, and we create a +slice using that pointer and the remaining number of items after `mid` as the +length. The function `slice::from_raw_parts_mut` is unsafe because it takes a raw pointer and must trust that this pointer is valid. The `add` method on raw -pointers is also unsafe, because it must trust that the offset location is also +pointers is also unsafe because it must trust that the offset location is also a valid pointer. Therefore, we had to put an `unsafe` block around our calls to `slice::from_raw_parts_mut` and `add` so we could call them. By looking at the code and by adding the assertion that `mid` must be less than or equal to @@ -280,7 +280,7 @@ the code and by adding the assertion that `mid` must be less than or equal to will be valid pointers to data within the slice. This is an acceptable and appropriate use of `unsafe`. -Note that we don’t need to mark the resulting `split_at_mut` function as +Note that we don’t need to mark the resultant `split_at_mut` function as `unsafe`, and we can call this function from safe Rust. We’ve created a safe abstraction to the unsafe code with an implementation of the function that uses `unsafe` code in a safe way, because it creates only valid pointers from the @@ -312,10 +312,10 @@ programming language to call those functions. Listing 20-8 demonstrates how to set up an integration with the `abs` function from the C standard library. Functions declared within `extern` blocks are -usually unsafe to call from Rust code, so they must also be marked `unsafe`. The -reason is that other languages don’t enforce Rust’s rules and guarantees, and -Rust can’t check them, so responsibility falls on the programmer to ensure -safety. +generally unsafe to call from Rust code, so `extern` blocks must also be marked +`unsafe`. The reason is that other languages don’t enforce Rust’s rules and +guarantees, and Rust can’t check them, so responsibility falls on the programmer +to ensure safety. <Listing number="20-8" file-name="src/main.rs" caption="Declaring and calling an `extern` function defined in another language"> @@ -331,11 +331,13 @@ which _application binary interface (ABI)_ the external function uses: the ABI defines how to call the function at the assembly level. The `"C"` ABI is the most common and follows the C programming language’s ABI. -This particular function does not have any memory safety considerations, though. -In fact, we know that any call to `abs` will always be safe for any `i32`, so we -can use the `safe` keyword to say that this specific function is safe to call -even though it is in an `unsafe extern` block. Once we make that change, calling -it no longer requires an `unsafe` block, as shown in Listing 20-9. +Every item declared within an `unsafe extern` block is implicitly `unsafe`. +However, some FFI functions *are* safe to call. For example, the `abs` function +from C’s standard library does not have any memory safety considerations and we +know it can be called with any `i32`. In cases like this, we can use the `safe` +keyword to say that this specific function is safe to call even though it is in +an `unsafe extern` block. Once we make that change, calling it no longer +requires an `unsafe` block, as shown in Listing 20-9. <Listing number="20-9" file-name="src/main.rs" caption="Explicitly marking a function as `safe` within an `unsafe extern` block and calling it safely"> @@ -354,16 +356,16 @@ responsibility to make sure that promise is kept! > We can also use `extern` to create an interface that allows other languages to > call Rust functions. Instead of creating a whole `extern` block, we add the > `extern` keyword and specify the ABI to use just before the `fn` keyword for -> the relevant function. We also need to add a `#[unsafe(no_mangle)]` annotation -> to tell the Rust compiler not to mangle the name of this function. _Mangling_ -> is when a compiler changes the name we’ve given a function to a different name -> that contains more information for other parts of the compilation process to -> consume but is less human readable. Every programming language compiler -> mangles names slightly differently, so for a Rust function to be nameable by -> other languages, we must disable the Rust compiler’s name mangling. This is -> unsafe because there might be name collisions across libraries without the -> built-in mangling, so it is our responsibility to make sure the name we have -> exported is safe to export without mangling. +> the relevant function. We also need to add an `#[unsafe(no_mangle)]` +> annotation to tell the Rust compiler not to mangle the name of this function. +> _Mangling_ is when a compiler changes the name we’ve given a function to a +> different name that contains more information for other parts of the +> compilation process to consume but is less human readable. Every programming +> language compiler mangles names slightly differently, so for a Rust function +> to be nameable by other languages, we must disable the Rust compiler’s name +> mangling. This is unsafe because there might be name collisions across +> libraries without the built-in mangling, so it is our responsibility to make +> sure the name we choose is safe to export without mangling. > > In the following example, we make the `call_from_c` function accessible from > C code, after it’s compiled to a shared library and linked from C: @@ -375,11 +377,12 @@ responsibility to make sure that promise is kept! > } > ``` > -> This usage of `extern` does not require `unsafe`. +> This usage of `extern` requires `unsafe` only in the attribute, not on the +> `extern` block. ### Accessing or Modifying a Mutable Static Variable -In this book, we’ve not yet talked about _global variables_, which Rust does +In this book, we’ve not yet talked about global variables, which Rust does support but can be problematic with Rust’s ownership rules. If two threads are accessing the same mutable global variable, it can cause a data race. @@ -420,13 +423,12 @@ static variable named `COUNTER`. </Listing> As with regular variables, we specify mutability using the `mut` keyword. Any -code that reads or writes from `COUNTER` must be within an `unsafe` block. The -code in Listing 20-11 compiles and prints `COUNTER: 3` as we would expect -because it’s single threaded. Having multiple threads access `COUNTER` would -likely result in data races, so it is undefined behavior. Therefore, we need to -mark the entire function as `unsafe`, and document the safety limitation, so -anyone calling the function knows what they are and are not allowed to do -safely. +code that reads or writes from `COUNTER` must be within an `unsafe` block. This +code compiles and prints `COUNTER: 3` as we would expect because it’s single +threaded. Having multiple threads access `COUNTER` would likely result in data +races, so it is undefined behavior. Therefore, we need to mark the entire +function as `unsafe`, and document the safety limitation, so anyone calling the +function knows what they are and are not allowed to do safely. Whenever we write an unsafe function, it is idiomatic to write a comment starting with `SAFETY` and explaining what the caller needs to do to call the @@ -445,7 +447,7 @@ With mutable data that is globally accessible, it’s difficult to ensure there are no data races, which is why Rust considers mutable static variables to be unsafe. Where possible, it’s preferable to use the concurrency techniques and thread-safe smart pointers we discussed in Chapter 16 so the compiler checks -that data accessed from different threads is done safely. +that data access from different threads is done safely. ### Implementing an Unsafe Trait @@ -458,7 +460,7 @@ Listing 20-12. <Listing number="20-12" caption="Defining and implementing an unsafe trait"> ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-12/src/main.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-12/src/main.rs:here}} ``` </Listing> @@ -470,29 +472,29 @@ As an example, recall the `Sync` and `Send` marker traits we discussed in [“Extensible Concurrency with the `Sync` and `Send` Traits”][extensible-concurrency-with-the-sync-and-send-traits]<!-- ignore --> in Chapter 16: the compiler implements these traits automatically if our types are -composed entirely of `Send` and `Sync` types. If we implement a type that -contains a type that is not `Send` or `Sync`, such as raw pointers, and we want -to mark that type as `Send` or `Sync`, we must use `unsafe`. Rust can’t verify -that our type upholds the guarantees that it can be safely sent across threads -or accessed from multiple threads; therefore, we need to do those checks -manually and indicate as such with `unsafe`. +composed entirely of other types that implement `Send` and `Sync`. If we +implement a type that contains a type that does not implement `Send` or `Sync`, +such as raw pointers, and we want to mark that type as `Send` or `Sync`, we must +use `unsafe`. Rust can’t verify that our type upholds the guarantees that it can +be safely sent across threads or accessed from multiple threads; therefore, we +need to do those checks manually and indicate as such with `unsafe`. ### Accessing Fields of a Union -The final action that works only with `unsafe` is accessing fields of a -_union_. A `union` is similar to a `struct`, but only one declared field is -used in a particular instance at one time. Unions are primarily used to -interface with unions in C code. Accessing union fields is unsafe because Rust -can’t guarantee the type of the data currently being stored in the union -instance. You can learn more about unions in [the Rust Reference][reference]. +The final action that works only with `unsafe` is accessing fields of a union. A +`union` is similar to a `struct`, but only one declared field is used in a +particular instance at one time. Unions are primarily used to interface with +unions in C code. Accessing union fields is unsafe because Rust can’t guarantee +the type of the data currently being stored in the union instance. You can learn +more about unions in [the Rust Reference][reference]. -### Using Miri to check unsafe code +### Using Miri to Check Unsafe Code When writing unsafe code, you might want to check that what you have written actually is safe and correct. One of the best ways to do that is to use -[Miri][miri], an official Rust tool for detecting undefined behavior. Whereas -the borrow checker is a _static_ tool which works at compile time, Miri is a -_dynamic_ tool which works at runtime. It checks your code by running your +Miri, an official Rust tool for detecting undefined behavior. Whereas +the borrow checker is a _static_ tool that works at compile time, Miri is a +_dynamic_ tool that works at runtime. It checks your code by running your program, or its test suite, and detecting when you violate the rules it understands about how Rust should work. @@ -505,32 +507,37 @@ You can run Miri on a project by typing `cargo +nightly miri run` or `cargo +nightly miri test`. For an example of how helpful this can be, consider what happens when we run it -against Listing 20-11: +against Listing 20-11. ```console {{#include ../listings/ch20-advanced-features/listing-20-11/output.txt}} ``` -It helpfully and correctly notices that we have shared references to mutable -data, and warns about it. In this case, it does not tell us how to fix the -problem, but it means that we know there is a possible issue and can think about -how to make sure it is safe. In other cases, it can actually tell us that some -code is _sure_ to be wrong and make recommendations about how to fix it. - -Miri doesn’t catch _everything_ you might get wrong when writing unsafe code. -For one thing, since it is a dynamic check, it only catches problems with code -that actually gets run. That means you will need to use it in conjunction with -good testing techniques to increase your confidence about the unsafe code you -have written. For another thing, it does not cover every possible way your code -can be unsound. If Miri _does_ catch a problem, you know there’s a bug, but just -because Miri _doesn’t_ catch a bug doesn’t mean there isn’t a problem. Miri can -catch a lot, though. Try running it on the other examples of unsafe code in this -chapter and see what it says! +Miri correctly warns us that we have shared references to mutable data. Here, +Miri issues only a warning because this is not guaranteed to be undefined +behavior in this case, and it does not tell us how to fix the problem. but at +least we know there is a risk of undefined behavior and can think about how to +make the code safe. In some cases, Miri can also detect outright errors—code +patterns that are _sure_ to be wrong—and make recommendations about how to fix +those errors. + +Miri doesn’t catch everything you might get wrong when writing unsafe code. Miri +is a dynamic analysis tool, so it only catches problems with code that actually +gets run. That means you will need to use it in conjunction with good testing +techniques to increase your confidence about the unsafe code you have written. +Miri also does not cover every possible way your code can be unsound. + +Put another way: If Miri _does_ catch a problem, you know there’s a bug, but +just because Miri _doesn’t_ catch a bug doesn’t mean there isn’t a problem. It +can catch a lot, though. Try running it on the other examples of unsafe code in +this chapter and see what it says! + +You can learn more about Miri at [its GitHub repository][miri]. ### When to Use Unsafe Code -Using `unsafe` to take one of the five actions (superpowers) just discussed -isn’t wrong or even frowned upon. But it is trickier to get `unsafe` code +Using `unsafe` to use one of the five superpowers just discussed +isn’t wrong or even frowned upon, but it is trickier to get `unsafe` code correct because the compiler can’t help uphold memory safety. When you have a reason to use `unsafe` code, you can do so, and having the explicit `unsafe` annotation makes it easier to track down the source of problems when they occur. diff --git a/src/ch20-02-advanced-traits.md b/src/ch20-02-advanced-traits.md index b66d550dfc..b40171cb24 100644 --- a/src/ch20-02-advanced-traits.md +++ b/src/ch20-02-advanced-traits.md @@ -5,7 +5,11 @@ Behavior”][traits-defining-shared-behavior]<!-- ignore --> in Chapter 10, but didn’t discuss the more advanced details. Now that you know more about Rust, we can get into the nitty-gritty. -### Specifying Placeholder Types in Trait Definitions with Associated Types +<!-- Old link, do not remove --> + +<a id="specifying-placeholder-types-in-trait-definitions-with-associated-types"></a> + +### Associated Types _Associated types_ connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures. The @@ -73,16 +77,16 @@ the concrete types of the generic type parameters each time. When we use the indicate which implementation of `Iterator` we want to use. With associated types, we don’t need to annotate types because we can’t -implement a trait on a type multiple times. In Listing 20-13 with the -definition that uses associated types, we can only choose what the type of -`Item` will be once, because there can only be one `impl Iterator for Counter`. -We don’t have to specify that we want an iterator of `u32` values everywhere -that we call `next` on `Counter`. +implement a trait on a type multiple times. In Listing 20-13 with the definition +that uses associated types, we can choose what the type of `Item` will be only +once, because there can be only one `impl Iterator for Counter`. We don’t have +to specify that we want an iterator of `u32` values everywhere that we call +`next` on `Counter`. Associated types also become part of the trait’s contract: implementors of the trait must provide a type to stand in for the associated type placeholder. Associated types often have a name that describes how the type will be used, -and documenting the associated type in the API documentation is good practice. +and documenting the associated type in the API documentation is a good practice. ### Default Generic Type Parameters and Operator Overloading @@ -100,7 +104,7 @@ operators. But you can overload the operations and corresponding traits listed in `std::ops` by implementing the traits associated with the operator. For example, in Listing 20-15 we overload the `+` operator to add two `Point` instances together. We do this by implementing the `Add` trait on a `Point` -struct: +struct. <Listing number="20-15" file-name="src/main.rs" caption="Implementing the `Add` trait to overload the `+` operator for `Point` instances"> @@ -128,7 +132,7 @@ trait Add<Rhs=Self> { This code should look generally familiar: a trait with one method and an associated type. The new part is `Rhs=Self`: this syntax is called _default -type parameters_. The `Rhs` generic type parameter (short for “right hand +type parameters_. The `Rhs` generic type parameter (short for “right-hand side”) defines the type of the `rhs` parameter in the `add` method. If we don’t specify a concrete type for `Rhs` when we implement the `Add` trait, the type of `Rhs` will default to `Self`, which will be the type we’re implementing @@ -175,7 +179,11 @@ type parameter to an existing trait, you can give it a default to allow extension of the functionality of the trait without breaking the existing implementation code. -### Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name +<!-- Old link, do not remove --> + +<a id="fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name"></a> + +### Disambiguating Between Methods with the Same Name Nothing in Rust prevents a trait from having a method with the same name as another trait’s method, nor does Rust prevent you from implementing both traits @@ -188,7 +196,7 @@ want to use. Consider the code in Listing 20-17 where we’ve defined two traits both traits on a type `Human` that already has a method named `fly` implemented on it. Each `fly` method does something different. -<Listing number="20-17" file-name="src/main.rs" caption="Two traits are defined to have a ` method and are implemented on the `Human` type, and a `fly` method is implemented on `Human` directly"> +<Listing number="20-17" file-name="src/main.rs" caption="Two traits are defined to have a ` method and are implemented on the `Human` type, and a `fly` method is implemented on `Human` directly."> ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-17/src/main.rs:here}} @@ -326,10 +334,14 @@ in the program. You only need to use this more verbose syntax in cases where there are multiple implementations that use the same name and Rust needs help to identify which implementation you want to call. -### Using Supertraits to Require One Trait’s Functionality Within Another Trait +<!-- Old link, do not remove --> + +<a id="using-supertraits-to-require-one-traits-functionality-within-another-trait"></a> + +### Using Supertraits -Sometimes, you might write a trait definition that depends on another trait: -for a type to implement the first trait, you want to require that type to also +Sometimes you might write a trait definition that depends on another trait: for +a type to implement the first trait, you want to require that type to also implement the second trait. You would do this so that your trait definition can make use of the associated items of the second trait. The trait your trait definition is relying on is called a _supertrait_ of your trait. @@ -400,7 +412,7 @@ To fix this, we implement `Display` on `Point` and satisfy the constraint that </Listing> -Then implementing the `OutlinePrint` trait on `Point` will compile +Then, implementing the `OutlinePrint` trait on `Point` will compile successfully, and we can call `outline_print` on a `Point` instance to display it within an outline of asterisks. @@ -408,16 +420,16 @@ it within an outline of asterisks. In [“Implementing a Trait on a Type”][implementing-a-trait-on-a-type]<!-- ignore --> in Chapter 10, we mentioned the orphan rule that states we’re only allowed -to implement a trait on a type if either the trait or the type are local to our -crate. It’s possible to get around this restriction using the _newtype pattern_, -which involves creating a new type in a tuple struct. (We covered tuple structs -in [“Using Tuple Structs without Named Fields to Create Different -Types”][tuple-structs]<!-- ignore --> in Chapter 5.) The tuple struct will have -one field and be a thin wrapper around the type we want to implement a trait -for. Then the wrapper type is local to our crate, and we can implement the trait -on the wrapper. _Newtype_ is a term that originates from the Haskell programming -language. There is no runtime performance penalty for using this pattern, and -the wrapper type is elided at compile time. +to implement a trait on a type if either the trait or the type, or both, are +local to our crate. It’s possible to get around this restriction using the +_newtype pattern_, which involves creating a new type in a tuple struct. (We +covered tuple structs in [“Using Tuple Structs Without Named Fields to Create +Different Types”][tuple-structs]<!-- ignore --> in Chapter 5.) The tuple struct +will have one field and be a thin wrapper around the type for which we want to +implement a trait. Then the wrapper type is local to our crate, and we can +implement the trait on the wrapper. _Newtype_ is a term that originates from the +Haskell programming language. There is no runtime performance penalty for using +this pattern, and the wrapper type is elided at compile time. As an example, let’s say we want to implement `Display` on `Vec<T>`, which the orphan rule prevents us from doing directly because the `Display` trait and the @@ -442,12 +454,12 @@ doesn’t have the methods of the value it’s holding. We would have to impleme all the methods of `Vec<T>` directly on `Wrapper` such that the methods delegate to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec<T>`. If we wanted the new type to have every method the inner type has, implementing the -`Deref` trait (discussed in [“Treating Smart Pointers Like Regular References -with the `Deref` Trait”][smart-pointer-deref]<!-- ignore --> in Chapter 15) on -the `Wrapper` to return the inner type would be a solution. If we don’t want the -`Wrapper` type to have all the methods of the inner type—for example, to -restrict the `Wrapper` type’s behavior—we would have to implement just the -methods we do want manually. +`Deref` trait on the `Wrapper` to return the inner type would be a solution (we +discussed implementing the `Deref` trait in [“Treating Smart Pointers Like +Regular References with the `Deref` Trait”][smart-pointer-deref]<!-- ignore --> +in Chapter 15). If we didn’t want the `Wrapper` type to have all the methods of +the inner type—for example, to restrict the `Wrapper` type’s behavior—we would +have to implement just the methods we do want manually. This newtype pattern is also useful even when traits are not involved. Let’s switch focus and look at some advanced ways to interact with Rust’s type system. diff --git a/src/ch20-03-advanced-types.md b/src/ch20-03-advanced-types.md index 88d9d307e1..8a162f3173 100644 --- a/src/ch20-03-advanced-types.md +++ b/src/ch20-03-advanced-types.md @@ -15,8 +15,9 @@ discussed so far, including statically enforcing that values are never confused and indicating the units of a value. You saw an example of using newtypes to indicate units in Listing 20-16: recall that the `Millimeters` and `Meters` structs wrapped `u32` values in a newtype. If we wrote a function with a -parameter of type `Millimeters`, we couldn’t compile a program that accidentally -tried to call that function with a value of type `Meters` or a plain `u32`. +parameter of type `Millimeters`, we wouldn’t be able to compile a program that +accidentally tried to call that function with a value of type `Meters` or a +plain `u32`. We can also use the newtype pattern to abstract away some implementation details of a type: the new type can expose a public API that is different from @@ -28,7 +29,7 @@ associated with their name. Code using `People` would only interact with the public API we provide, such as a method to add a name string to the `People` collection; that code wouldn’t need to know that we assign an `i32` ID to names internally. The newtype pattern is a lightweight way to achieve encapsulation to -hide implementation details, which we discussed in [“Encapsulation that Hides +hide implementation details, which we discussed in [“Encapsulation That Hides Implementation Details”][encapsulation-that-hides-implementation-details]<!-- ignore --> in Chapter 18. @@ -53,7 +54,7 @@ values of type `i32`: Because `Kilometers` and `i32` are the same type, we can add values of both types and we can pass `Kilometers` values to functions that take `i32` -parameters. However, using this method, we don’t get the type checking benefits +parameters. However, using this method, we don’t get the type-checking benefits that we get from the newtype pattern discussed earlier. In other words, if we mix up `Kilometers` and `i32` values somewhere, the compiler will not give us an error. @@ -81,7 +82,7 @@ A type alias makes this code more manageable by reducing the repetition. In Listing 20-26, we’ve introduced an alias named `Thunk` for the verbose type and can replace all uses of the type with the shorter alias `Thunk`. -<Listing number="20-26" caption="Introducing a type alias, `Thunk`, to reduce repetition"> +<Listing number="20-26" caption="Introducing a type alias `Thunk` to reduce repetition"> ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-26/src/main.rs:here}} @@ -127,7 +128,7 @@ us a consistent interface across all of `std::io`. Because it’s an alias, it just another `Result<T, E>`, which means we can use any methods that work on `Result<T, E>` with it, as well as special syntax like the `?` operator. -### The Never Type that Never Returns +### The Never Type That Never Returns Rust has a special type named `!` that’s known in type theory lingo as the _empty type_ because it has no values. We prefer to call it the _never type_ @@ -143,7 +144,7 @@ never are called _diverging functions_. We can’t create values of the type `!` so `bar` can never possibly return. But what use is a type you can never create values for? Recall the code from -Listing 2-5, part of the number guessing game; we’ve reproduced a bit of it +Listing 2-5, part of the number-guessing game; we’ve reproduced a bit of it here in Listing 20-27. <Listing number="20-27" caption="A `match` with an arm that ends in `continue`"> diff --git a/src/ch20-04-advanced-functions-and-closures.md b/src/ch20-04-advanced-functions-and-closures.md index 11a25d7554..3f2500daf5 100644 --- a/src/ch20-04-advanced-functions-and-closures.md +++ b/src/ch20-04-advanced-functions-and-closures.md @@ -8,14 +8,14 @@ including function pointers and returning closures. We’ve talked about how to pass closures to functions; you can also pass regular functions to functions! This technique is useful when you want to pass a function you’ve already defined rather than defining a new closure. Functions -coerce to the type `fn` (with a lowercase f), not to be confused with the `Fn` +coerce to the type `fn` (with a lowercase _f_), not to be confused with the `Fn` closure trait. The `fn` type is called a _function pointer_. Passing functions with function pointers will allow you to use functions as arguments to other functions. The syntax for specifying that a parameter is a function pointer is similar to that of closures, as shown in Listing 20-28, where we’ve defined a function -`add_one` that adds one to its parameter. The function `do_twice` takes two +`add_one` that adds 1 to its parameter. The function `do_twice` takes two parameters: a function pointer to any function that takes an `i32` parameter and returns an `i32`, and one `i32` value. The `do_twice` function calls the function `f` twice, passing it the `arg` value, then adds the two function call @@ -51,39 +51,53 @@ functions can accept functions as arguments, but C doesn’t have closures. As an example of where you could use either a closure defined inline or a named function, let’s look at a use of the `map` method provided by the `Iterator` -trait in the standard library. To use the `map` function to turn a vector of -numbers into a vector of strings, we could use a closure, like this: +trait in the standard library. To use the `map` method to turn a vector of +numbers into a vector of strings, we could use a closure, as in Listing 20-29. + +<Listing number="20-29" caption="Using a closure with the `map` method to convert numbers to strings"> ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-15-map-closure/src/main.rs:here}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-29/src/main.rs:here}} ``` -Or we could name a function as the argument to `map` instead of the closure, -like this: +</Listing> + +Or we could name a function as the argument to map instead of the closure. +Listing 20-30 shows what this would look like. + +<Listing number="20-30" caption="Using the `String::to_string` method to convert numbers to strings"> ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-16-map-function/src/main.rs:here}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-30/src/main.rs:here}} ``` +</Listing> + Note that we must use the fully qualified syntax that we talked about in [“Advanced Traits”][advanced-traits]<!-- ignore --> because there are multiple -functions available named `to_string`. Here, we’re using the `to_string` -function defined in the `ToString` trait, which the standard library has -implemented for any type that implements `Display`. +functions available named `to_string`. + +Here, we’re using the `to_string` function defined in the `ToString` trait, +which the standard library has implemented for any type that implements +`Display`. Recall from [“Enum values”][enum-values]<!-- ignore --> in Chapter 6 that the name of each enum variant that we define also becomes an initializer function. We can use these initializer functions as function pointers that implement the closure traits, which means we can specify the initializer functions as -arguments for methods that take closures, like so: +arguments for methods that take closures, as seen in Listing 20-31. + +<Listing number="20-31" caption="Using an enum initializers with the `map` method to create a `Status` instance from numbers"> ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-17-map-initializer/src/main.rs:here}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-31/src/main.rs:here}} ``` +</Listing> + Here we create `Status::Value` instances using each `u32` value in the range that `map` is called on by using the initializer function of `Status::Value`. -Some people prefer this style, and some people prefer to use closures. They +Some people prefer this style and some people prefer to use closures. They compile to the same code, so use whichever style is clearer to you. ### Returning Closures @@ -92,31 +106,66 @@ Closures are represented by traits, which means you can’t return closures directly. In most cases where you might want to return a trait, you can instead use the concrete type that implements the trait as the return value of the function. However, you can’t usually do that with closures because they don’t -usually have a concrete type that is returnable. You’re not allowed to use the -function pointer `fn` as a return type if the closure captures any values from -its scope, for example. +have a concrete type that is returnable. You’re not allowed to use the function +pointer `fn` as a return type if the closure captures any values from its scope, +for example. Instead, you will normally use the `impl Trait` syntax we learned about in Chapter 10. You can return any function type, using `Fn`, `FnOnce` and `FnMut`. -For example, this code will work just fine: +For example, the code in Listing 20-32 will work just fine. + +<Listing number="20-32" caption="Returning a closure from a function using the `impl Trait` syntax"> ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-18-returns-closure/src/lib.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-32/src/lib.rs}} +``` + +</Listing> + +However, as we noted in [“Closure Type Inference and +Annotation”][closure-types]<!-- ignore --> in Chapter 13, each closure is also +its own distinct type. If you need to work with multiple functions that have the +same signature but different implementations, you will need to use a trait +object for them. Consider what happens if you write code like that shown in +Listing 20-33. + +<Listing file-name="src/main.rs" number="20-33" caption="Creating a `Vec<T>` of closures defined by functions that return `impl Fn`"> + +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-33/src/main.rs}} ``` -However, as we noted in the [“Closure Type Inference and -Annotation”][closure-types]<!-- ignore --> section in Chapter 13, each closure -is also its own distinct type. If you need to work with multiple functions that -have the same signature but different implementations, you will need to use a -trait object for them: +</Listing> + +Here we have two functions, `returns_closure` and `returns_initialized_closure`, +which both return `impl Fn(i32) -> i32`. Notice that he closures that they +return are different, even though they implement the same type. If we try to +compile this, Rust lets us know that it won’t work: -```rust,noplayground -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-19-returns-closure-trait-object/src/main.rs}} +```text +{{#include ../listings/ch20-advanced-features/listing-20-33/output.txt}} ``` -This code will compile just fine—but it wouldn’t if we had tried to stick with -`impl Fn(i32) -> i32`. For more about trait objects, refer to the section -[“Using Trait Objects That Allow for Values of Different +The error message tells us that whenever we return an `impl Trait` Rust creates +a unique _opaque type_, a type where we cannot see into the details of what Rust +constructs for us. So even though these functions both return closures that +implements the same trait, `Fn(i32) -> i32`, the opaque types Rust generates for +each are distinct. (This is similar to how Rust produces different concrete +types for distinct async blocks even when they have the same output type, as we +saw in [“Working with Any Number of Futures”][any-number-of-futures] in Chapter +17. We have seen a solution to this problem a few times now: we can use a trait +object, as in Listing 20-34. + +<Listing number="20-34" caption="Creating a `Vec<T>` of closures defined by functions that return `Box<dyn Fn>` so they have the same type"> + +```rust +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-34/src/main.rs:here}} +``` + +</Listing> + +This code will compile just fine. For more about trait objects, refer to the +section [“Using Trait Objects That Allow for Values of Different Types”][using-trait-objects-that-allow-for-values-of-different-types]<!-- ignore --> in Chapter 18. @@ -125,4 +174,5 @@ Next, let’s look at macros! [advanced-traits]: ch20-02-advanced-traits.html#advanced-traits [enum-values]: ch06-01-defining-an-enum.html#enum-values [closure-types]: ch13-01-closures.html#closure-type-inference-and-annotation +[any-number-of-futures]: ch17-03-more-futures.html [using-trait-objects-that-allow-for-values-of-different-types]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types diff --git a/src/ch20-05-macros.md b/src/ch20-05-macros.md index 26e6941620..d39077c8c5 100644 --- a/src/ch20-05-macros.md +++ b/src/ch20-05-macros.md @@ -24,7 +24,7 @@ macros _expand_ to produce more code than the code you’ve written manually. Metaprogramming is useful for reducing the amount of code you have to write and maintain, which is also one of the roles of functions. However, macros have -some additional powers that functions don’t. +some additional powers that functions don’t have. A function signature must declare the number and type of parameters the function has. Macros, on the other hand, can take a variable number of @@ -51,7 +51,7 @@ are also sometimes referred to as “macros by example,” “`macro_rules!` mac or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust `match` expression. As discussed in Chapter 6, `match` expressions are control structures that take an expression, compare the -resulting value of the expression to patterns, and then run the code associated +resultant value of the expression to patterns, and then run the code associated with the matching pattern. Macros also compare a value to patterns that are associated with particular code: in this situation, the value is the literal Rust source code passed to the macro; the patterns are compared with the @@ -73,19 +73,19 @@ We could also use the `vec!` macro to make a vector of two integers or a vector of five string slices. We wouldn’t be able to use a function to do the same because we wouldn’t know the number or type of values up front. -Listing 20-29 shows a slightly simplified definition of the `vec!` macro. +Listing 20-35 shows a slightly simplified definition of the `vec!` macro. -<Listing number="20-29" file-name="src/lib.rs" caption="A simplified version of the `vec!` macro definition"> +<Listing number="20-35" file-name="src/lib.rs" caption="A simplified version of the `vec!` macro definition"> ```rust,noplayground -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-29/src/lib.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-35/src/lib.rs}} ``` </Listing> > Note: The actual definition of the `vec!` macro in the standard library -> includes code to preallocate the correct amount of memory up front. That code -> is an optimization that we don’t include here to make the example simpler. +> includes code to pre-allocate the correct amount of memory up front. That code +> is an optimization that we don’t include here, to make the example simpler. The `#[macro_export]` annotation indicates that this macro should be made available whenever the crate in which the macro is defined is brought into @@ -103,13 +103,13 @@ is the only pattern in this macro, there is only one valid way to match; any other pattern will result in an error. More complex macros will have more than one arm. -Valid pattern syntax in macro definitions is different than the pattern syntax +Valid pattern syntax in macro definitions is different from the pattern syntax covered in Chapter 19 because macro patterns are matched against Rust code structure rather than values. Let’s walk through what the pattern pieces in Listing 20-29 mean; for the full macro pattern syntax, see the [Rust Reference][ref]. -First, we use a set of parentheses to encompass the whole pattern. We use a +First we use a set of parentheses to encompass the whole pattern. We use a dollar sign (`$`) to declare a variable in the macro system that will contain the Rust code matching the pattern. The dollar sign makes it clear this is a macro variable as opposed to a regular Rust variable. Next comes a set of @@ -151,20 +151,20 @@ Daniel Keep and continued by Lukas Wirth. ### Procedural Macros for Generating Code from Attributes -The second form of macros is the _procedural macro_, which acts more like a -function (and is a type of procedure). Procedural macros accept some code as an -input, operate on that code, and produce some code as an output rather than +The second form of macros is the procedural macro, which acts more like a +function (and is a type of procedure). _Procedural macros_ accept some code as +an input, operate on that code, and produce some code as an output rather than matching against patterns and replacing the code with other code as declarative -macros do. The three kinds of procedural macros are custom derive, +macros do. The three kinds of procedural macros are custom `derive`, attribute-like, and function-like, and all work in a similar fashion. When creating procedural macros, the definitions must reside in their own crate with a special crate type. This is for complex technical reasons that we hope -to eliminate in the future. In Listing 20-30, we show how to define a +to eliminate in the future. In Listing 20-36, we show how to define a procedural macro, where `some_attribute` is a placeholder for using a specific macro variety. -<Listing number="20-30" file-name="src/lib.rs" caption="An example of defining a procedural macro"> +<Listing number="20-36" file-name="src/lib.rs" caption="An example of defining a procedural macro"> ```rust,ignore use proc_macro; @@ -186,7 +186,7 @@ that specifies which kind of procedural macro we’re creating. We can have multiple kinds of procedural macros in the same crate. Let’s look at the different kinds of procedural macros. We’ll start with a -custom derive macro and then explain the small dissimilarities that make the +custom `derive` macro and then explain the small dissimilarities that make the other forms different. ### How to Write a Custom `derive` Macro @@ -199,12 +199,12 @@ we’ll provide a procedural macro so users can annotate their type with function. The default implementation will print `Hello, Macro! My name is TypeName!` where `TypeName` is the name of the type on which this trait has been defined. In other words, we’ll write a crate that enables another -programmer to write code like Listing 20-31 using our crate. +programmer to write code like Listing 20-37 using our crate. -<Listing number="20-31" file-name="src/main.rs" caption="The code a user of our crate will be able to write when using our procedural macro"> +<Listing number="20-37" file-name="src/main.rs" caption="The code a user of our crate will be able to write when using our procedural macro"> ```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-31/src/main.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-37/src/main.rs}} ``` </Listing> @@ -218,21 +218,25 @@ $ cargo new hello_macro --lib Next, we’ll define the `HelloMacro` trait and its associated function: -<Listing file-name="src/lib.rs"> +<Listing file-name="src/lib.rs" number="20-38" caption="A simple trait that we will use with the `derive` macro"> ```rust,noplayground -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/src/lib.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-38/hello_macro/src/lib.rs}} ``` </Listing> We have a trait and its function. At this point, our crate user could implement -the trait to achieve the desired functionality, like so: +the trait to achieve the desired functionality, as in Listing 20-39. + +<Listing number="20-39" file-name="src/main.rs" caption="How it would look if users wrote a manual implementation of the `HelloMacro` trait"> ```rust,ignore -{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-39/pancakes/src/main.rs}} ``` +</Listing> + However, they would need to write the implementation block for each type they wanted to use with `hello_macro`; we want to spare them from having to do this work. @@ -245,7 +249,7 @@ name at runtime. We need a macro to generate code at compile time. The next step is to define the procedural macro. At the time of this writing, procedural macros need to be in their own crate. Eventually, this restriction might be lifted. The convention for structuring crates and macro crates is as -follows: for a crate named `foo`, a custom derive procedural macro crate is +follows: for a crate named `foo`, a custom `derive` procedural macro crate is called `foo_derive`. Let’s start a new crate called `hello_macro_derive` inside our `hello_macro` project: @@ -272,19 +276,19 @@ _Cargo.toml_ file for `hello_macro_derive`: <Listing file-name="hello_macro_derive/Cargo.toml"> ```toml -{{#include ../listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/Cargo.toml:6:12}} +{{#include ../listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/Cargo.toml:6:12}} ``` </Listing> -To start defining the procedural macro, place the code in Listing 20-32 into +To start defining the procedural macro, place the code in Listing 20-40 into your _src/lib.rs_ file for the `hello_macro_derive` crate. Note that this code won’t compile until we add a definition for the `impl_hello_macro` function. -<Listing number="20-32" file-name="hello_macro_derive/src/lib.rs" caption="Code that most procedural macro crates will require in order to process Rust code"> +<Listing number="20-40" file-name="hello_macro_derive/src/lib.rs" caption="Code that most procedural macro crates will require in order to process Rust code"> ```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-32/hello_macro/hello_macro_derive/src/lib.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-40/hello_macro/hello_macro_derive/src/lib.rs}} ``` </Listing> @@ -319,10 +323,10 @@ The `hello_macro_derive` function first converts the `input` from a `TokenStream` to a data structure that we can then interpret and perform operations on. This is where `syn` comes into play. The `parse` function in `syn` takes a `TokenStream` and returns a `DeriveInput` struct representing the -parsed Rust code. Listing 20-33 shows the relevant parts of the `DeriveInput` -struct we get from parsing the `struct Pancakes;` string: +parsed Rust code. Listing 20-40 shows the relevant parts of the `DeriveInput` +struct we get from parsing the `struct Pancakes;` string. -<Listing number="20-33" caption="The `DeriveInput` instance we get when parsing the code that has the macro’s attribute in Listing 20-31"> +<Listing number="20-40" caption="The `DeriveInput` instance we get when parsing the code that has the macro’s attribute in Listing 20-37"> ```rust,ignore DeriveInput { @@ -347,13 +351,13 @@ DeriveInput { </Listing> The fields of this struct show that the Rust code we’ve parsed is a unit struct -with the `ident` (identifier, meaning the name) of `Pancakes`. There are more +with the `ident` (_identifier_, meaning the name) of `Pancakes`. There are more fields on this struct for describing all sorts of Rust code; check the [`syn` documentation for `DeriveInput`][syn-docs] for more information. Soon we’ll define the `impl_hello_macro` function, which is where we’ll build the new Rust code we want to include. But before we do, note that the output -for our derive macro is also a `TokenStream`. The returned `TokenStream` is +for our `derive` macro is also a `TokenStream`. The returned `TokenStream` is added to the code that our crate users write, so when they compile their crate, they’ll get the extra functionality that we provide in the modified `TokenStream`. @@ -368,12 +372,12 @@ about what went wrong by using `panic!` or `expect`. Now that we have the code to turn the annotated Rust code from a `TokenStream` into a `DeriveInput` instance, let’s generate the code that implements the -`HelloMacro` trait on the annotated type, as shown in Listing 20-34. +`HelloMacro` trait on the annotated type, as shown in Listing 20-42. -<Listing number="20-34" file-name="hello_macro_derive/src/lib.rs" caption="Implementing the `HelloMacro` trait using the parsed Rust code"> +<Listing number="20-42" file-name="hello_macro_derive/src/lib.rs" caption="Implementing the `HelloMacro` trait using the parsed Rust code"> ```rust,ignore -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-34/hello_macro/hello_macro_derive/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-42/hello_macro/hello_macro_derive/src/lib.rs:here}} ``` </Listing> @@ -384,7 +388,7 @@ we run the `impl_hello_macro` function on the code in Listing 20-31, the `ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus, the `name` variable in Listing 20-34 will contain an `Ident` struct instance that, when printed, will be the string `"Pancakes"`, the name of the struct in -Listing 20-31. +Listing 20-37. The `quote!` macro lets us define the Rust code that we want to return. The compiler expects something different to the direct result of the `quote!` @@ -405,7 +409,7 @@ the name of the annotated type. The `stringify!` macro used here is built into Rust. It takes a Rust expression, such as `1 + 2`, and at compile time turns the expression into a -string literal, such as `"1 + 2"`. This is different than `format!` or +string literal, such as `"1 + 2"`. This is different from `format!` or `println!`, macros which evaluate the expression and then turn the result into a `String`. There is a possibility that the `#name` input might be an expression to print literally, so we use `stringify!`. Using `stringify!` also @@ -424,22 +428,22 @@ dependencies; if not, you can specify them as `path` dependencies as follows: {{#include ../listings/ch20-advanced-features/no-listing-21-pancakes/pancakes/Cargo.toml:7:9}} ``` -Put the code in Listing 20-31 into _src/main.rs_, and run `cargo run`: it +Put the code in Listing 20-37 into _src/main.rs_, and run `cargo run`: it should print `Hello, Macro! My name is Pancakes!` The implementation of the `HelloMacro` trait from the procedural macro was included without the `pancakes` crate needing to implement it; the `#[derive(HelloMacro)]` added the trait implementation. Next, let’s explore how the other kinds of procedural macros differ from custom -derive macros. +`derive` macros. ### Attribute-Like macros -Attribute-like macros are similar to custom derive macros, but instead of +Attribute-like macros are similar to custom `derive` macros, but instead of generating code for the `derive` attribute, they allow you to create new attributes. They’re also more flexible: `derive` only works for structs and enums; attributes can be applied to other items as well, such as functions. -Here’s an example of using an attribute-like macro: say you have an attribute +Here’s an example of using an attribute-like macro. Say you have an attribute named `route` that annotates functions when using a web application framework: ```rust,ignore @@ -460,7 +464,7 @@ contents of the attribute: the `GET, "/"` part. The second is the body of the item the attribute is attached to: in this case, `fn index() {}` and the rest of the function’s body. -Other than that, attribute-like macros work the same way as custom derive +Other than that, attribute-like macros work the same way as custom `derive` macros: you create a crate with the `proc-macro` crate type and implement a function that generates the code you want! @@ -489,7 +493,7 @@ syntactically correct, which is much more complex processing than a pub fn sql(input: TokenStream) -> TokenStream { ``` -This definition is similar to the custom derive macro’s signature: we receive +This definition is similar to the custom `derive` macro’s signature: we receive the tokens that are inside the parentheses and return the code we wanted to generate. @@ -498,7 +502,7 @@ generate. Whew! Now you have some Rust features in your toolbox that you likely won’t use often, but you’ll know they’re available in very particular circumstances. We’ve introduced several complex topics so that when you encounter them in -error message suggestions or in other peoples’ code, you’ll be able to +error message suggestions or in other people’s code, you’ll be able to recognize these concepts and syntax. Use this chapter as a reference to guide you to solutions. diff --git a/src/ch21-00-final-project-a-web-server.md b/src/ch21-00-final-project-a-web-server.md index 8b9cf6d346..88eb2e6ebe 100644 --- a/src/ch21-00-final-project-a-web-server.md +++ b/src/ch21-00-final-project-a-web-server.md @@ -20,7 +20,7 @@ Here is our plan for building the web server: 4. Create a proper HTTP response. 5. Improve the throughput of our server with a thread pool. -Before we get started, we should mention two details: First, the method we’ll +Before we get started, we should mention two details. First, the method we’ll use won’t be the best way to build a web server with Rust. Community members have published a number of production-ready crates available on [crates.io](https://crates.io/) that provide more complete web server and thread diff --git a/src/ch21-02-multithreaded.md b/src/ch21-02-multithreaded.md index 51a6bab722..c89244e26d 100644 --- a/src/ch21-02-multithreaded.md +++ b/src/ch21-02-multithreaded.md @@ -75,7 +75,7 @@ we can handle before reaching that point. This technique is just one of many ways to improve the throughput of a web server. Other options you might explore are the fork/join model, the -single-threaded async I/O model, and the multi-threaded async I/O model. If +single-threaded async I/O model, and the multithreaded async I/O model. If you’re interested in this topic, you can read more about other solutions and try to implement them; with a low-level language like Rust, all of these options are possible. diff --git a/src/title-page.md b/src/title-page.md index 5d16f7f98f..872e108204 100644 --- a/src/title-page.md +++ b/src/title-page.md @@ -4,8 +4,9 @@ _by Steve Klabnik, Carol Nichols, and Chris Krycho, with contributions from the Rust Community_ This version of the text assumes you’re using Rust 1.85.0 (released 2025-02-17) -or later. See the [“Installation” section of Chapter 1][install]<!-- ignore --> -to install or update Rust. +or later with `edition = "2024"` in the Cargo.toml file of all projects to +configure them to use Rust 2024 edition idioms. See the [“Installation” section +of Chapter 1][install]<!-- ignore --> to install or update Rust. The HTML format is available online at [https://doc.rust-lang.org/stable/book/](https://doc.rust-lang.org/stable/book/)