Lifetime is one of the Rust features that the compiler uses to ensure memory safety. The lifetime specifies the minimum duration an object must live to be used safely. Let's try to do something that is allowed in certain programming languages, but is actually an error to do so:
fn get_element_inc(elements: &[i32], index: usize) -> &i32 { let element = elements[index] + 1; &element }
Here, we try to return a reference from a stack-allocated value. The problem is that this value will be deallocated when the function returns and the caller will try to access this deallocated value. In other programming languages, this code will compile fine and produce (hopefully) a segmentation fault at runtime. But Rust is a safe programming language and refuses to compile such code:
error[E0597]: `element` does not live long enough --> src/main.rs:3:6 | 3 | &element | ^^^^^^^ does not live long enough 4 | } | - borrowed value only lives until here
The compile noticed that the value element will be deallocated at the end of the function; that's what the sentence on the last line means. This is right, because the lifetime of element starts from its declaration until the end of the scope where it is declared; here, the scope is the function. Here's an illustration of the lifetime of element:
But how does the compiler know what the required lifetime is for the returned value? To answer this question, let's add the lifetime annotations that were added by the compiler:
fn get_element_inc<'a>(elements: &'a [i32], index: usize) -> &'a i32 { let element = elements[index] + 1; &element }
As you can see, the syntax for lifetimes is the same as the one used for labels—'label. When we want to specify the lifetimes, we need to declare the lifetime names between angle brackets, in a similar way to how we declare generic types. In this case, we specified that the lifetime of the returned value must be the same as the one from the parameter elements.
Let's annotate the code again with lifetimes:
Here, we clearly see that the lifetime of the returned value is smaller than the required one; that's why the compiler rejected our code.
In this case, there are two ways to fix this code (without changing the signature). One way to get a value that satisfies the lifetime 'a is to get a reference to a value of the same lifetime; the parameter elements also has the lifetime 'a , so we can write the following code:
fn get_element<'a>(elements: &'a [i32], index: usize) -> &'a i32 { &elements[index] }
Another way is to return a reference to a value of lifetime 'static. This special lifetime is equal to the duration of the program, that is, the value must live until the end of the program. One way to get such a lifetime is to use a literal:
fn get_element<'a>(elements: &'a [i32], index: usize) -> &'a i32 { &42 }
The lifetime 'static satisfies the constraint 'a because 'static lives longer than the latter.
In both of these examples, the lifetime annotations were not required. We didn't have to specify the lifetime in the first place, thanks to a feature called lifetime elision; the compiler can infer what the required lifetimes are in most cases by following these simple rules:
- A different lifetime parameter is assigned to each parameter
- If there's only one parameter that needs a lifetime, that lifetime is assigned to every lifetime in the return value (as for our get_element function)
- If there are multiple parameters that need a lifetime, but one of them is for &self, the lifetime for self is assigned to every lifetime in the return value
Let's go back to the method signature:
fn connect_clicked<F: Fn(&Self) + 'static>(&self, f: F) -> u64
Here, we notice that the parameter f has the 'static lifetime. We now know that this means that this parameter must live until the end of the program. That's why we cannot use the normal version of the closure: because the lifetime of self is not 'static , meaning the app will get deallocated when the main function ends. To make this work, we cloned the play_button variable:
let play_button = self.toolbar.play_button.clone();
Now we can use this new variable in the closure.
Note: Take note that cloning a GTK+ widget is really cheap; only a pointer is cloned.
However, trying to do the following will still result in a compilation error:
let play_button = self.toolbar.play_button.clone(); self.toolbar.play_button.connect_clicked(|_| { if play_button.get_stock_id() == Some(PLAY_STOCK.to_string()) { play_button.set_stock_id(PAUSE_STOCK); } else { play_button.set_stock_id(PLAY_STOCK); } });
Here's the error:
error[E0373]: closure may outlive the current function, but it borrows `play_button`, which is owned by the current function --> src/toolbar.rs:80:50 | 80 | self.toolbar.play_button.connect_clicked(|_| { | ^^^ may outlive borrowed value `play_button` 81 | if play_button.get_stock_id() == Some(PLAY_STOCK.to_string()) { | ----------- `play_button` is borrowed here | help: to force the closure to take ownership of `play_button` (and any other referenced variables), use the `move` keyword | 80 | self.toolbar.play_button.connect_clicked(move |_| { | ^^^^^^^^
The problem with this code is that the closure can (and will) be called after the function returns, but the variable button is declared in the method connect_toolbar_events() and will be deallocated when it returns. Again, Rust prevents us from having a segmentation fault by checking if we correctly use references. The compiler talks about ownership; let's look at what that is.