From 27ceeb2dab37745f405f8e4f1ad5a17d4b8a50fc Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Apr 2017 13:37:37 -0400 Subject: [PATCH] More edits --- second-edition/src/ch17-02-trait-objects.md | 3 +- .../src/ch19-02-advanced-lifetimes.md | 25 +- second-edition/src/ch19-03-advanced-traits.md | 12 +- second-edition/src/ch19-04-advanced-types.md | 295 +++++++++++------- ...ch19-05-advanced-functions-and-closures.md | 129 ++++---- 5 files changed, 284 insertions(+), 180 deletions(-) diff --git a/second-edition/src/ch17-02-trait-objects.md b/second-edition/src/ch17-02-trait-objects.md index c4b2caa62..4f6b7a4d4 100644 --- a/second-edition/src/ch17-02-trait-objects.md +++ b/second-edition/src/ch17-02-trait-objects.md @@ -44,7 +44,8 @@ instances and call `draw` on them. In Rust, though, we can define a trait that we'll name `Draw` and that will have one method named `draw`. Then we can define a vector that takes a *trait object*, which is a trait behind some sort of pointer, such as a `&` reference -or a `Box` smart pointer. +or a `Box` smart pointer. We'll talk about the reason trait objects have to +be behind a pointer in Chapter 19. We mentioned that we don't call structs and enums "objects" to distinguish structs and enums from other languages' objects. The data in the struct or enum diff --git a/second-edition/src/ch19-02-advanced-lifetimes.md b/second-edition/src/ch19-02-advanced-lifetimes.md index e4153873a..0036bc018 100644 --- a/second-edition/src/ch19-02-advanced-lifetimes.md +++ b/second-edition/src/ch19-02-advanced-lifetimes.md @@ -281,10 +281,10 @@ something you have a reference to. In Chapter 10, we discussed how to use trait bounds on generic types. We can also add lifetime parameters as constraints on generic types. For example, let's say we wanted to make a wrapper over references. Remember `RefCell` -from Chapter 15? This is how the `borrow` and `borrow_mut` methods work; -they return wrappers over references in order to keep track of the borrowing -rules at run time. The struct definition without lifetime parameters would look -like Listing 19-16: +from Chapter 15? This is how the `borrow` and `borrow_mut` methods work; they +return wrappers over references in order to keep track of the borrowing rules +at runtime. The struct definition, without lifetime parameters for now, would +look like Listing 19-16: ```rust,ignore struct Ref(&T); @@ -350,14 +350,15 @@ struct StaticRef(&'static T); to constrain `T` to types that have only `'static` references or no references -Types with no references count as `T: 'static`. Because `'static` means "this -reference must live as long as the entire program," a type that contains no -references, well, all of them live as long as the entire program. This can be -a little bit hard to get at first, but think of it this way: if the borrow -checker is concerned about references living long enough, then there's no -real distinction between "this has no references" and "this has references -that live forever"; both of them are the same for the purpose of "does this -reference live shorter than what it refers to." +Types with no references count as `T: 'static`. Because `'static` means the +reference must live as long as the entire program, a type that contains no +references meets the criteria of all references living as long as the entire +program (since there are no references). Think of it this way: if the borrow +checker is concerned about references living long enough, then there's no real +distinction between a type that has no references and a type that has +references that live forever; both of them are the same for the purpose of +determining whether or not a reference has a shorter lifetime than what it +refers to. ### Lifetimes in Trait Objects diff --git a/second-edition/src/ch19-03-advanced-traits.md b/second-edition/src/ch19-03-advanced-traits.md index 4f0fecd07..5b32f5070 100644 --- a/second-edition/src/ch19-03-advanced-traits.md +++ b/second-edition/src/ch19-03-advanced-traits.md @@ -398,6 +398,10 @@ the instance of `Baz` as the first argument. Listing 19-28 shows how to call # impl Bar for Baz { # fn f(&self) { println!("Baz’s impl of Bar"); } # } +# impl Baz { +# fn f(&self) { println!("Baz's impl"); } +# } +# fn main() { let b = Baz; b.f(); @@ -423,7 +427,7 @@ on `Baz` and the `Foo` trait implemented on `Baz` in scope, we could call the `f` method in `Foo` by using `Foo::f(&b)` since we wouldn't have to disambiguate from the `Bar` trait. -### Super Traits +### Supertraits to Use One Trait's Functionality Within Another Trait Sometimes, we may want a trait to be able to rely on another trait also being implemented wherever our trait is implemented, so that our trait can use the @@ -482,6 +486,7 @@ If we try to implement `OutlinePrint` on a type that doesn't implement `Display`, such as the `Point` struct: ```rust +# trait OutlinePrint {} struct Point { x: i32, y: i32, @@ -510,6 +515,11 @@ Once we implement `Display` on `Point` and satisfy the constraint that `OutlinePrint` requires, like so: ```rust +# struct Point { +# x: i32, +# y: i32, +# } +# use std::fmt; impl fmt::Display for Point { diff --git a/second-edition/src/ch19-04-advanced-types.md b/second-edition/src/ch19-04-advanced-types.md index fdb600c64..51376a8c8 100644 --- a/second-edition/src/ch19-04-advanced-types.md +++ b/second-edition/src/ch19-04-advanced-types.md @@ -1,42 +1,79 @@ -# Advanced Types +## Advanced Types -There's a few aspects of Rust's type system we haven't gone over. Write a better -intro that isn't literally the same as every other section here :frown: +The Rust type system has some features that we've mentioned or used without +discussing. We started talking about the newtype pattern in regards to traits; +we'll start with a more general discussion about why newtypes are useful as +types. We'll then move to type aliases, a feature that is similar to newtypes +but has slightly different semantics. We'll also discuss the `!` type and +dynamically sized types. -## Type Aliases +### Using the Newtype Pattern for Type Safety and Abstraction -Rust provides the ability to declare a 'type alias' with the `type` keyword: +The newtype pattern that we started discussing at the end of the "Advanced +Traits" section, where we create a new type as a tuple struct with one field +that wraps a type can also be useful for statically enforcing that values are +never confused, and is often used to indicate the units of a value. We actually +had an example of this in Listing 19-26: the `Millimeters` and `Meters` structs +both wrap `u32` values in a new type. If we write a function with a parameter +of type `Millimeters`, we won't be able to compile a program that accidentally +tries to call that function with a value of type `Meters` or a plain `u32`. + +Another reason to use the newtype pattern is to abstract away some +implementation details of a type: the wrapper type can expose a different +public API than the private inner type would if we used it directly in order to +restrict the functionality that is available, for example. New types can also +hide internal generic types. For example, we could provide a `People` type that +wraps a `HashMap` that stores a person's ID 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, and 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 that we discussed in Chapter 17. + +### Type Aliases Create Type Synonyms + +The newtype pattern involves creating a new struct to be a new, separate type. +Rust also provides the ability to declare a *type alias* with the `type` +keyword to give an existing type another name. For example, we can create the +alias `Kilometers` to `i32` like so: ```rust -type Foo = i32; +type Kilometers = i32; ``` -This means that `Foo` is a _synonym_ for `i32`; it's not its own, new type. Which -means you can do this: +This means `Kilometers` is a *synonym* for `i32`; unlike the `Millimeters` and +`Meters` types we created in Listing 19-26, `Kilometers` is not a separate, new +type. Values that have the type `Kilometers` will be treated exactly the same +as values of type `i32`: ```rust -type Foo = i32; +type Kilometers = i32; let x: i32 = 5; -let y: Foo = 5; +let y: Kilometers = 5; println!("x + y = {}", x + y); ``` -Since `Foo` is an alias for `i32`, they're the same type, and we can add them together. -If you want a distinct type for `Foo`, you'd use the newtype pattern from Chapter XX. +Since `Kilometers` is an alias for `i32`, they're the same type. We can add +values of type `i32` and `Kilometers` together, and we can pass `Kilometers` +values to functions that take `i32` parameters. We don't get the type checking +benefits that we get from the newtype pattern that we discussed in the previous +section. -The main use-case for type synonyms is to reduce repetition. For example, you may have -a type like this: +The main use case for type synonyms is to reduce repetition. For example, we +may have a lengthy type like this: ```rust,ignore Box ``` -Typing this out all over the place can be tiresome and error-prone: +Writing this out in function signatures and as type annotations all over the +place can be tiresome and error-prone. Imagine having a project full of code +like that in Listing 19-31: -```rust,ignore -let f: Box = |x| x + 1; +```rust +let f: Box = Box::new(|| println!("hi")); fn takes_long_type(f: Box) { // ... @@ -44,15 +81,21 @@ fn takes_long_type(f: Box) { fn returns_long_type() -> Box { // ... +# Box::new(|| ()) } ``` -An alias makes this more manageable: +Listing 19-31: Using a long type in many places -```rust,ignore +A type alias makes this code more manageable by reducing the amount of +repetition this project has. Here, we've introduced an alias named `Thunk` for +the verbose type, and we can replace all uses of the type with the shorter +`Thunk` as shown in Listing 19-32: + +```rust type Thunk = Box; -let f: Thunk = |x| x + 1; +let f: Thunk = Box::new(|| println!("hi")); fn takes_long_type(f: Thunk) { // ... @@ -60,78 +103,97 @@ fn takes_long_type(f: Thunk) { fn returns_long_type() -> Thunk { // ... +# Box::new(|| ()) } ``` -Much easier. A related case is with the `Result` type. Consider the `std::io` -module in the standard library. I/O operations often return a `Result`, as they -may fail to work. So, there's a struct, `std::io::Error`, that represents all of these -different possible errors. Many of the functions in `std::io` will be returning a -`Result` where the `E` is an `std::io::Error`. For example, the `Write` trait: +Listing 19-32: Introducing a type alias `Thunk` to reduce +repetition -```rust,ignore +Much easier to read and write! Choosing a good name for a type alias can help +communicate your intent as well (*thunk* is a word for code to be evaluated at +a later time, so it's an appropriate name for a closure that gets stored). + +Another common use of type aliases is with the `Result` type. Consider +the `std::io` module in the standard library. I/O operations often return a +`Result`, since their operations may fail to work. There's a +`std::io::Error` struct that represents all of the possible I/O errors. Many of +the functions in `std::io` will be returning `Result` where the `E` is +`std::io::Error`, such as these functions in the `Write` trait: + +```rust use std::io::Error; +# use std::fmt::Arguments; pub trait Write { fn write(&mut self, buf: &[u8]) -> Result; fn flush(&mut self) -> Result<(), Error>; - fn write_all(&mut self, buf: &[u8]) -> Result<(), Error> { ... } - fn write_fmt(&mut self, fmt: Arguments) -> Result<(), Error> { ... } + fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>; + fn write_fmt(&mut self, fmt: Arguments) -> Result<(), Error>; } ``` -We're writing `Result<..., Error>` a lot. As such, `std::io` has this -declaration: +We're writing `Result<..., Error>` a lot. As such, `std::io` has this type +alias declaration: ```rust,ignore type Result = Result; ``` -Because this is in the `std::io` module, it's now `std::io::Result`; that is, -a `Result` with the `E` filled in as `std::io::Error`. This helps in two -ways: first, the `Write` trait ends up looking like this: +Because this is in the `std::io` module, the fully qualified alias that we can +use is `std::io::Result`; that is, a `Result` with the `E` filled in +as `std::io::Error`. The `Write` trait function signatures end up looking like +this: ```rust,ignore pub trait Write { fn write(&mut self, buf: &[u8]) -> Result; fn flush(&mut self) -> Result<()>; - fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... } - fn write_fmt(&mut self, fmt: Arguments) -> Result<()> { ... } + fn write_all(&mut self, buf: &[u8]) -> Result<()>; + fn write_fmt(&mut self, fmt: Arguments) -> Result<()>; } ``` -This is easier to write *and* gives us a consistent interface across all -of `std::io`. But because it's an alias, it is just another `Result`, -which means we can use any methods that work on `Result` with it, -and special syntax like `?`. +The type alias helps in two ways: this is easier to write *and* it gives us a +consistent interface across all of `std::io`. Because it's an alias, it is just +another `Result`, which means we can use any methods that work on +`Result` with it, and special syntax like `?`. -## The 'never' type, `!` +### The Never Type, `!`, that Never Returns -Rust has a special type named `!`. In type theory lingo, it's called the 'bottom type', -but we prefer the name 'never'. The name describes what it does: +Rust has a special type named `!`. In type theory lingo, it's called the +*bottom type*, but we prefer the name *never*. The name describes what it does: +it stands in the place of the return type when a function will never return. +For example: ```rust,ignore fn bar() -> ! { ``` -This is read as "the function `bar` returns never." And in this case, that's what -it means! You cannot create values of the type `!`, and so `bar` can never possibly -return. How could it, if it can't create a value to return? +This is read as "the function `bar` returns never," and functions that return +never are called *diverging functions*. We can't create values of the type `!`, +so `bar` can never possibly return. What use is a type you can never create +values for? If you think all the way back to Chapter 2, we had some code that +looked like this, reproduced here in Listing 19-33: -What use is a type you can never create values for? If you think all the way back -to Chapter 2, we had some code that looked like this: - -```rust,ignore +```rust +# let guess = "3"; +# loop { let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; +# break; +# } ``` -At the time, we skipped over some details. For example, you've learned that -`match` arms must have the same value. This doesn't work: +Listing 19-33: A `match` with an arm that ends in +`continue` + +At the time, we skipped over some details in this code. In Chapter 6, we +learned that `match` arms must return the same type. This doesn't work: ```rust,ignore let guess = match guess.trim().parse() { @@ -140,20 +202,24 @@ let guess = match guess.trim().parse() { } ``` -What would the type of `guess` be here? It'd have to be both an integer and a string, -and that doesn't work. So why does `continue`? +What would the type of `guess` be here? It'd have to be both an integer and a +string, and Rust requires that `guess` can only have one type. So what +`continue` return? Why are we allowed to return a `u32` from one arm in Listing +19-33 and have another arm that ends with `continue`? -As you may have guessed, `continue` has a value of `!`. That is, when Rust goes to -compute the type of `guess`, it looks at both of the match arms. The former has a -value of `u32`, and the latter has a value of `!`. Since `!` can never have a value, -Rust is okay with this, and decides that the type of `guess` is `u32`. The fancy way -of saying this is that "never unifies with all other types". This works because -`continue` doesn't actually return a value; it instead moves control back to the top -of the loop. In the `Err` case, we never actually assign a value to `guess`. So -this is fine. +As you may have guessed, `continue` has a value of `!`. That is, when Rust goes +to compute the type of `guess`, it looks at both of the match arms. The former +has a value of `u32`, and the latter has a value of `!`. Since `!` can never +have a value, Rust is okay with this, and decides that the type of `guess` is +`u32`. The formal way of describing this behavior of `!` is that the never type +unifies with all other types. We're allowed to end this `match` arm with +`continue` because `continue` doesn't actually return a value; it instead moves +control back to the top of the loop, so in the `Err` case, we never actually +assign a value to `guess`. -Another example of the never type is `panic!`. Remember the `unwrap` function that -we call on `Option` values to produce a value or panic? Here's its definition: +Another use of the never type is `panic!`. Remember the `unwrap` function that +we call on `Option` values to produce a value or panic? Here's its +definition: ```rust,ignore impl Option { @@ -166,10 +232,11 @@ impl Option { } ``` -Here, the same thing happens: We know that `val` has the type `T`, and `panic!` has -the type `!`. So the result of the overall `match` expression is `T`. This works -because `panic!` doesn't produce a value; it panics. In the `None` case, we won't be -returning a value from `unwrap`, and so it all works out. +Here, the same thing happens as in the `match` in Listing 19-33: we know that +`val` has the type `T`, and `panic!` has the type `!`, so the result of the +overall `match` expression is `T`. This works because `panic!` doesn't produce +a value; it ends the program. In the `None` case, we won't be returning a value +from `unwrap`, so this code is valid. One final expression that has the type `!` is a `loop`: @@ -181,89 +248,93 @@ loop { } ``` -Here, the loop never ends, and so the value of the expression is `!`. This -wouldn't be true if we included a `break`, however, as the loop would terminate. +Here, the loop never ends, so the value of the expression is `!`. This wouldn't +be true if we included a `break`, however, as the loop would terminate when it +gets to the `break`. -## Dynamically Sized Types & `Sized` +### Dynamically Sized Types & `Sized` -Because Rust needs to know things like memory layout, there's a particular corner -of its type system that can be confusing, and that's the concept of 'dynamically -sized types.' Sometimes referred to as 'DSTs' or 'unsized types', these types let -us talk about things that we only know the size of at runtime. +Because Rust needs to know things like memory layout, there's a particular +corner of its type system that can be confusing, and that's the concept of +*dynamically sized types*. Sometimes referred to as 'DSTs' or 'unsized types', +these types let us talk about types whose size we can only know at runtime. -That's extremely abstract, so let's dig into the details of a dynamically sized -type that we've been using this whole book: `str`. That's right, not `&str`, but -`str`, on its own. `str` is a DST; we can't know how long the string is until -runtime. Since we can't know that, we can't create a variable of type `str`; -nor can we take an argument of type `str`. Consider this code, which does not -work: +Let's dig into the details of a dynamically sized type that we've been using +this whole book: `str`. That's right, not `&str`, but `str` on its own. `str` +is a DST; we can't know how long the string is until runtime. Since we can't +know that, we can't create a variable of type `str`, nor can we take an +argument of type `str`. Consider this code, which does not work: ```rust,ignore let s1: str = "Hello there!"; let s2: str = "How's it going?"; ``` -These two `str`s would need to have the exact same memory layout, but they have -different lengths: `s1` needs 12 bytes of storage, and `s2` needs 15. This is -why it's not possible to create a variable holding a dynamically sized type. +These two `str` values would need to have the exact same memory layout, but +they have different lengths: `s1` needs 12 bytes of storage, and `s2` needs 15. +This is why it's not possible to create a variable holding a dynamically sized +type. -So what to do? Well, you already know the answer in this case: `s1` and `s2` -aren't just `str`s, but `&str`s, and more specifically, `&'static str`s, though -the static bit isn't particularly relevant here. If you think back to Chapter 4, -we said this about `&str`: +So what to do? Well, you already know the answer in this case: the types of +`s1` and `s2` are `&str` rather than `str`. If you think back to Chapter 4, we +said this about `&str`: > ... it’s a reference to an internal position in the String and the number of > elements that it refers to. -So while a `&T` is a single value, storing the memory address of where the `T` -is located, a `&str` is _two_ values: the address of the `str`, and how long it +So while a `&T` is a single value that stors the memory address of where the `T` +is located, a `&str` is *two* values: the address of the `str` and how long it is. As such, a `&str` has a size we can know at compile time: it's two times the size of a `usize` in length. That is, we always know the size of a `&str`, no matter how long the string it refers to is. This is the general way in which dynamically sized types are used in Rust; they have an extra bit of metadata -that stores the dynamic information. This leads us to the golden rule of -dynamically sized types: - -You must always put values of dynamically sized types behind a pointer of some -kind. +that stores the size of the dynamic information. This leads us to the golden +rule of dynamically sized types: we must always put values of dynamically sized +types behind a pointer of some kind. While we've talked a lot about `&str`, we can combine `str` with all kinds of pointers: `Box`, for example, or `Rc`. In fact, you've already seen -this before, but with a different dynamically sized type: `Trait`. That is, -the name of a trait, without any sort of qualifications. In Chapter 17, -we only talked about `Box` as a trait object, but given that -just `Trait` on its own is a dynamically sized type, `Rc` or -`&Trait` work too. +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 +Chapter 17, we mentioned that in order to use traits as trait objects, we have +to put them behind a pointer like `&Trait` or `Box` (`Rc` would +work too). Traits being dynamically sized is the reason we have to do that! - +#### The `Sized` Trait -### The Sized trait + To work with DSTs, Rust has a trait that determines if a type's size is known -at compile time or not: `Sized`. This trait is automatically implemented for -everything the compiler knows the size of at compile time. In addition, Rust -sneaks in a bound on `Sized` to every generic function. That is, +at compile time or not, which is `Sized`. This trait is automatically +implemented for everything the compiler knows the size of at compile time. In +addition, Rust implicitly adds a bound on `Sized` to every generic function. +That is, a generic function definitition like this: ```rust,ignore fn generic(t: T) { ``` -is actually +is actually treated as if we had written this: ```rust,ignore fn generic(t: T) { ``` -That is, by default, everything can only work on types that are sized at compile -time. There is, however, special syntax you can use to relax this restriction: +By default, generic functions will only work on types that have a known size at +compile time. There is, however, special syntax you can use to relax this +restriction: ```rust,ignore fn generic(t: &T) { ``` -There's two differences here: `?Sized` is the opposite of `Sized`, that is, this -reads as '`T` may or may not be `Sized`. This syntax is only available for `Sized`, -and not other traits. +A trait bound on `?Sized` is the opposite of a trait bound on `Sized`; that is, +we would read this as '`T` may or may not be `Sized`'. This syntax is only +available for `Sized`, no other traits. -Secondly, you'll note we switched to `&T`; because the argument may not be `Sized`, -we need to use it behind some kind of pointer, in this case, a reference. \ No newline at end of file +Also note we switched the type of the `t` parameter from `T` to `&T`: since the +type might not be `Sized`, we need to use it behind some kind of pointer. In +this case, we've chosen a reference. + +Next let's talk about functions and closures! diff --git a/second-edition/src/ch19-05-advanced-functions-and-closures.md b/second-edition/src/ch19-05-advanced-functions-and-closures.md index a0a3f1ad2..0c41e6558 100644 --- a/second-edition/src/ch19-05-advanced-functions-and-closures.md +++ b/second-edition/src/ch19-05-advanced-functions-and-closures.md @@ -1,14 +1,17 @@ -# Advanced Functions & Closures +## Advanced Functions & Closures -We've talked a lot about functions in this book, and a little bit about a -related feature, closures. There's a few bits we haven't covered yet, so let's -go over those now. +Finally, let's discuss some advanced features having to do with functions and +closures: function pointers, diverging functions, and returning closures. -## Function pointers +### Function pointers We've talked about how to pass closures to functions, but you can pass regular -functions to functions too! Functions have the type `fn()`, with a lower case 'f'. -Don't confuse it with the `Fn()` closure trait! The syntax is similar: +functions to functions too! Functions have the type `fn`, with a lower case 'f' +not to be confused with the `Fn` closure trait. `fn` is called a *function +pointer*. The syntax for specifying that a parameter is a function pointer is +similar to that of closures, as shown in Listing 19-34: + +Filename: src/main.rs ```rust fn add_one(x: i32) -> i32 { @@ -26,61 +29,65 @@ fn main() { } ``` -This prints `The answer is: 12`. This `f(i32) -> i32` syntax is called -a 'function pointer', and unlike closures, you don't use it as a trait, -you use it directly, as you can see in the signature of `do_twice`. +Listing 19-34: Using the `fn` type to accept a function +pointer as an argument -### Point-free style +This 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 +`i32`. We can then call `f` in the body of `do_twice`. In `main`, we can pass +the function name `add_one` as the first argument to `do_twice`. -Function pointers implement all three of the closure traits: `Fn`, `FnMut`, and -`FnOnce`. So you can always pass a pointer to a function that expects a closure: +Unlike closures, `fn` is a type rather than a trait, so we specify `fn` as the +parameter type directly rather than declaring a generic type parameter with one +of the `Fn` traits as a trait bound. + +Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and +`FnOnce`), so we can always pass a function pointer as an argument when calling +a function that expects a closure. Prefer to write functions using a generic +type and one of the closure traits, so that your functions can accept either +functions or closures. An example of a case where you'd only want to accept +`fn` is when interfacing with external code that doesn't have closures: C +functions can accept functions as arguments, but C doesn't have closures. + +For example, if we wanted to use the `map` function to turn a vector of numbers +into a vector of strings, we could use a closure: ```rust -// fold takes a FnMut closure... but we can use this function too! -fn add(acc: i32, x: &i32) -> i32 { - acc + *x -} - -let v = vec![1, 2, 3]; - -let six = v.iter().fold(0, |acc, &x| acc + x); -let six = v.iter().fold(0, add); +let list_of_numbers = vec![1, 2, 3]; +let list_of_strings: Vec = list_of_numbers + .iter() + .map(|i| i.to_string()) + .collect(); ``` -This is sometimes called 'point-free style', for fairly obscure reasons -that don't matter. This can work for anything where the types line up. -For example: +Or we could name a function as the argument to `map` instead of the closure: ```rust -let v = vec![1, 2, 3]; - -let strings: Vec = v.iter().map(|s| s.to_string()).collect(); - -// to_string is provided by the ToString trait -let strings: Vec = v.iter().map(ToString::to_string).collect(); +let list_of_numbers = vec![1, 2, 3]; +let list_of_strings: Vec = list_of_numbers + .iter() + .map(ToString::to_string) + .collect(); ``` +Note that we do have to use the fully qualified syntax that we talked about in +the "Advanced Traits" section 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`. + Some people prefer this style, some people prefer the closure. They end up with the same code, so use whichever feels more clear to you. -## Diverging functions +### Returning Closures -In the previous section, we talked about the never type, `!`. Functions -that return never are called "diverging functions": +Because closures are represented by traits, returning closures is a little +tricky; we can't do it directly. In most cases where we may want to return a +trait, we can instead use the concrete type that implements the trait of what +we're returning as the return value of the function. We can't do that with +closures, though; we're not allowed to use `fn` as a return type, for example. -```rust -fn never_returns() -> ! { - panic!("oh no!"); -} -``` - -For more details, see the previous section. - -## Returning closures - -As we discussed before, closures are represented by traits: `Fn`, `FnMut`, and `FnOnce`. -This means that returning them is a little tricky; you can't do it directly. This will -give a compiler error: +This code that tries to return a closure directly won't compile: ```rust,ignore fn returns_closure() -> Fn(i32) -> i32 { @@ -88,21 +95,25 @@ fn returns_closure() -> Fn(i32) -> i32 { } ``` -It looks like this: +The compiler error is: ```text -error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static: std::marker::Sized` is not satisfied +error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static: +std::marker::Sized` is not satisfied --> :2:25 | 2 | fn returns_closure() -> Fn(i32) -> i32 { - | ^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `std::ops::Fn(i32) -> i32 + 'static` + | ^^^^^^^^^^^^^^ the trait `std::marker::Sized` is + not implemented for `std::ops::Fn(i32) -> i32 + 'static` | - = note: `std::ops::Fn(i32) -> i32 + 'static` does not have a constant size known at compile-time + = note: `std::ops::Fn(i32) -> i32 + 'static` does not have a constant size + known at compile-time = note: the return type of a function must have a statically known size ``` -What to do? With most things that implement traits, we could return them by naming -the type, but we can't do that with closures. Instead, we need to use a trait object: +The `Sized` trait again! Rust doesn't know much space it'll need to store the +closure. We saw a solution to this in the previous section, though: we can use +a trait object: ```rust fn returns_closure() -> Box i32> { @@ -110,4 +121,14 @@ fn returns_closure() -> Box i32> { } ``` -For more about trait objects, see Chapter 18. \ No newline at end of file +For more about trait objects, refer back to Chapter 18. + +## Summary + +Whew! Now we've gone over features of Rust that aren't used very often, but are +available if you need them. We've introduced a lot of complex topics so that +when you encounter them in error message suggestions or when reading others' +code, you'll at least have seen these concepts and syntax once before. + +Now, let's put everything we've learned throughout the book into practice with +one more project!