mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
finished ch10.1
This commit is contained in:
parent
d87328a5fc
commit
b1bef1f2f6
@ -9,7 +9,7 @@ choosing the right one is a skill that is developed over time
|
||||
## Common Collections
|
||||
- [*Vector*](Vector.md) - allows for storing a variable number of values next to each other
|
||||
- [*String*](String.md) - a collection of characters
|
||||
- [*Hash Map*](Hash Map.md) - allows you to associate a value with a specific key
|
||||
- [*Hash Map*](Hash.md) - allows you to associate a value with a specific key
|
||||
- Its particular implementation is a more general version of a general data struct called a map
|
||||
- {
|
||||
- 1: data,
|
||||
|
@ -8,4 +8,14 @@ One of these tools is *generics*
|
||||
|
||||
They are abstract stand-ins for concrete types or other properties
|
||||
|
||||
We can express the behavior of generics or how they relate to other generics without knowing what type that will be in plcae when copiling and running the code.
|
||||
We can express the behavior of generics or how they relate to other generics without knowing what type that will be in place when copiling and running the code.
|
||||
|
||||
Functions can either take parameters of some generic type or a concrete type (like a `i32` or `String`) in the same way they take parameters with unknwon values to run the same code on multiple concrete values
|
||||
|
||||
One examle where concrete values were already used was with `Option<T>`, `Vec<T>`, `HashMap<K, V>` and `Result<T, E>`
|
||||
|
||||
This chapter covers 3 things
|
||||
1. [Reducing Code duplication](Reducing Code Duplication.md)
|
||||
1. [Generics](Generics.md)
|
||||
1. [Traits](Traits.md)
|
||||
1. [Lifetimes](Lifetimes.md)
|
303
Generics.md
Normal file
303
Generics.md
Normal file
@ -0,0 +1,303 @@
|
||||
# Generic Data Types
|
||||
These are used to create definitions for items like function signatures or sturctures, where it is then used with many different concrete data types
|
||||
|
||||
## In Function Definitions
|
||||
|
||||
When defining a function that uses generics, yo place the generics in the signature of the funct.
|
||||
|
||||
This is usually in the place where concrete data types are specified (the parameters and return type)
|
||||
|
||||
This makes code more flexible and provide more functionality whilst preventing code duplication
|
||||
|
||||
Continuing with the finding the largest num in a `i32` vector slice and a `char` vector slice
|
||||
|
||||
```rust
|
||||
fn largest_i32(list: &[i32]) -> &i32 {
|
||||
let mut largest = &list[0];
|
||||
|
||||
for item in list {
|
||||
if item > largest {
|
||||
largest = item;
|
||||
}
|
||||
}
|
||||
|
||||
largest
|
||||
}
|
||||
|
||||
fn largest_char(list: &[char]) -> &char {
|
||||
let mut largest = &list[0];
|
||||
|
||||
for item in list {
|
||||
if item > largest {
|
||||
largest = item;
|
||||
}
|
||||
}
|
||||
|
||||
largest
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number_list = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let result = largest_i32(&number_list);
|
||||
println!("The largest number is {result}");
|
||||
|
||||
let char_list = vec!['y', 'm', 'a', 'q'];
|
||||
|
||||
let result = largest_char(&char_list);
|
||||
println!("The largest char is {result}");
|
||||
}
|
||||
```
|
||||
|
||||
Here is the code before the use of generics
|
||||
|
||||
Notice the repeated code for a both types of slices
|
||||
|
||||
To parameterize the types in a signle function, the first thing is to name the type parameter (just like with concrete types)
|
||||
|
||||
You can use any identifier as a type parameter name but it is common to use `T`
|
||||
|
||||
Type parameter names in Rust are short, often just one letter and Rust's type-maning convention is UpperCamelCase
|
||||
|
||||
The `T` is the default choice of most Rust programmers
|
||||
|
||||
To use a generic it needs to be declared before it can be used
|
||||
|
||||
To delcare is place it inside `<>` and between the name of the function and the parameter list
|
||||
|
||||
Example
|
||||
```rust
|
||||
fn larget<T>(list: &[T]) -> &T {}
|
||||
```
|
||||
|
||||
This function is read as: the function `largest` is generic over some type `T`. This function has one parameter named `list`, which is a slice of values of type `T`. The `largest` function will return a reference to a value fo the same type `T`.
|
||||
|
||||
Here is the updated, **but this code will not compile**
|
||||
```rust
|
||||
fn largest<T>(list: &[T]) -> &T {
|
||||
let mut largest = &list[0];
|
||||
|
||||
for item in list {
|
||||
if item > largest {
|
||||
largest = item;
|
||||
}
|
||||
}
|
||||
|
||||
largest
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number_list = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let result = largest(&number_list);
|
||||
println!("The largest number is {result}");
|
||||
|
||||
let char_list = vec!['y', 'm', 'a', 'q'];
|
||||
|
||||
let result = largest(&char_list);
|
||||
println!("The largest char is {result}");
|
||||
}
|
||||
```
|
||||
Here is the compilation error you will get
|
||||
```
|
||||
$ cargo run
|
||||
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
|
||||
error[E0369]: binary operation `>` cannot be applied to type `&T`
|
||||
--> src/main.rs:5:17
|
||||
|
|
||||
5 | if item > largest {
|
||||
| ---- ^ ------- &T
|
||||
| |
|
||||
| &T
|
||||
|
|
||||
help: consider restricting type parameter `T`
|
||||
|
|
||||
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
|
||||
| ++++++++++++++++++++++
|
||||
|
||||
For more information about this error, try `rustc --explain E0369`.
|
||||
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
|
||||
```
|
||||
This mentions ```std::cmp::PartialOrd```, thisis a *trait*.
|
||||
|
||||
For now know that this error states that the body of largest won't work for all possible types that `T` could be, becasue you cant compair against all possible values that `T` could be
|
||||
|
||||
To enable comparisions, the std library has the `std::cmp::PartialOrd` tait that you can implement on types.
|
||||
|
||||
By following the help text's usggestion you can restrict the types vaild for `T` to only those that implement `PartialOrd`, then this example will compile, becuase of the restriction that the std library implements `PartialOrd` on both `i32` and `char`
|
||||
|
||||
## In Struct Definitions
|
||||
This can also be in a structure the generic also has to be defined in `<>` with the `T` being inside
|
||||
|
||||
Here is an example
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Point { x: 5, y: 10 };
|
||||
let float = Point { x: 1.0, y: 4.0 };
|
||||
}
|
||||
```
|
||||
This is similar to function definitions, after the generic is defined it can be used in the body
|
||||
|
||||
Note the use of only one generic type, this forces the use of only one concrete type when used in the entirity of the body
|
||||
|
||||
Here is a disallowed initialization
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let wont_work = Point { x: 5, y: 4.0 };
|
||||
}
|
||||
```
|
||||
This is not allowed bacuse you are trying to assign both an `i32` and a folating point integer, the struct definition does not allow this basue to only accpets one type per instance
|
||||
|
||||
You could use two generics in the definition
|
||||
|
||||
Here is an example where the use above would be allowed
|
||||
```rust
|
||||
struct Point<T, U> {
|
||||
x: T,
|
||||
y: U,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let both_integer = Point { x: 5, y: 10 };
|
||||
let both_float = Point { x: 1.0, y: 4.0 };
|
||||
let integer_and_float = Point { x: 5, y: 4.0 };
|
||||
}
|
||||
```
|
||||
Notice that `x` and `y` can now both be different concrete types.
|
||||
|
||||
You can use as many generic type parameters in a definition as wanted but more than a few makes it hard to read the code.
|
||||
|
||||
If you need lots of generic tpyes in your code, it indicates that yor code need restructing into smaller pieces
|
||||
|
||||
## In Enum Definitions
|
||||
Just like structs, it can also be used in enums.
|
||||
|
||||
An example that was used in the past was `Option<T>`
|
||||
|
||||
Here is an example in the form of the ``Result` enum that was unsed before sa well
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
```
|
||||
|
||||
The `Result` enum has two generics which typically hold the sucess value and an error type
|
||||
|
||||
If you recognize situations in your code with multiple struct or enum definitions that differ only in the types of the values they hold, thne you can avoid duplication by using generic types
|
||||
|
||||
## In Method Definitions
|
||||
You can implement generics in the associate methods of structs as well
|
||||
|
||||
Here is an example that uses the example form the Structures Section
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
fn x(&self) -> &T {
|
||||
&self.x
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p = Point { x: 5, y: 10 };
|
||||
|
||||
println!("p.x = {}", p.x());
|
||||
}
|
||||
```
|
||||
|
||||
Not hat you have to delcare `T` right after `impl` so that we can use `T` to specify that we are implementing meothds on tpye `Point<T>`
|
||||
|
||||
By declaring `T` as a generic type after `impl`, Rust can identify that the type in the angle brackets in `Point` is a generic type rather than a concerete type.
|
||||
|
||||
You can choose a differnet nmae fo the generic than the original definition in the struct, using the same name is the standard
|
||||
|
||||
Methods written in that `impl` that delcares the generic will also be definined in any method in that `impl` scope no matter what concrete type is used in the definition
|
||||
|
||||
We can also specifiy constraints on generic types when deining methods on the type
|
||||
|
||||
For example lets say you have methods that you only want on `Point<f32>` instances rather than on `Point<T>` instances with a generic type
|
||||
|
||||
Here is an example of this
|
||||
```rust
|
||||
impl Point<f32> {
|
||||
fn distance_from_origin(&self) -> f32 {
|
||||
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ths code means the tpye `Point<f32>` will have a `distance_from_origin` method and other methods of `Point<T>` that is not a `f32`
|
||||
|
||||
This method measures how far way the point is away from the coordinates (0.0, 0.0) and uses operations that are only available for floating-point types
|
||||
|
||||
Generic type parameters in a stuct def arent always the same as those you use in you in the same struct's methods signatures
|
||||
```rust
|
||||
struct Point<X1, Y1> {
|
||||
x: X1,
|
||||
y: Y1,
|
||||
}
|
||||
|
||||
impl<X1, Y1> Point<X1, Y1> {
|
||||
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
|
||||
Point {
|
||||
x: self.x,
|
||||
y: other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Point { x: 5, y: 10.4 };
|
||||
let p2 = Point { x: "Hello", y: 'c' };
|
||||
|
||||
let p3 = p1.mixup(p2);
|
||||
|
||||
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
|
||||
}
|
||||
```
|
||||
This is used for deep copies where you dont know if the generic is the same as the original struct
|
||||
|
||||
This is an ample to show a situation where is is possible and used
|
||||
|
||||
It also shows how in the case that you need it
|
||||
|
||||
## Perfomance of Code Using Generics
|
||||
There is no cost at runtime for uing generics
|
||||
|
||||
Rust accomplishes this by performing monomorphization of the code using generics at compile time
|
||||
|
||||
*Monomorphization* is the proccess of turing generic code into specific code by filling in the concrete types that are used when complied
|
||||
|
||||
The compiler does the opposite of the steps we used o create the generic function
|
||||
|
||||
Here is an illustration of what the complier would do, just with different names
|
||||
```rust
|
||||
enum Option_i32 {
|
||||
Some(i32),
|
||||
None,
|
||||
}
|
||||
|
||||
enum Option_f64 {
|
||||
Some(f64),
|
||||
None,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Option_i32::Some(5);
|
||||
let float = Option_f64::Some(5.0);
|
||||
}
|
||||
```
|
81
Reducing Code Duplication.md
Normal file
81
Reducing Code Duplication.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Removing Duplcation by Extracting a Function
|
||||
Generics allow us to replace specific tpyes with a placeholder that represents multiple values
|
||||
|
||||
Lets look at how to remove duplication in a way that doesnt invlove generic types by extracting a function that replaces specific values with a generics.
|
||||
|
||||
By being able to recognize duplicated code you can extract into a funct, you can start to recognize duplicated code that use generics
|
||||
|
||||
Lets look at some code that finds the largest number in a list
|
||||
```rust
|
||||
fn main() {
|
||||
let number_list = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let mut largest = &number_list[0];
|
||||
|
||||
for number in &number_list {
|
||||
if number > largest {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {largest}");
|
||||
|
||||
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
|
||||
|
||||
let mut largest = &number_list[0];
|
||||
|
||||
for number in &number_list {
|
||||
if number > largest {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {largest}");
|
||||
}
|
||||
```
|
||||
|
||||
Although it owrks, duplicated code is tedious and error prone, also you have to update the code in multiple places when we want to change it
|
||||
|
||||
To eliminate duplication you can create an abstraction by defining a function that operates on any list of integers passed in as a parameter
|
||||
|
||||
This makes our code clearer
|
||||
|
||||
Here is the same code by extracted into a function that finds the largest number in a `i32` list
|
||||
|
||||
```rust
|
||||
fn largest(list: &[i32]) -> &i32 {
|
||||
let mut largest = &list[0];
|
||||
|
||||
for item in list {
|
||||
if item > largest {
|
||||
largest = item;
|
||||
}
|
||||
}
|
||||
|
||||
largest
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number_list = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let result = largest(&number_list);
|
||||
println!("The largest number is {result}");
|
||||
|
||||
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
|
||||
|
||||
let result = largest(&number_list);
|
||||
println!("The largest number is {result}");
|
||||
}
|
||||
```
|
||||
|
||||
The parameter of the `largest` function called `list` represents any concrete slice of `i32` values that might be passed into the funct
|
||||
|
||||
Here are the steps taken to change the code from the original to the second with the abstracted function:
|
||||
1. Identify duplicate code
|
||||
1. Extract the duplicated code into a function, and specify the inputs and return values of that code in the funct signature
|
||||
1. Update the two instances of dplicated code to make a call to the funct instead
|
||||
|
||||
We can use these smae steps with generics to reduce code duplication, in the same way the fnction body can operate on an abstract `list` instead of specific values
|
||||
|
||||
Generics allow code to operate on abstract types
|
||||
|
Loading…
x
Reference in New Issue
Block a user