Compare commits

...

3 Commits

Author SHA1 Message Date
e6bb4977b8 almost finished ch20.4
All checks were successful
Test Gitea Actions / first (push) Successful in 21s
Test Gitea Actions / check-code (push) Successful in 16s
Test Gitea Actions / test (push) Successful in 19s
Test Gitea Actions / documentation-check (push) Successful in 19s
2025-04-16 17:13:02 -06:00
e73197aa26 finished ch20.3 2025-04-16 15:44:05 -06:00
0c0dcfee81 finished ch20.2 2025-04-15 17:03:30 -06:00
5 changed files with 1004 additions and 95 deletions

View File

@ -7,60 +7,18 @@
"id": "64904eb93f53e8e0",
"type": "tabs",
"children": [
{
"id": "b49e674e0ebaaeb7",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Trait Objects that allow for Values of Different Types.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Trait Objects that allow for Values of Different Types"
}
},
{
"id": "f9ef446f856cead7",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Implementing OO Design Pattern.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Implementing OO Design Pattern"
}
},
{
"id": "629d55df46f486d8",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Pattern Matching.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Pattern Matching"
}
},
{
"id": "de2ac5df5b921166",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Advanced Features.md",
"file": "Advanced Functions and Closures.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Advanced Features"
"title": "Advanced Functions and Closures"
}
},
{
@ -77,6 +35,20 @@
"title": "Advanced Traits"
}
},
{
"id": "74db89a42def0b8b",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Advanced Types.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Advanced Types"
}
},
{
"id": "8cdde5c4be386d20",
"type": "leaf",
@ -91,48 +63,6 @@
"title": "Unsafe Rust"
}
},
{
"id": "78cd7043d5d51ffa",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Places Patterns Can Be Used.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Places Patterns Can Be Used"
}
},
{
"id": "7cb3bb4674a93e14",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Refutability.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Refutability"
}
},
{
"id": "6d24bd7ca73ed503",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Pattern Syntax.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Pattern Syntax"
}
},
{
"id": "6ed9f182aa3d5f3e",
"type": "leaf",
@ -143,8 +73,7 @@
"title": "Graph view"
}
}
],
"currentTab": 3
]
}
],
"direction": "vertical"
@ -289,8 +218,11 @@
},
"active": "de2ac5df5b921166",
"lastOpenFiles": [
"Advanced Traits.md",
"Advanced Features.md",
"Advanced Types.md",
"Advanced Traits.md",
"Enums.md",
"Advanced Functions and Closures.md",
"Unsafe Rust.md",
"Pattern Matching.md",
"Places Patterns Can Be Used.md",
@ -312,9 +244,6 @@
"().md",
"Smart Pointers.md",
"Simultaneous Code Running.md",
"Passing Data Between Threads.md",
"Leaky Reference Cycles.md",
"Test Controls.md",
"minigrep/src/lib.rs",
"does_not_compile.svg",
"Untitled.canvas",

View File

@ -10,7 +10,7 @@ The features covered here are useful in very specific situations.
In this chapter we will cover
- [Unsafe Rust](./Unsafe%20Rust.md): How to opt out of some of Rust's guarantees and take responsibility for manually upholding those guarantees
- [Advanced traits](./Advanced%20Traits.md): associated types, default type parameters, fully qualified syntax, supertraits, and the new type pattern in relation to traits
- [Advanced types](): more about the newtype pattern, type aliases, the never type, and dynamically sized types
- [Advanced functions and closures](): function pointers and returning closures
- [Advanced types](./Advanced%20Types.md): more about the newtype pattern, type aliases, the never type, and dynamically sized types
- [Advanced functions and closures](./Advanced%20Functions%20and%20Closures.md): function pointers and returning closures
- [Macros](): ways to define code that defines more code at compile time
a

View File

@ -0,0 +1,106 @@
# Advanced Functions and Closures
This section includes some more advanced features including function pointers and returning closures.
## Function Pointers
We have 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 that you have already defined rather than defining a new closure.
Functions coerce to the type `fn` (with a lowercase f), not to be confused with, not to be confused with the `Fn` closure trait.
The `fn` type is called a *function pointer*.
Passing 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 below.
This shows that we have defined a function `add_one` that adds one to its parameter.
The function `do_twice` takes two parameters: a function pointer to any function 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 results together.
The `main` function calls `do_twice` with the arguments `add_one` and `5`.
```rust
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {answer}");
}
```
Output
```
The answer is: 12
```
Here we specify that the parameter `f` in `do_twice` is an `fn` that takes one parameter of type `i32` and returns a `i32`.
We can then call `f` from the body of `do_twice`.
In `main` we can pass the function name `add_one` as the first arg to `do_twice`.
Unlike closures, `fn` is a type rather than a trait, so we need to 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`).
This means that you can always pass a function pointer as an argument for a function that expects a closure.
It is best to write functions using a generic type and one of the closure traits so your functions can accept either functions or closures.
That being said, one example of where you would only want to accept `fn` and not closures is when interfacing with external code that doesn't have closures.
C 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.
Lets look at a use of the `map` method provided by the `Iterator` trait in the std library.
To use the `map` function to turn a vector of numbers into a vector of strings, we could use a closure.
Like this:
```rust
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> =
list_of_numbers.iter().map(|i| i.to_string()).collect();
```
We could have a function as the argument to `map` instead of the closure.
Like this:
```rust
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> =
list_of_numbers.iter().map(ToString::to_string).collect();
```
Note, we must use the fully qualified syntax that we talked about earlier in ["Advanced Traits"](./Advanced%20Traits.md) section.
This is because there are multiple functions available named `to_string`.
Here we are using the `to_string` function defined in the `ToString` trait, which is in the std library has implemented for any type that implements `Display`.
Recall form the ["Enum values"]() section of Ch6 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, this means we can specify the initializer functions as arguments for methods that take closures.
Like this:
```rust
enum Status {
Value(u32),
Stop,
}
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
```
Here we creates `Status::Value` instances using each `u32` value in the range that `map` is called on by using the initializer function of `Status::Value`.
Some prefer to use this style, and some people prefer to use closures.
They compile to the same code, so use whatever style is clearer to you.
## Returning Closures

View File

@ -1 +1,542 @@
# Advanced Traits
# Advanced Traits
Here we will go into the nitty-gritty of traits..
## 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 implementor of a trait will specify the concrete type to be used instead of the placeholder type for the particular implementation.
This way we can define a trait that uses some types without needing to know exactly what those types are until the trait is implemented.
We have described most of the advanced features in this chapter as being rarely needed.
Associated types are somewhere in the middle: they are used more rarely than features explained in the rest of the book, but more commonly than many of the other features discussed in this chapter.
One example of trait with an associated type is the `Iterator` trait that the std 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.
Here is the definition of the `Iterator` trait.
```rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::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 `Iterator` trait will specify the concrete type for `Item` and the `next` method will return an `Option` containing a value of that concrete type.
Associated types may seem like a similar concept to generics, in that the latter allow us to define a function without specifying what types it can handle.
To examine the difference between the two, we will look at the implementation of the `Iterator` trait on a type named `Counter` that specifies the `Item` type is `u32`.
```rust
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
```
This syntax seems similar to that of generics.
So why not just define the `Iterator` trait with generics
```rust
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
```
The differences is that when using generics, 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 implementations of `Iterator` for `Counter`.
This could also be said, 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 `next` method on `Counter`, we would have to provide type annotations to 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 the first 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 also 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.
## 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 can specify a default type when declaring a generic type with the `<PlaceholderType=ConcreteType>` syntax.
A good example of a situation where this technique is useful is with *operator overloading*, where you customize the behavior of an operator (such as `+`) in particular situations.
Rust doesn't allow you to create your own operators or overload arbitrary operators.
You can overload the operation and corresponding traits listed in `std::ops` by implementing the traits associated with the operator.
For example here we overload the `+` operator to add two `Point` instances together.
We do this by implementing the `Add` trait on a `Point` struct.
```rust
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}
```
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 associated type named `Output` that determines the type returned from the `add` method.
The default generic type in this code is within the `Add` trait.
Here is its definition
```rust
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
```
This 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 side") defines the type of the `rhs` parameter in the `add` method.
If we didn'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 are implementing `Add` on.
When we implemented `Add` for `Point`, we used the default for `Rhs` because we wanted to add two `Point` instances.
Now lets look at an example of implementing the `Add` trait where we want to customize the `Rhs` type rather than using the default.
Here we want two structs `Millimeters` and `MEters`, which hold values in different units.
This thin wrapping of an existing type in another struct is known as the *newtype pattern*, which will be described 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`.
```rust
use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
```
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`.
You will use default type parameters in two main ways:
- To extend a type without breaking existing code
- To allow customization in specific cases most users won't need
The std library's `Add` trait is an example of the second purpose.
Usually, you will add two like types, but the `Add` trait provides the ability to customize beyond that.
Using a default type parameter in the `Add` trait definition means you don't have to specify the extra parameter most of the time.
A bit of implementation boilerplate isn't needed, making it easier to use the trait.
The first purpose is similar to the second but in reverse.
If you want to add a 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
Nothing in Rust prevents a trait from having a method with the same name as another trait's methods.
Nor does Rust prevent you from implementing both traits on one type.
It is also possible to implement a method directly on the type with the same name as methods form traits.
When calling methods with the same name, you need to specify to Rust which one you want to use.
Consider this code where we have defined two traits, `Pilot` and `Wizard`, that both have a method called `fly`.
Then implement both traits on a type `Human` that already has a method named `fly` implemented on it.
Each `fly` method does something different.
```rust
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
```
When we call `fly` on an instance of `Human`, the compiler defaults to calling the method that is directly implemented on the type.
```rust
fn main() {
let person = Human;
person.fly();
}
```
The output of 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 specific syntax to specify which `fly` method we mean.
Here is a demonstration of this syntax,
```rust
fn main() {
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
```
Specifying the trait name before the method name clarifies to Rust and Us which implementation of `fly` we want to call.
We could also write `Human::fly(&person)` but that us the same as `person.fly()`.
This is also a bit longer to write if we don't need to disambiguate.
Output
```
$ 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*
```
Because `fly` takes a `self` parameter, if we had two *types* that both implement one *trait*, Rust would be able to figure out which implementation of a `trait` to use based on the type of `self`.
Associated function that are not methods do not have a `self` parameter.
When there are multiple types of 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, we here 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`, which we also provide an associated non-method function `baby_name` directly.
```rust
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
}
```
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 `Animal`, which describes characteristics that all animals have.
Baby dogs are called puppies, and that is expressed in the implementation of the `Animal` trait on `Dog` in the `baby_name` function associated with the `Animal` trait.
In `main` we call the `Dog::baby_name` function, which calls the associated function defined on `Dog` directly.
This outputs
```
$ 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 isn't thew output we wanted.
We want to call the `baby_name` function that is part of the `Animal` trait that we implemented on `Dog` so that it prints `A baby dog is called a puppy`.
The technique of specifying the trait name that we used here doesn't help here.
If we changed `main` to the code below, we get a compiler error.
```rust
fn main() {
println!("A baby dog is called a {}", Animal::baby_name());
}
```
Because `Animal::baby_name` doesn't have a `self` parameter and there could be other types implements the `Animal` trait, Rust can't figure out which implementation of `Animal::baby_name` we want.
We get this compiler error
```
$ 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 call associated function of trait
|
help: use the fully-qualified path to the only available implementation
|
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.
Here demonstrates how to use fully qualified syntax
```rust
fn main() {
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
```
Here we provide Rust with a type annotation within the angle brackets.
This indicates we want to call the `baby_name` method from the `Animal` trait as implementation on `Dog` by saying that we want to treat the `Dog` type as an `Animal` for this function call.
Here is the new output
```
$ 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
```
Here us a fully qualified syntax is defined as follows
```
<Type as Trait>::function(receiver_if_method, next_arg, ...);
```
For associated function that aren't methods, there would not be a `receiver`: there would only be the list of other arguments.
You could use fully qualified syntax everywhere that you call functions or methods.
You are allowed to omit any part of this syntax that Rust can figure out from other information in the program.
You only need to use this 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
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.
Lets say we want to make an `OutlinePrint` trait with an `outline_print` method that will print a given value formatted so that it is framed in asterisks.
Given that a `Point` struct that implements the std library trait `Display` to result in `(x, y)`.
When we call `outline_print` on a `Point` instance that has `1` for `x` and `3` for `y`, it should print the following
```
**********
* *
* (1, 3) *
* *
**********
```
The implementation of the `outline_print` method we want to use the `Display` trait's functionality.
Therefore we need to specify that the `OutlinePrint` trait will work only for types that also implement `Display` and provide the functionality that `OutlinePrint` needs.
We can do this in the trait definition by specifying `OutlinePrint: Display`.
This technique is similar to adding a trait bound to the trait.
Here shows an implementation of the `OutlinePrint` trait
```rust
use std::fmt;
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len + 2));
println!("* {output} *");
println!("*{}*", " ".repeat(len + 2));
println!("{}", "*".repeat(len + 4));
}
}
```
Because we specified that `OutlinePrint` requires the `Display` trait.
We can use the `to_string` function that is automatically implemented for any type that implements `Display`.
If we attempted to use `to_string` without adding a color and specifying the `Display` trait after the trait name, we would get an error saying that no method named `to_string` was found for the type `&Self` in the current scope.
Lets see what happens when we try to implement `OutlinePrint` on a type that doesn't implement `Display`, such as the `Point` struct
```rust
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {}
```
We still 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:23
|
20 | impl OutlinePrint for Point {}
| ^^^^^ `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`
--> 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
```
In order to fix this, we implement `Display` on `Point` and satisfy the constraint that `OutlinePrint` requires.
Like this
```rust
use std::fmt;
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
```
Now implementing the `OutlinePrint` trait on `Point` will compile successfully.
We can call `outline_print` on a `Point` instance to display it within an outline of asterisks.
## Using the Newtype Pattern to Implement Traits on External Types
Previously we mentioned the orphan rule that states we are only allowed to implement a trait on a type if either the trait or the type are local to our crate.
It is possible to get around this restriction using the *newtype pattern*.
This involves creating a new type in a tuple struct.
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 wrapper type is elided at compile time.
For 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>`.
Next we can implement `Display` on `Wrapper` and use the `Vec<T>` value.
```rust
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {w}");
}
```
The implementation of `Display` uses `self.0` to access the inner `Vec<T>`.
Because `Wrapper` is a tuple 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` trait on `Wrapper`.
The downside of this technique is that `Wrapper` is a new type, so it doesn't have the methods of the value it is holding.
We would need to implement all the methods of `Vec<T>` directly on `Wrapper` such that the methods delegate to `self.0`, which would allows 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.
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 useful even when traits are not involved.

333
Advanced Types.md Normal file
View File

@ -0,0 +1,333 @@
# Advanced Types
The Rust type system has some features that we have mentioned so far but haven't gone into detail.
To start we will go into the newtypes in general as we examine why newtypes are useful as types.
Then we will go onto type aliases, a feature similar to newtypes but slightly different semantics.
As well we will discuss the `!` and dynamically sized types.
## Using the Newtype Pattern for Type Safety and Abstraction
The newtype pattern are also useful for tasks beyond those discussed already.
This includes statically enforcing that values are never confused and indicating the units of a value.
Before we saw an example of using newtypes to indicate units: 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 tired to call 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 form the API of the private inner type.
Newtypes can also hide internal implementation.
Lets say we could provide a `People` type to wrap a `HashMap<i32, String>` that store a person's ID associated with their name.
Code using `People` would only interact with the public API we provide.
Like a method to add a name string to the `People` collect: this 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 before in [Ch18](./Characteristics%20of%20OO%20Languages.md#encapsulation-that-hides-implementation-details).
## Creating Type Synonyms with Type Aliases
Rust provides the ability to declare a *type alias* to give an existing type another name.
We need to use the `type` keyword to do this.
For example we can create the alias `Kilometers` to `i32` like this.
```rust
type Kilometers = i32;
```
The alias `Kilometers` is a *synonym* for `i32`.
Unlike the `Millimeters` and `Meters` types we created before.
`Kilometers` is not a separate, new type.
Values that have the type `Kilometers` will be treated the same as values of type `i32`.
```rust
type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
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 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.
The main use for type synonyms is to reduce repetition.
As an example, we might have a lengthy type like this.
```rust
Box<dyn Fn() + Send + 'static>
```
Writing this lengthy type function signatures and as type annotations all over the code can be tiresome and error prone.
Just image a project full of code like this.
```rust
let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
// --snip--
}
fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
// --snip--
}
```
A type alias makes this code more manageable by reducing the amount of repetition.
Here we have introduced an alias named `Thunk` for the verbose type and can replace all uses of the type with the shorter alias `Thunk`.
```rust
type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -> Thunk {
// --snip--
}
```
This 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 to be evaluated at a later time, this is an appropriate name for a closure that gets stored.
Type aliases are also commonly used with the `Result<T, E>` type for repetition.
Consider the `std::io` module in the std library.
I/O operations often return a `Result<T, E>` to handle situations when operations fail to work.
This library has a `std::io::Error` struct that represents all possible I/O errors.
Many of the functions in `std::io` will be returning `Result<T, E>` where the `E` is `std::io::Error`, such as these functions in `Write` trait:
```rust
use std::fmt;
use std::io::Error;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
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>;
}
```
The `Result<..., Error>` is repeated a lot.
Therefore `std::io` has this type alias declaration
```rust
type Result<T> = std::result::Result<T, std::io::Error>;
```
Due to this declaration is in the `std::io` module, we can use the fully qualified alias `std::io::Result<T>`.
That is a `Result<T, E>` with the `E` filled in as `std::io::Error`.
The `Write` trait function signatures end up looking like this.
```rust
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
```
This type alias helps in two ways:
- It makes code easier to write.
- *And*
- It gives us a consistent interface across all of `std::io`
Due to it being an alias, it is just another `Result<T, E>`, this 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
Rust has a special type named `!` that is known in type theory lingo as the *empty type* because it has no values.
We prefer to call it the *never type* because it stands in the place of the return type when a function will never return.
Here is an example in use.
```rust
fn bar() -> ! {
// --snip--
}
```
This code should be read as "the function `bar` returns never."
Functions that return never are called *diverging functions*.
We can't create values of the type `!` so `bar` can never possibly return.
What is the use of a type you can never create values for?
Recall the code from Ch2, part of the number guessing game.
Here is a sample of that code
```rust
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
```
Before we skipped over some details about this code.
In ch6 we discussed that `match` arms must all return the same type.
For example this code will not compile.
```rust
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 are we allowed to return a `u32` from one arm and have another arm that ends with `continue`?
`continue` has a `!` value.
That is, when Rust computes the type of `guess`, it looks at both match arms, the former with a value of `u32` and the latter with a `!` value.
Because `!` can never have a value, Rust decides that the type of `guess` is `u32`.
The formal way to describe this behavior is that expressions of type `!` can be coerced into any other type.
We are allowed to end this `match` arm with `continue` because `continue` doesn't return a value.
Instead it moves control back to the top of the loop, so in the `Err` case, we never assign a value to `guess`.
The never type is useful with the `panic!` macro as well.
Remember the `unwrap` function that we call on `Option<T>` values to produce a value or panic with this definition:
```rust
impl<T> Option<T> {
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
```
Here, the same thing happens as in the `match` case form before.
Rust sees 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 will not be returning a value form `unwarp` so this code is valid.
One final expression that has the type `!` is a `loop`.
```rust
print!("forever ");
loop {
print!("and ever ");
}
```
This loop never ends, so `!` is the value of the expression.
However, this wouldn't be true if we included a `break`, because the loop would terminate when it got to the `break`.
## Dynamically Sized Types and the `Sized` Trait
Rust must know certain details about its types, such as how much space to allocate for a value of a particular type.
This leaves one corner of its type system a little confusing at first: the concept of *dynamically sized types*.
Sometimes referred to as *DSTs* or *unsized types*, these types let us write code using values whose size we can know only at runtime.
Lets look into the details of a dynamically sized type called `str`, which we have been using throughout.
This does not include `&str`, but `str` on its own, is a DST.
We can't know how long the string is until runtime, meaning we can't create a variable of type `str`, nor can we make that argument of type `str`.
Consider this code, which will not compile.
```rust
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 type, and all values of a type must use the same amount of memory.
If Rust allowed use to write this code, these two `str` values would need to take up the same amount of memory.
These two have different lengths:
- `s1` needs 12 bytes of storage.
- `s2` needs 15.
This is why it is not possible to create a variable holding a dynamically sized type.
So what should we do?
We should make the types of `s1` and `s2` a `&str` rather than a `str`.
Recall from the "String Slice" section from Ch4, that the slice data structure just stores the starting position and the length of the slice.
Even though 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.
We can know the size of a `&str` value at compile time: it's twice the length of a `usize`.
This means we always know the size of a `&str`, no matter how long the string it refers to is.
Generally this is the way in which dynamically sized types are used in Rust, they have an extra but 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 we have 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 Ch18 in ["Using Trait Objects That Allow for Values of Different Types"](./Characteristics%20of%20OO%20Languages.md#encapsulation-that-hides-implementation-details), we mentioned that to use trait as trait objects, we must put them behind a pointer, such as `&dyn Trait` or `Box<dyn Trait>` (`Rc<dyn Trait>` would work as well).
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 for everything whose size is known at compile time.
Additionally Rust implicitly adds a bound on `Sized` to every generic function.
That is, a generic function definition like this:
```rust
fn generic<T>(t: T) {
// --snip--
}
```
This is actually treated as though we had written this:
```rust
fn generic<T: Sized>(t: T) {
// --snip--
}
```
By default, generic functions will work only on types that have a known size at compile time.
However, you can use the following special syntax to relax this restriction.
```rust
fn generic<T: ?Sized>(t: &T) {
// --snip--
}
```
A trait bound on `?Sized` means "`T` may or may not be `Sized`".
This notation overrides the default that generic types must have a known size at compile time.
The `?Trait` syntax with this meaning is only available for `Sized`, not any other traits.
Note that we switched the type of the `t` parameter from `T` to `&T`.
Because the type might not be `Sized`, we need to use it behind some kind of pointer.
Here we have chosen to use a reference.