Advance lifetime
Trait Bounds
Just like generic types can be bounded, lifetimes can also be bounded as below:
T: 'a,all references inTmust outlive the lifetime'aT: Trait + 'a:Tmust implement traitTraitand all references inTmust outlive'a
Example
1. use std::fmt::Debug; // Trait to bound with.
3. #[derive(Debug)]
4. struct Ref<'a, T: 'a>(&'a T);
5. // `Ref` contains a reference to a generic type `T` that has
6. // an unknown lifetime `'a`. `T` is bounded such that any
7. // *references* in `T` must outlive `'a`. Additionally, the lifetime
8. // of `Ref` may not exceed `'a`.
10. // A generic function which prints using the `Debug` trait.
11. fn print<T>(t: T) where
12. T: Debug {
13. println!("`print`: t is {:?}", t);
14. }
16. // Here a reference to `T` is taken where `T` implements
17. // `Debug` and all *references* in `T` outlive `'a`. In
18. // addition, `'a` must outlive the function.
19. fn print_ref<'a, T>(t: &'a T) where
20. T: Debug + 'a {
21. println!("`print_ref`: t is {:?}", t);
22. }
24. fn main() {
25. let x = 7;
26. let ref_x = Ref(&x);
28. print_ref(&ref_x);
29. print(ref_x);
30. }
- 🌟
1. /* Annotate struct with lifetime:
2. 1. `r` and `s` must have different lifetimes
3. 2. lifetime of `s` is bigger than that of 'r'
4. */
5. struct DoubleRef<T> {
6. r: &T,
7. s: &T
8. }
9. fn main() {
10. println!("Success!")
11. }
- 🌟🌟
1. /* Adding trait bounds to make it work */
2. struct ImportantExcerpt<'a> {
3. part: &'a str,
4. }
6. impl<'a, 'b> ImportantExcerpt<'a> {
7. fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str {
8. println!("Attention please: {}", announcement);
9. self.part
10. }
11. }
13. fn main() {
14. println!("Success!")
15. }
- 🌟🌟
1. /* Adding trait bounds to make it work */
2. fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) {
3. y = x;
4. let r: &'b &'a i32 = &&0;
5. }
7. fn main() {
8. println!("Success!")
9. }
HRTB(Higher-ranked trait bounds)
Type bounds may be higher ranked over lifetimes. These bounds specify a bound is true for all lifetimes. For example, a bound such as for<'a> &'a T: PartialEq<i32> would require an implementation like:
1. #![allow(unused)]
2. fn main() {
3. impl<'a> PartialEq<i32> for &'a T {
4. // ...
5. }
6. }
and could then be used to compare a &'a T with any lifetime to an i32.
Only a higher-ranked bound can be used here, because the lifetime of the reference is shorter than any possible lifetime parameter on the function.
- 🌟🌟🌟
1. /* Adding HRTB to make it work!*/
2. fn call_on_ref_zero<'a, F>(f: F) where F: Fn(&'a i32) {
3. let zero = 0;
4. f(&zero);
5. }
7. fn main() {
8. println!("Success!");
9. }
NLL (Non-Lexical Lifetime)
Before explaining NLL, let’s see some code first:
1. fn main() {
2. let mut s = String::from("hello");
4. let r1 = &s;
5. let r2 = &s;
6. println!("{} and {}", r1, r2);
8. let r3 = &mut s;
9. println!("{}", r3);
10. }
Based on our current knowledge, this code will cause en error due to violating the borrowing rules in Rust.
But if you cargo run it, then everything will be ok, so what’s going on here?
The ability of the compiler to tell that a reference is no longer used at a point before the end of the scope, is called Non-Lexical Lifetimes (NLL for short).
With this ability the compiler knows when is the last time that a reference is used and optimizing the borrowing rules based on this knowledge.
1. #![allow(unused)]
2. fn main() {
3. let mut u = 0i32;
4. let mut v = 1i32;
5. let mut w = 2i32;
7. // lifetime of `a` = α ∪ β ∪ γ
8. let mut a = &mut u; // --+ α. lifetime of `&mut u` --+ lexical "lifetime" of `&mut u`,`&mut u`, `&mut w` and `a`
9. use(a); // | |
10. *a = 3; // <-----------------+ |
11. ... // |
12. a = &mut v; // --+ β. lifetime of `&mut v` |
13. use(a); // | |
14. *a = 4; // <-----------------+ |
15. ... // |
16. a = &mut w; // --+ γ. lifetime of `&mut w` |
17. use(a); // | |
18. *a = 5; // <-----------------+ <--------------------------+
19. }
Reborrow
After learning NLL, we can easily understand reborrow now.
Example
1. #[derive(Debug)]
2. struct Point {
3. x: i32,
4. y: i32,
5. }
7. impl Point {
8. fn move_to(&mut self, x: i32, y: i32) {
9. self.x = x;
10. self.y = y;
11. }
12. }
14. fn main() {
15. let mut p = Point { x: 0, y: 0 };
16. let r = &mut p;
17. // Here comes the reborrow
18. let rr: &Point = &*r;
20. println!("{:?}", rr); // Reborrow ends here, NLL introduced
22. // Reborrow is over, we can continue using `r` now
23. r.move_to(10, 10);
24. println!("{:?}", r);
25. }
- 🌟🌟
1. /* Make it work by reordering some code */
2. fn main() {
3. let mut data = 10;
4. let ref1 = &mut data;
5. let ref2 = &mut *ref1;
7. *ref1 += 1;
8. *ref2 += 2;
10. println!("{}", data);
11. }
Unbound lifetime
See more info in Nomicon - Unbounded Lifetimes.
More elision rules
1. #![allow(unused)]
2. fn main() {
3. impl<'a> Reader for BufReader<'a> {
4. // 'a is not used in the following methods
5. }
7. // can be written as :
8. impl Reader for BufReader<'_> {
10. }
11. }
1. #![allow(unused)]
2. fn main() {
3. // Rust 2015
4. struct Ref<'a, T: 'a> {
5. field: &'a T
6. }
8. // Rust 2018
9. struct Ref<'a, T> {
10. field: &'a T
11. }
12. }
A difficult exercise
- 🌟🌟🌟🌟
1. /* Make it work */
2. struct Interface<'a> {
3. manager: &'a mut Manager<'a>
4. }
6. impl<'a> Interface<'a> {
7. pub fn noop(self) {
8. println!("interface consumed");
9. }
10. }
12. struct Manager<'a> {
13. text: &'a str
14. }
16. struct List<'a> {
17. manager: Manager<'a>,
18. }
20. impl<'a> List<'a> {
21. pub fn get_interface(&'a mut self) -> Interface {
22. Interface {
23. manager: &mut self.manager
24. }
25. }
26. }
28. fn main() {
29. let mut list = List {
30. manager: Manager {
31. text: "hello"
32. }
33. };
35. list.get_interface().noop();
37. println!("Interface should be dropped here and the borrow released");
39. use_list(&list);
40. }
42. fn use_list(list: &List) {
43. println!("{}", list.manager.text);
44. }
You can find the solutions here(under the solutions path), but only use it when you need it :)
