started ch10.3

This commit is contained in:
darkicewolf50 2025-02-03 17:54:20 -07:00
parent 792606e001
commit 724ea9a5fe
3 changed files with 187 additions and 6 deletions

View File

@ -26,5 +26,6 @@
"workspaces": false, "workspaces": false,
"file-recovery": true, "file-recovery": true,
"publish": false, "publish": false,
"sync": false "sync": false,
"webviewer": false
} }

View File

@ -11,10 +11,14 @@
"id": "3025ec3e871a2841", "id": "3025ec3e871a2841",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "empty", "type": "markdown",
"state": {}, "state": {
"file": "Lifetimes.md",
"mode": "source",
"source": false
},
"icon": "lucide-file", "icon": "lucide-file",
"title": "New tab" "title": "Lifetimes"
} }
} }
] ]
@ -36,7 +40,8 @@
"state": { "state": {
"type": "file-explorer", "type": "file-explorer",
"state": { "state": {
"sortOrder": "alphabetical" "sortOrder": "alphabetical",
"autoReveal": false
}, },
"icon": "lucide-folder-closed", "icon": "lucide-folder-closed",
"title": "Files" "title": "Files"
@ -161,6 +166,7 @@
}, },
"active": "3025ec3e871a2841", "active": "3025ec3e871a2841",
"lastOpenFiles": [ "lastOpenFiles": [
"Generic Types Traits and Lifetimes.md",
"data_types.md", "data_types.md",
"Collection of Common Data Structs.md", "Collection of Common Data Structs.md",
"Constants.md", "Constants.md",
@ -168,7 +174,6 @@
"Data Types.md", "Data Types.md",
"Enums.md", "Enums.md",
"Error Handling.md", "Error Handling.md",
"Generic Types Traits and Lifetimes.md",
"Generics.md", "Generics.md",
"Hash.md", "Hash.md",
"Modules and Use.md", "Modules and Use.md",

175
Lifetimes.md Normal file
View File

@ -0,0 +1,175 @@
# Lifetimes
## Valid References with Lifetimes
Lifetimes are another type of generic generic that has been used in the past.
Rather than ensuring that a type has the behavior we want lifetimes ensure that references are valid for as long as we need them to be.
Every reference in Rust has a *lifetime*, which is the scope that a reference is valid
A majority of the time references are implicit and inferred, like how types are inferred for the most part
We must only annotate types when multiple types are possible
The same concept can happen with lifetimes, where references could be related in a few different ways
Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime are valid
Annotating lifetimes is not a concept or feature that is in other programming languages
## Preventing Dangling References with Lifetimes
The main aim of lifetimes is to prevent *dangling references*, this causes a program to reference data other than the data it's intended to reference
Here is an example that will not compile due to the value being referred to going out of scope, then the reference being invalid and being used elsewhere
```rust
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {r}");
}
```
This is initialization is allowed because if yo try to use a variable with no value associated then Rust will throw a compile-time error.
This proves that rust does not allow or have a null type
in this code `r` has a reference to `x` but `x` goes out of scope, this makes `r` not be valid reference, this invalid reference is attempted to be used.
The error message that would occur afterwards would say that `x` "does not live long enough". This refers to `x` going out of scope but `r` is still in scope
Hence we say that `r` "lives longer" because it has a larger scope
## The Borrow Checker
The Rust compiler has a *borrow checker* that compares scopes to determine whether all borrows are valid
Here is an example with annotations showing the lifetimes of variables
```rust
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {r}"); // |
} // ---------+
```
In this annotations `r` is represented by `'a` and `x` is represented by `'b`
`'a` is the lifetime of `r` and `'b` is the lifetime of `x`
As you can see `'a` encompasses more than `'b`
At compile time Rust will compare the size of the two lifetimes and sees that `r` has a lifetime of `'a` but that it refers to memory with a lifetime of `'b`. This results in a rejection of the program because the reference becomes invalid before a use of it: the subject reference doesn't live as long as the reference.
The lifetime refers to how long it "lives" in memory or is available in memory
Here is the fix to the program above
```rust
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {r}"); // | |
// --+ |
} // ----------+
```
As you can now see the the lifetime of `'b` is now larger than the `'a` reference
Know you know that the reference `r` will always be valid because `x` is always valid
## Generic Lifetimes in Functions
To illustrate this we will write a function that returns the longer of two string slices
This function takes in two string slices and returns a single string slice
Here is an example of the use of the function `longest` that does this functionality
```rust
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {result}");
}
```
Note that `longest` takes in a string slice, which are references, we don't want the function to take ownership of the strings
If we try to implement `longest` function like below, it won't compile
```rust
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
```
Here is the compiler error
```
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
--> src/main.rs:9:33
|
9 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ++++ ++ ++ ++
For more information about this error, try `rustc --explain E0106`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
```
This error highlights that the return type needs a generic lifetime parameter because rust cannot determine whether the reference is referring to `x` or `y`
We don't know either because of the `if` and `else` in the body of the function refer to two different things depending on what the inputs are
We also don't know the concrete lifetimes of the references , so we cannot even analyze the lifetimes like we did before, so we cant ensure that the reference and lifetime will always be valid.
The borrow checker cannot check for this either so it throws an error.
This is because it doesn't know the lifetimes of `x` and `y` relate to the lifetime of the return value
To fix this error we should add a generic lifetime parameter that defines the relationship between the references so the borrow checker can perform its analysis
## Lifetime Annotation Syntax
Lifetime annotations don't change based on how long any of the references live, instead they describe the relationship of the lifetimes of multiple references to each other without affecting the lifetimes
Like how functions can accept any type when the signature specifies a generic, functions can accept any lifetime by specifying a generic lifetime parameter
Lifetime annotations have a unusual syntax: the naming of a lifetime parameter must start with an apostrophe `'` and are usually all lowercase and very short, just like generics
Most people use `'a` as the first lifetime annotation
The placement of the lifetime goes after the `&` reference using a space to separate the lifetime from the reference's type
Here is an example to an `i32` reference
```rust
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
```
One annotation by itself has no meaning because cannot be a relationship to another (or multiple) reference(s)
## Lifetime Annotations in Function Signatures