almost finished with ch10.3

This commit is contained in:
darkicewolf50 2025-02-04 17:22:50 -07:00
parent 9f29d30aec
commit 89f9705f62

View File

@ -318,3 +318,127 @@ Rust will not let you create a dangling reference
The fix for this would be to transfer ownership out of the function
## Lifetime Annotations in Struct Definitions
Structs can also hold references
When it holds a reference it needs a lifetime annotation on every reference in the struct's def
Here is an example where the struct holds a single string
```rust
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let i = ImportantExcerpt {
part: first_sentence,
};
}
```
Notice that the lifetime annotation is the same as a function's signature
The lifetime goes in a `<>` after the name of the structure
This annotation means that an instance of the struct can't outlive the reference
## Lifetime Elision
In this case the function does not have a lifetime annotation and it compiles
```rust
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
```
The reason why this function compiles without lifetime annotations is due to previous versions of Rust
In earlier versions of Rust (pre-1.0), this code wouldn't have compiled because every reference needed an explicit lifetime
At that time a function signature would have been written like this
```rust
fn first_word<'a>(s: &'a str) -> &'a str {
```
After writing a lot of Rust code, the Rust development team found that Rust programmers were writing a lot of the same code
These patterns were predictable and followed a few deterministic patterns. So the development team allowed the compiler and borrow checker to infer the lifetime annotation by using these common lifetime annotation patterns
Its important to know that it is possible that more deterministic patterns will emerge and be added to the compiler.
In the future even fewer lifetime annotations might be required
The patterns programmed into Rust's lifetime analysis of references are called the *lifetime elision rules*
These aren't rules for programmers, they are a set of particular cases that the compiler will consider. If your code fits these cases you don't have two explicitly define the lifetimes.
The elision rules don't provide full inference.
If there is still ambiguity as to what lifetimes are after the compiler applies the rules, the compiler will give an error instead of guessing
Instead of guessing the compiler will give you an error that you can resolve by adding the lifetime annotations
Lifetimes on a function or method parameters are called input lifetimes, and lifetimes on return values are called *output lifetimes*
The compiler uses three rules to figure out the lifetimes of the references when there aren't explicit annotations
The first rule applies to input lifetimes and the second and third rules apply to output lifetimes
If the complier gets to the end of these three rules and still cant figure it out then the compiler will stop with an error
These rules apply to `fn` definitions and `impl` blocks
The first rule is that the compiler assigns a lifetime parameter to each parameter that is a reference.
Each parameter gets its own separate lifetime annotation that has no relationship to any other lifetime (`fn foo<'a, 'b>(x: &'a i32, y: &'b i32`)
The second rule is that if there is exactly one input parameter, that lifetime is assigned to all output parameters (`fn foo<'a>(x: &'a i32) -> &'a i32`)
The third rule is that there are multiple input lifetime parameters, but one of them is `&self` or `&mut self`. Due to it being a method, the lifetime of `self` is assigned to all output lifetime parameters
This rule makes methods much cleaner to read and write because fewer symbols are necessary
## Lifetime Annotations in Method Definitions
When we implement methods on struct with lifetimes, we use the same syntax as generic type parameters.
Where we declare and use the lifetime parameters depends on whether they are related to the struct fields or the method parameters and return values
In method signatures inside the `impl` block, references might be tied to the lifetime of references in the strut's fields or they might be independent
The lifetime elision rules often make it so that lifetime annotations aren't necessary in method signatures
Lets look at some examples using the struct `ImportantExcerpt`
First the method named `level` whose only parameter is a reference to `self` and whose return value is an `i32`, which is not a reference to anything
```rust
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
```
The lifetime parameter declaration after `impl` and its use after the type name are required but we're not required to annotate the lifetime of the reference to `self` because of the first elision rule
Here is an example where the third elision rule is applicable
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {announcement}");
self.part
}
}
```
There are two input lifetimes, so Rust applies the first lifetime elision rule and give both `&self` and `announcement` their own lifetimes.
Then because, because one of the parameters is `&self` the return type gets the lifetime of `&self` and all lifetimes have been dealt with
## The Static Lifetime