finished ch9

This commit is contained in:
darkicewolf50 2025-01-27 23:38:20 +00:00
parent 67f7477663
commit 2767435f47

View File

@ -620,4 +620,80 @@ Its advisable to have your code panic when its possible to get ino a bad state
A *bad state* is when some assumption, guarantee, contract, or invariant has been broken such as when invalid values, contradictory values, or missing values are passed to your code plus one or more of the following:
- The bad state is somthing that is unexpected which is not something that is a likely problem (a user entering data in the wrong format)
- The code needs to rely on not being in a bad state, rather than checking at every step
- The code needs to rely on not being in a bad state, rather than checking at every step
- There is not a good way to encode this info in the types you use
If someoneelse calls your code and passes in bad values or values that dont make sense, its best to return an error if you can, so that the lbirary's user can decide what they want to do in that case
In some cases where continuing could be insecure or harmful, the best choice might bbe to call a `panic!` and alert the library user to the bug in thier code so that they can ifx it during development
`panic!` is so often appropriate if you are calling external cod that is out of your control and it retunrs an invaild state that you have no way of fixing
If a failure is expected then it is more appropriate to return a `Result` than to make a `panic` call
Examples of this include a parser being being malformed data or an HTTP requst retunring a status that indicates ou have hit a rate limit.
In these caes it would be more appropriate to return a `Result` indicating that a failure is an expected possiblity that the calling code must decide how to handle.
When your code performs an opertation that could put a user at risk if is called using invalid values, your code should verify the values first then paic if the values aren't valid
This is mostly for safety reasons, attempting to operate on invalid data that could expose your code to vulnerabilities
This would be the main reason the std library will call `panic!` if you attempt an out-of-bounds memory access
Trying to access memory that doesnt belong to the current data struct, this is a common security problem.
Functions often have *contract*, their behavior is only guaranteed if the inputs meet particlar requirements.
Panicking when the cotnract is violated makes sense because a contract violation always indicates a caller-side bug and its not a kind of error you want the calling code to have to handle explicitly.
In this case there is no reasonable way for the calling code to reacover or fix the code becasue the programmer needs to dix the code.
Contracts for a fnction, especially when a violation will cause a panic, this should be explained in the API docs for the function.
Having lots of error chcks in all of your functions would be verbos and annoying.
Instead you can use Rust's type system and thus the compiler to do many of the checks for you.
If your function has a particular tpe as a parameter, you can proceed with your code's logic knowing that the compiler will ensure that you have a vaild value
For example, using u32 instead of i32 enusres you will never have a negative number, or you cant pass a `Option` into a function with a conrete tpye, the code won't even compile becuase it cant take in an `Option`
## Creating Custom Types for Validation
This is useful for enchancing or guiding the user toward valid data, and have different behavior when a user give an almost invalid data
Having too many checks can impact performance if you had many many functions with a requirement that is the same.
Instead you can make a new type and put the validations in a function to create an instance of the tpye rather than needing to repeat the validations everywhere
This makes it more safe for functions to use the new type in their signatures and confidently use the values they recieve.
Here is a way to implement this kind of type for the original guessing game from ch2
```rust
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
pub fn value(&self) -> i32 {
self.value
}
}
```
The guess struct can only hold a i32 value that is private
In its associated functions there is a `new` function or a contructor that first checks if the value is between 1-100 and reutrns a new instance of the function if it is valid otherwise it calls a `panic!`
It can take in an `i32` but it still calls a `panic!` if it is out of range, the panic is ued to alert the progammer who is writing thr calling ocde that they have a bug to fix becuae calling a value outside of the range would violate the contact that `Guess::new` depends on.
The conditions in which `Guess:new` might panic should be told in its public-facing API docs.
A function that has a parameter or returns only numbers between 1 and 100 could declarre in its signature that it takes in or returns a `Guess` rather than an `i32` and you wouldnt need to addtionaly checks in its body