From 9f29d30aec44fdf52eaff1143412cc80859cc68c Mon Sep 17 00:00:00 2001 From: darkicewolf50 Date: Tue, 4 Feb 2025 12:12:39 -0700 Subject: [PATCH] halfway through ch10.3 --- .obsidian/workspace.json | 10 +-- Lifetimes.md | 145 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 5 deletions(-) diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index e912c19..4614abd 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -4,11 +4,11 @@ "type": "split", "children": [ { - "id": "9cf638edb21842e3", + "id": "64904eb93f53e8e0", "type": "tabs", "children": [ { - "id": "3025ec3e871a2841", + "id": "caf0233e624d6c1c", "type": "leaf", "state": { "type": "markdown", @@ -164,8 +164,10 @@ "command-palette:Open command palette": false } }, - "active": "3025ec3e871a2841", + "active": "caf0233e624d6c1c", "lastOpenFiles": [ + "2025-02-04.md", + "Lifetimes.md", "Generic Types Traits and Lifetimes.md", "data_types.md", "Collection of Common Data Structs.md", @@ -190,10 +192,8 @@ "Variables.md", "Vector.md", "Reducing.md", - "crates.io.md", "does_not_compile.svg", "Untitled.canvas", - "Packages and Crates.md", "Good and Bad Code/Commenting Pratices", "Good and Bad Code" ] diff --git a/Lifetimes.md b/Lifetimes.md index 1206af2..ca6e266 100644 --- a/Lifetimes.md +++ b/Lifetimes.md @@ -173,3 +173,148 @@ Here is an example to an `i32` reference One annotation by itself has no meaning because cannot be a relationship to another (or multiple) reference(s) ## Lifetime Annotations in Function Signatures +To use lifetime annotations in a function's signature you must first declare the generic lifetime parameter inside `<>` between the function name and the parameter list (just like generics) + +In this case the constraint we have is that we want the returned reference to be valid as long as both parameters are valid + +This will be the relationship between the lifetime of the parameters and the return reference. + +Here is this implemented with the name `'a` as the lifetime, it is added to each reference in the function signature +```rust +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} +``` +This code should compile now with the use of lifetime annotations + +The function signature now tells Rust that for some lifetime `'a`, the function takes two parameters, both of which are string slices that live at least as long as lifetime `'a` + +The signature also tells Rust that the string slice returned from the function will live at least as long as life `'a`. + +What this means is that the smaller of the lifetime returned by the `longest` function is the same as the smaller of the lifetime values referred to by the function arguments + +These relationships are what we want Rust to use when analyzing this code + +Lifetime annotations go in the signature NOT the body of the function + +Lifetime annotations are part of the contract of the function, like types in the signature + +This makes the lifetime analysis easier on the Rust compiler + +If there is a problem in the signature it makes it easier to identify and express in an error as well, and give clear solutions + + + Here is another example of the use of the `longest` function +```rust +fn main() { + let string1 = String::from("long string is long"); + + { + let string2 = String::from("xyz"); + let result = longest(string1.as_str(), string2.as_str()); + println!("The longest string is {result}"); + } +} +``` +In this example the lifetime of the return value of the `longest` function is the same as `string2` which means that after the inner scope or `{}` the reference is no longer valid + +This is both due to `string2`, `result` (which stores the reference) and the function signature states that it only lives as long as the shortest function + +The value reference in `result` is `long string is long` and the program will print `The longest string is long string is long` + +In this example the program will not compile +```rust +fn main() { + let string1 = String::from("long string is long"); + let result; + { + let string2 = String::from("xyz"); + result = longest(string1.as_str(), string2.as_str()); + } + println!("The longest string is {result}"); +} +``` +This is due to `string2` not living long enough for the reference in `result` to be used while in a valid state + +Here is the error it would provide +``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) +error[E0597]: `string2` does not live long enough + --> src/main.rs:6:44 + | +5 | let string2 = String::from("xyz"); + | ------- binding `string2` declared here +6 | result = longest(string1.as_str(), string2.as_str()); + | ^^^^^^^ borrowed value does not live long enough +7 | } + | - `string2` dropped here while still borrowed +8 | println!("The longest string is {result}"); + | -------- borrow later used here + +For more information about this error, try `rustc --explain E0597`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error +``` + +This also states that `string2` would need to be valid until the `print!` macro. Hence why it states that it doesn't live long enough + +Even though the reference in this case is to `string1` the compiler and the function signature states that the lifetime of the return value is the same as the shortest lifetime + +## Thinking in Terms of Lifetimes +The way that you need to specify lifetime parameters depends on what your function is doing + +For example if this was your function +```rust +fn longest<'a>(x: &'a str, y: &str) -> &'a str { + x +} +``` +This function's return reference's lifetime is the same as `x` so the returned reference only lives as long as what is passed into `x` + +The return reference lifetime has no relations to the `y` lifetime + +Any lifetime MUST have another relationship to another reference + +Any lifetime in a function signature's return value MUST relate to AT LEAST one parameter + +If it doesn't it would create a dangling reference, this is because the value would go out of scope at the end of the function + +Here is an example of `longest` that creates both a dangling reference and an invalid lifetime +```rust +fn longest<'a>(x: &str, y: &str) -> &'a str { + let result = String::from("really long string"); + result.as_str() +} +``` + +Here is the error +``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) +error[E0515]: cannot return value referencing local variable `result` + --> src/main.rs:11:5 + | +11 | result.as_str() + | ------^^^^^^^^^ + | | + | returns a value referencing data owned by the current function + | `result` is borrowed here + +For more information about this error, try `rustc --explain E0515`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error +``` +The problem is that `result` goes out of scope and gets cleaned up at the end of the function. + +We also try to return a reference at the end of the function to `result` + +There is no way we can specify lifetime parameters that would change the dangling reference in this case + +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