diff --git a/Lifetimes.md b/Lifetimes.md index ca6e266..b47878e 100644 --- a/Lifetimes.md +++ b/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