mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-16 05:24:17 -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
|
The fix for this would be to transfer ownership out of the function
|
||||||
|
|
||||||
## Lifetime Annotations in Struct Definitions
|
## 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