diff --git a/en/src/SUMMARY.md b/en/src/SUMMARY.md index 3852591..1ddb95f 100644 --- a/en/src/SUMMARY.md +++ b/en/src/SUMMARY.md @@ -52,7 +52,7 @@ - [Lifetime](lifetime/intro.md) - [basic](lifetime/basic.md) - [&'static and T: 'static](lifetime/static.md) - - [advance TODO](lifetime/advance.md) + - [advanced](lifetime/advance.md) - [Functional programing TODO](functional-programing/intro.md) - [Closure](functional-programing/cloure.md) - [Iterator](functional-programing/iterator.md) diff --git a/en/src/lifetime/advance.md b/en/src/lifetime/advance.md index cba2b30..41c9464 100644 --- a/en/src/lifetime/advance.md +++ b/en/src/lifetime/advance.md @@ -1,3 +1,284 @@ -# advance +# advanced lifetime -aaaa \ No newline at end of file +## Trait Bounds +Just like generic types can be bounded, lifetimes can also be bounded as below: +- `T: 'a`,all references in `T` must outlive the lifetime `'a` +- `T: Trait + 'a`: `T` must implement trait `Trait` and all references in `T` must outlive `'a` + +**Example** +```rust,editable +use std::fmt::Debug; // Trait to bound with. + +#[derive(Debug)] +struct Ref<'a, T: 'a>(&'a T); +// `Ref` contains a reference to a generic type `T` that has +// an unknown lifetime `'a`. `T` is bounded such that any +// *references* in `T` must outlive `'a`. Additionally, the lifetime +// of `Ref` may not exceed `'a`. + +// A generic function which prints using the `Debug` trait. +fn print(t: T) where + T: Debug { + println!("`print`: t is {:?}", t); +} + +// Here a reference to `T` is taken where `T` implements +// `Debug` and all *references* in `T` outlive `'a`. In +// addition, `'a` must outlive the function. +fn print_ref<'a, T>(t: &'a T) where + T: Debug + 'a { + println!("`print_ref`: t is {:?}", t); +} + +fn main() { + let x = 7; + let ref_x = Ref(&x); + + print_ref(&ref_x); + print(ref_x); +} +``` + +1γ€πŸŒŸ +```rust,editable +/* Annotate struct with lifetime: +1. `r` and `s` must has different lifetimes +2. lifetime of `s` is bigger than that of 'r' +*/ +struct DoubleRef { + r: &T, + s: &T +} +fn main() { + println!("Success!") +} +``` + + +2γ€πŸŒŸπŸŒŸ +```rust,editable +/* Adding trait bounds to make it work */ +struct ImportantExcerpt<'a> { + part: &'a str, +} + +impl<'a, 'b> ImportantExcerpt<'a> { + fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str { + println!("Attention please: {}", announcement); + self.part + } +} + +fn main() { + println!("Success!") +} +``` + +3γ€πŸŒŸπŸŒŸ +```rust,editable +/* Adding trait bounds to make it work */ +fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) { + y = x; + let r: &'b &'a i32 = &&0; +} + +fn main() { + println!("Success!") +} +``` + +## 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` would require an implementation like: + +```rust +impl<'a> PartialEq for &'a T { + // ... +} +``` + +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。 + +4γ€πŸŒŸπŸŒŸπŸŒŸ +```rust +/* Adding HRTB to make it work!*/ +fn call_on_ref_zero<'a, F>(f: F) where F: Fn(&'a i32) { + let zero = 0; + f(&zero); +} + +fn main() { + println!("Success!") +} +``` +## NLL (Non-Lexical Lifetime) +Before explaining NLL, let's see some code first: +```rust +fn main() { + let mut s = String::from("hello"); + + let r1 = &s; + let r2 = &s; + println!("{} and {}", r1, r2); + + let r3 = &mut s; + println!("{}", r3); +} +``` + +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. + +```rust +let mut u = 0i32; +let mut v = 1i32; +let mut w = 2i32; + +// lifetime of `a` = Ξ± βˆͺ Ξ² βˆͺ Ξ³ +let mut a = &mut u; // --+ Ξ±. lifetime of `&mut u` --+ lexical "lifetime" of `&mut u`,`&mut u`, `&mut w` and `a` +use(a); // | | +*a = 3; // <-----------------+ | +... // | +a = &mut v; // --+ Ξ². lifetime of `&mut v` | +use(a); // | | +*a = 4; // <-----------------+ | +... // | +a = &mut w; // --+ Ξ³. lifetime of `&mut w` | +use(a); // | | +*a = 5; // <-----------------+ <--------------------------+ +``` + +## Reborrow +After learning NLL, we can easily understand reborrow now. + +**Example** +```rust +#[derive(Debug)] +struct Point { + x: i32, + y: i32, +} + +impl Point { + fn move_to(&mut self, x: i32, y: i32) { + self.x = x; + self.y = y; + } +} + +fn main() { + let mut p = Point { x: 0, y: 0 }; + let r = &mut p; + // Here comes the reborrow + let rr: &Point = &*r; + + println!("{:?}", rr); // Reborrow ends here, NLL introduced + + // Reborrow is over, we can continue using `r` now + r.move_to(10, 10); + println!("{:?}", r); +} +``` + + +5γ€πŸŒŸπŸŒŸ +```rust,editable +/* Make it work by reordering some code */ +fn main() { + let mut data = 10; + let ref1 = &mut data; + let ref2 = &mut *ref1; + + *ref1 += 1; + *ref2 += 2; + + println!("{}", data); +} +``` + + +## Unbound lifetime +See more info in [Nomicon - Unbounded Lifetimes](https://doc.rust-lang.org/nomicon/unbounded-lifetimes.html). + + +## More elision rules + +```rust +impl<'a> Reader for BufReader<'a> { + // 'a is not used in the following methods +} + +// can be writting as : +impl Reader for BufReader<'_> { + +} +``` + +```rust +// Rust 2015 +struct Ref<'a, T: 'a> { + field: &'a T +} + +// Rust 2018 +struct Ref<'a, T> { + field: &'a T +} +``` + + +## A difficult exercise + +6γ€πŸŒŸπŸŒŸπŸŒŸπŸŒŸ +```rust +/* Make it work */ +struct Interface<'a> { + manager: &'a mut Manager<'a> +} + +impl<'a> Interface<'a> { + pub fn noop(self) { + println!("interface consumed"); + } +} + +struct Manager<'a> { + text: &'a str +} + +struct List<'a> { + manager: Manager<'a>, +} + +impl<'a> List<'a> { + pub fn get_interface(&'a mut self) -> Interface { + Interface { + manager: &mut self.manager + } + } +} + +fn main() { + let mut list = List { + manager: Manager { + text: "hello" + } + }; + + list.get_interface().noop(); + + println!("Interface should be dropped here and the borrow released"); + + use_list(&list); +} + +fn use_list(list: &List) { + println!("{}", list.manager.text); +} +``` \ No newline at end of file diff --git a/en/src/lifetime/basic.md b/en/src/lifetime/basic.md index b693c72..1c5104e 100644 --- a/en/src/lifetime/basic.md +++ b/en/src/lifetime/basic.md @@ -270,7 +270,7 @@ fn main() let no_copy = NoCopyType {}; let example = Example { a: &1, b: &no_copy }; fix_me(&example); - print!("Success!") + println!("Success!") } ``` diff --git a/solutions/lifetime/advance.md b/solutions/lifetime/advance.md new file mode 100644 index 0000000..55cfad7 --- /dev/null +++ b/solutions/lifetime/advance.md @@ -0,0 +1,126 @@ +1、 +```rust +struct DoubleRef<'a,'b:'a, T> { + r: &'a T, + s: &'b T +} +fn main() { + println!("Success!") +} +``` + + +2、 +```rust +struct ImportantExcerpt<'a> { + part: &'a str, +} + +impl<'a: 'b, 'b> ImportantExcerpt<'a> { + fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str { + println!("Attention please: {}", announcement); + self.part + } +} + +fn main() { + println!("Success!") +} +``` + +3、 +```rust +fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b { + y = x; // &'a i32 is a subtype of &'b i32 because 'a: 'b + let r: &'b &'a i32 = &&0; // &'b &'a i32 is well formed because 'a: 'b +} +fn main() { + println!("Success!") +} +``` + + +4、 +```rust +fn call_on_ref_zero(f: F) where for<'a> F: Fn(&'a i32) { + let zero = 0; + f(&zero); +} + +fn main() { + println!("Success!") +} +``` + +Higher-ranked lifetimes may also be specified just before the trait: the only difference is the scope of the lifetime parameter, which extends only to the end of the following trait instead of the whole bound. This function is equivalent to the last one. + +```rust +fn call_on_ref_zero(f: F) where F: for<'a> Fn(&'a i32) { + let zero = 0; + f(&zero); +} +``` + +5、 +```rust +fn main() { + let mut data = 10; + let ref1 = &mut data; + let ref2 = &mut *ref1; + + *ref2 += 2; + *ref1 += 1; + + println!("{}", data); +} +``` + + +6、 +```rust +struct Interface<'b, 'a: 'b> { + manager: &'b mut Manager<'a> +} + +impl<'b, 'a: 'b> Interface<'b, 'a> { + pub fn noop(self) { + println!("interface consumed"); + } +} + +struct Manager<'a> { + text: &'a str +} + +struct List<'a> { + manager: Manager<'a>, +} + +impl<'a> List<'a> { + pub fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a> + where 'a: 'b { + Interface { + manager: &mut self.manager + } + } +} + +fn main() { + + let mut list = List { + manager: Manager { + text: "hello" + } + }; + + list.get_interface().noop(); + + println!("Interface should be dropped here and the borrow released"); + + use_list(&list); +} + +fn use_list(list: &List) { + println!("{}", list.manager.text); +} +``` \ No newline at end of file