mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
almost finished with ch10.3
This commit is contained in:
parent
9f29d30aec
commit
89f9705f62
124
Lifetimes.md
124
Lifetimes.md
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user