{
Make hypotheses about whether or not your experiments will pass the borrow checker before you compile
reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We must annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways.
The main aim of lifetimes is to prevent dangling references, which cause a program to reference data other than the data it’s intended to reference.
}
All references in Rust have a lifetime, even if they are not explicitly annotated. The compiler is capable of implicitly assigning lifetimes.
A value’s lifetime is the period when accessing that value is valid behavior. A function’s local variables live until the function returns, while global variables might live for the life of the program.
the notion of ownership is rather limited. An owner cleans up when its values’ lifetimes end.
lifetime=timetolive=subset of their scope
<'a, 'b> declares two lifetime variables, 'a and 'b, within the scope of
j: &'b i32 binds the lifetime variable 'b to the lifetime of j. The syntax reads as “parameter j is a reference to an i32 with lifetime b.”
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
{
'a in generic func means: function will live at least as long as lifetime 'a
e.g. Note that the longest function doesn’t need to know exactly how long x and y will live, only that some scope can be substituted for 'a that will satisfy this signature.
}
{
•Using two lifetime parameters (a and b) indicates that the lifetimes of i and j are decoupled.
fn add_with_lifetimes<'a, 'b>(i: &'a i32, j: &'b i32) -> i32 {}
}
{
Lifetime annotations don’t change how long any of the references live. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter.
Lifetime annotations describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
The lifetime annotations indicate that the references first and second must both live as long as that generic lifetime.
Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.
}
lifetime=timetolive=subset of their scope
<'a, 'b> declares two lifetime variables, 'a and 'b, within the scope of
j: &'b i32 binds the lifetime variable 'b to the lifetime of j. The syntax reads as “parameter j is a reference to an i32 with lifetime b.”
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
{
let result = longest(string1.as_str(), string2);
fun longest<'a>(x: &'a str, y: &'a str) -> &'a str {}
When we pass concrete references to longest, the concrete lifetime that is substituted for 'a is the part of the scope of x that overlaps with the scope of y. In other words, the generic lifetime 'a will get the concrete lifetime that is equal to the smaller of the lifetimes of x and y. Because we’ve annotated the returned reference with the same lifetime parameter 'a, the returned reference will also be valid for the length of the smaller of the lifetimes of x and y.
}
{
•Using two lifetime parameters (a and b) indicates that the lifetimes of i and j are decoupled.
fn add_with_lifetimes<'a, 'b>(i: &'a i32, j: &'b i32) -> i32 {}
}
{
{lifetime of that usage:
the LOC('existence time' or Line of code) between when a location is first used in a certain way, and when that usage stops.
lifetime of that value:
the LOC (or actual time) between when a value is created, and when that value is dropped.
}
might be useful when discussing open file descriptors, but also irrelevant here.
}
{
let r: &'c S = &c;
the 'c part, like a type, also guards what is allowed into r.
}
{
Ultimately, lifetime syntax is about connecting the lifetimes of various parameters and return values of functions. Once they’re connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety.
}
e.g = rust•armanazi•borrowchecker•lifetime•struct
. The borrow checker checks that all access to data is legal, which allows Rust to prevent safety issues. Learning how this works will, at the very least, speed up your development time by helping you avoid run-ins with the compiler. More significantly though, learning to work with the borrow checker allows you to build larger software systems with confidence.
It underpins the term fearless concurrency.
Ownership is a stretched metaphor. There is no relationship to property rights. Within Rust, ownership relates to cleaning values when these are no longer needed. For example, when a function returns, the memory holding its local variables needs to be freed. Owners cannot prevent other parts of the program from accessing their values or report data theft to some overarching Rust authority.
Ownership is a term used within the Rust community to refer to the compile-time process that checks that every use of a value is valid and that every value is destroyed cleanly.
The ownership system can trip you up if you don’t understand what’s happening. This is particularly the case when you bring the programming style from your past experience to a new paradigm. Four general strategies can help with ownership issues:
Use references where full ownership is not required.
Duplicate the value.
Refactor code to reduce the number of long-lived objects.
Wrap your data in a type designed to assist with movement issues.
To borrow a value means to access it. This terminology is somewhat confusing as there is no obligation to return the value to its owner. Its meaning is used to emphasize that while values can have a single owner, it’s possible for many parts of the program to share access to those values.
Lifetime names for struct fields always need to be declared after the impl keyword and then used after the struct’s name, because those lifetimes are part of the struct’s type.
lifetime annotations aren’t necessary in method signatures.
In method signatures inside the impl block, references might be tied to the lifetime of references in the struct’s fields, or they might be independent. In addition, the lifetime elision rules often make it so that lifetime annotations aren’t necessary in method signatures
e.g:
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 gives both &self and announcement their own lifetimes. Then, because one of the parameters is &self, the return type gets the lifetime of &self, and all lifetimes have been accounted for.*
struct ImportantExcerpt<'a> {
part: &'a str,
}
*/
//no_err_func
fn first_word(s: &str) -> &str {
fn first_word<'a>(s: &'a str) -> &str {
fn first_word<'a>(s: &'a str) -> &'a str {
fn longest(x: &str, y: &str) -> &str {
//err_func
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
//no_err_method
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
let s: &'static str = "I have a static lifetime.";
The text of this string is stored directly in the program’s binary, which is always available. Therefore, the lifetime of all string literals is 'static.
You might see suggestions to use the 'static lifetime in error messages. But before specifying 'static as the lifetime for a reference, think about whether the reference you have actually lives the entire lifetime of your program or not. You might consider whether you want it to live that long, even if it could. Most of the time, the problem results from attempting to create a dangling reference or a mismatch of the available lifetimes. In such cases, the solution is fixing those problems, not specifying the 'static lifetime.