Rewrite generics

pull/67/head
Dhghomon 4 years ago committed by GitHub
parent 7a32cb8f84
commit 141dc7c970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3260,11 +3260,11 @@ fn main() {
}
```
But maybe you also want to input an `i8`, or a `u32`, and so on. You can use generics for this. Generics means "maybe one type, maybe another type".
But what if you want to take more than just `i32`? You can use generics for this. Generics means "maybe one type, maybe another type".
For generics, you use angle brackets with the type inside: `<T>` This means "any type you put into the function". Usually, generics uses types with one capital letter (T, U, V, etc.).
For generics, you use angle brackets with the type inside, like this: `<T>` This means "any type you put into the function". Usually, generics uses types with one capital letter (T, U, V, etc.), though you don't have to just use one letter.
This is how you change the function:
This is how you change the function to make it generic:
```rust
fn return_number<T>(number: T) -> T {
@ -3279,7 +3279,7 @@ fn main() {
The important part is the `<T>` after the function name. Without this, Rust will think that T is a concrete (concrete = not generic) type, like `String` or `i8`.
This is easier to understand if we write out a type name:
This is easier to understand if we write out a type name. See what happens when we change `T` to `MyType`:
```rust
fn return_number(number: MyType) -> MyType { // ⚠️
@ -3301,9 +3301,11 @@ fn main() {
}
```
So the single letter `T` is for human eyes, but the part after the function name is for the compiler's "eyes". Without it, it's not generic.
Now we will go back to type `T`, because Rust code usually uses `T`.
You will remember that some types in Rust are **Copy**, some are **Clone**, some are **Display**, some are **Debug**, and so on. With **Debug**, we can print with `{:?}`. So now you can see that this is a problem:
You will remember that some types in Rust are **Copy**, some are **Clone**, some are **Display**, some are **Debug**, and so on. With **Debug**, we can print with `{:?}`. So now you can see that we have a problem if we want to print `T`:
```rust
fn print_number<T>(number: T) {
@ -3315,7 +3317,7 @@ fn main() {
}
```
`print_number` needs **Debug** to print `number`, but is `T` a type with `Debug`? Maybe not. The compiler doesn't know, so it gives an error:
`print_number` needs **Debug** to print `number`, but is `T` a type with `Debug`? Maybe not. Maybe it doesn't have `#[derive(Debug)]`, who knows. The compiler doesn't know either, so it gives an error:
```text
error[E0277]: `T` doesn't implement `std::fmt::Debug`
@ -3325,13 +3327,12 @@ error[E0277]: `T` doesn't implement `std::fmt::Debug`
| ^^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
```
T doesn't implement **Debug**. So do we implement Debug for T? No, because we don't know what T is. But we can tell the function: "only accept types that have Debug".
T doesn't implement **Debug**. So do we implement Debug for T? No, because we don't know what T is. But we can tell the function: "Don't worry, because any type T for this function will have Debug".
```rust
use std::fmt::Debug; // Debug is located at std::fmt::Debug. If we write this,
// then now we can just write 'Debug'.
use std::fmt::Debug; // Debug is located at std::fmt::Debug. So now we can just write 'Debug'.
fn print_number<T: Debug>(number: T) {
fn print_number<T: Debug>(number: T) { // <T: Debug> is the important part
println!("Here is your number: {:?}", number);
}
@ -3340,9 +3341,9 @@ fn main() {
}
```
So now the compiler knows: "Okay, I will only take a type if it has Debug". Now the code works, because `i32` is Debug. Now we can give it many types: `String`, `&str`, and so on.
So now the compiler knows: "Okay, this type T is going to have Debug". Now the code works, because `i32` has Debug. Now we can give it many types: `String`, `&str`, and so on, because they all have Debug.
Now we can create a struct and give it Debug, and now we can print it too. Our function can take `i32`, the struct Animal, and more:
Now we can create a struct and give it Debug with #[derive(Debug)], so now we can print it too. Our function can take `i32`, the struct Animal, and more:
```rust
use std::fmt::Debug;
@ -3377,9 +3378,9 @@ Here is your item: Animal { name: "Charlie", age: 1 }
Here is your item: 55
```
Sometimes we need more than one type in a generic function. We have to write out each type name, and think about how we want to use it. In this example, we want two types. First we want to print a statement for type T. Printing with `{}` is nicer, so we will require Display for T.
Sometimes we need more than one type in a generic function. We have to write out each type name, and think about how we want to use it. In this example, we want two types. First we want to print a statement for type T. Printing with `{}` is nicer, so we will require `Display` for `T`.
Next is type U, and the two variables `num_1` and `num_2` have type U (U is some sort of number). We want to compare them, so we need PartialOrd. We want to print them too, so we require Display for U as well.
Next is type U, and the two variables `num_1` and `num_2` have type U (U is some sort of number). We want to compare them, so we need `PartialOrd`. That trait lets us use things like `<`, `>`, `==`, and so on. We want to print them too, so we require `Display` for `U` as well.
```rust
use std::fmt::Display;
@ -3394,6 +3395,8 @@ fn main() {
}
```
This prints `Listen up!! Is 9 greater than 8? true`.
So `fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, num_1: U, num_2: U)` says:
- The function name is `compare_and_display`,
@ -3402,7 +3405,7 @@ So `fn compare_and_display<T: Display, U: Display + PartialOrd>(statement: T, nu
Now we can give `compare_and_display` different types. `statement` can be a `String`, a `&str`, anything with Display.
To make generic functions easier to read, we can also write it like this:
To make generic functions easier to read, we can also write it like this with `where` right before the code block:
```rust
use std::cmp::PartialOrd;
@ -3439,11 +3442,18 @@ fn say_two<T: Display, U: Display>(statement_1: T, statement_2: U) { // Type T n
fn main() {
say_two("Hello there!", String::from("I hate sand.")); // Type T is a &str. Type U is a String.
say_two("Hello there!", String::from("I hate sand.")); // Type T is a &str, but type U is a String.
say_two(String::from("Where is Padme?"), String::from("Is she all right?")); // Both types are String.
}
```
This prints:
```text
I have two things to say: Hello there! and I hate sand.
I have two things to say: Where is Padme? and Is she all right?
```
## Option and Result
We understand enums and generics now, so we can understand `Option` and `Result`. Rust uses these two enums to make code safer. We will start with Option.

Loading…
Cancel
Save