From 9a1c8d7b4f170a34cc7d2d6a7c798fcd57cdfae7 Mon Sep 17 00:00:00 2001 From: sunface Date: Sat, 9 Apr 2022 19:12:47 +0800 Subject: [PATCH] add Zh Chapter: Lifetime-basic --- ChangeLog.md | 2 + zh-CN/src/SUMMARY.md | 2 +- zh-CN/src/lifetime/basic.md | 343 ++++++++++++++++++++++++++++++++++-- zh-CN/src/lifetime/intro.md | 5 +- 4 files changed, 334 insertions(+), 18 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 54ff41d..e5fbdad 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ ## 2022-04-09 - Add [Zh - Formating] +- Add [Zh - Lifetime-Basic] + ## 2022-03-31 diff --git a/zh-CN/src/SUMMARY.md b/zh-CN/src/SUMMARY.md index 7c3ecc3..815c040 100644 --- a/zh-CN/src/SUMMARY.md +++ b/zh-CN/src/SUMMARY.md @@ -45,7 +45,7 @@ - [模块 Module](crate-module/module.md) - [使用use引入模块及受限可见性](crate-module/use-pub.md) - [注释和文档](comments-docs.md) -- [格式化输出 todo](formatted-output.md) +- [格式化输出](formatted-output.md) - [生命周期 todo](lifetime/intro.md) - [生命周期基础](lifetime/basic.md) - [&'static 和 T: 'static](lifetime/static.md) diff --git a/zh-CN/src/lifetime/basic.md b/zh-CN/src/lifetime/basic.md index 40439f4..1ad41a2 100644 --- a/zh-CN/src/lifetime/basic.md +++ b/zh-CN/src/lifetime/basic.md @@ -1,26 +1,337 @@ -## 生命周期消除 +## 生命周期基础 +编译器通过生命周期来确保所有的借用都是合法的,典型的,一个变量在创建时生命周期随之开始,销毁时生命周期也随之结束。 + + +## 生命周期的范围 +1、 🌟 +```rust,editable +/* 为 `i` 和 `borrow2` 标注合适的生命周期范围 */ + + +// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。 +// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠 +fn main() { + let i = 3; + { + let borrow1 = &i; // `borrow1` 生命周期开始. ──┐ + // │ + println!("borrow1: {}", borrow1); // │ + } // `borrow1` 生命周期结束. ──────────────────────────────────┘ + { + let borrow2 = &i; + + println!("borrow2: {}", borrow2); + } +} +``` + +2. 🌟🌟 + +**示例** ```rust -fn print(s: &str); // elided -fn print<'a>(s: &'a str); // expanded +{ + let x = 5; // ----------+-- 'b + // | + let r = &x; // --+-- 'a | + // | | + println!("r: {}", r); // | | + // --+ | +} // ----------+ +``` -fn debug(lvl: usize, s: &str); // elided -fn debug<'a>(lvl: usize, s: &'a str); // expanded -fn substr(s: &str, until: usize) -> &str; // elided -fn substr<'a>(s: &'a str, until: usize) -> &'a str; // expanded +```rust,editable +/* 像上面的示例一样,为 `r` 和 `x` 标准生命周期,然后从生命周期的角度. */ -fn get_str() -> &str; // ILLEGAL +fn main() { + { + let r; // ---------+-- 'a + // | + { // | + let x = 5; // -+-- 'b | + r = &x; // | | + } // -+ | + // | + println!("r: {}", r); // | + } // ---------+ +} +``` -fn frob(s: &str, t: &str) -> &str; // ILLEGAL +## 生命周期标注 +Rust 的借用检查器使用显式的生命周期标注来确定一个引用的合法范围。但是对于用户来说,我们在大多数场景下,都无需手动去标注生命周期,原因是编译器会在某些情况下自动应用生命周期消除规则。 -fn get_mut(&mut self) -> &mut T; // elided -fn get_mut<'a>(&'a mut self) -> &'a mut T; // expanded +在了解编译器使用哪些规则帮我们消除生命周期之前,首先还是需要知道该如何手动标记生命周期。 -fn args(&mut self, args: &[T]) -> &mut Command // elided -fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // expanded -fn new(buf: &mut [u8]) -> BufWriter; // elided -fn new(buf: &mut [u8]) -> BufWriter<'_>; // elided (with `rust_2018_idioms`) -fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // expanded +#### 函数 +**大家先忽略生命周期消除规则**,让我们看看,函数签名中的生命周期有哪些限制: + +- 需要为每个引用标注上合适的生命周期 +- 返回值中的引用,它的生命周期要么跟某个引用参数相同,要么是 `'statc` + +**示例** +```rust,editable +// 引用参数中的生命周期 'a 至少要跟函数活得一样久 +fn print_one<'a>(x: &'a i32) { + println!("`print_one`: x is {}", x); +} + +// 可变引用依然需要标准生命周期 +fn add_one<'a>(x: &'a mut i32) { + *x += 1; +} + +// 下面代码中,每个参数都拥有自己独立的生命周期,事实上,这个例子足够简单,因此它们应该被标记上相同的生命周期 `'a`,但是对于复杂的例子而言,独立的生命周期标注是可能存在的 +fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) { + println!("`print_multi`: x is {}, y is {}", x, y); +} + +// 返回一个通过参数传入的引用是很常见的,但是这种情况下需要标注上正确的生命周期 +fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x } + +fn main() { + let x = 7; + let y = 9; + + print_one(&x); + print_multi(&x, &y); + + let z = pass_x(&x, &y); + print_one(z); + + let mut t = 3; + add_one(&mut t); + print_one(&t); +} +``` + +3、 🌟 +```rust,editable +/* 添加合适的生命周期标注,让下面的代码工作 */ +fn longest(x: &str, y: &str) -> &str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() {} +``` +4、🌟🌟🌟 +```rust,editable +/* 使用三种方法修复下面的错误 */ +fn invalid_output<'a>() -> &'a String { + &String::from("foo") +} + +fn main() { +} +``` + +5、🌟🌟 +```rust,editable +// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久 +fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) { + println!("x is {} and y is {}", x, y); +} + +/* 让下面的代码工作 */ +fn failed_borrow<'a>() { + let _x = 12; + + // ERROR: `_x` 活得不够久does not live long enough + let y: &'a i32 = &_x; + + // 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小 + // 你不能将一个小的生命周期强转成大的 +} + +fn main() { + let (four, nine) = (4, 9); + + + print_refs(&four, &nine); + // 这里,four 和 nice 的生命周期必须要比函数 print_refs 长 + + failed_borrow(); + // `failed_borrow` 没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static` +} +``` + +#### Structs +6、 🌟 +```rust,editable +/* 增加合适的生命周期标准,让代码工作 */ + +// `i32` 的引用必须比 `Borrowed` 活得更久 +#[derive(Debug)] +struct Borrowed(&i32); + +// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久 +#[derive(Debug)] +struct NamedBorrowed { + x: &i32, + y: &i32, +} + +#[derive(Debug)] +enum Either { + Num(i32), + Ref(&i32), +} + +fn main() { + let x = 18; + let y = 15; + + let single = Borrowed(&x); + let double = NamedBorrowed { x: &x, y: &y }; + let reference = Either::Ref(&x); + let number = Either::Num(y); + + println!("x is borrowed in {:?}", single); + println!("x and y are borrowed in {:?}", double); + println!("x is borrowed in {:?}", reference); + println!("y is *not* borrowed in {:?}", number); +} +``` + + +7、 🌟🌟 +```rust,editable +/* 让代码工作 */ + +#[derive(Debug)] +struct NoCopyType {} + +#[derive(Debug)] +struct Example<'a, 'b> { + a: &'a u32, + b: &'b NoCopyType +} + +fn main() +{ + let var_a = 35; + let example: Example; + + { + let var_b = NoCopyType {}; + + /* 修复错误 */ + example = Example { a: &var_a, b: &var_b }; + } + + println!("(Success!) {:?}", example); +} +``` + + +8、 🌟🌟 +```rust,editable + +#[derive(Debug)] +struct NoCopyType {} + +#[derive(Debug)] +#[allow(dead_code)] +struct Example<'a, 'b> { + a: &'a u32, + b: &'b NoCopyType +} + +/* 修复函数的签名 */ +fn fix_me(foo: &Example) -> &NoCopyType +{ foo.b } + +fn main() +{ + let no_copy = NoCopyType {}; + let example = Example { a: &1, b: &no_copy }; + fix_me(&example); + println!("Success!") +} +``` + +## 方法 +方法的生命周期标注跟函数类似。 + +**示例** +```rust,editable +struct Owner(i32); + +impl Owner { + fn add_one<'a>(&'a mut self) { self.0 += 1; } + fn print<'a>(&'a self) { + println!("`print`: {}", self.0); + } +} + +fn main() { + let mut owner = Owner(18); + + owner.add_one(); + owner.print(); +} +``` + +9、🌟🌟 +```rust,editable +/* 添加合适的生命周期让下面代码工作 */ +struct ImportantExcerpt { + part: &str, +} + +impl ImportantExcerpt { + fn level(&'a self) -> i32 { + 3 + } +} + +fn main() {} +``` + +## 生命周期消除( Elision ) + +有一些生命周期的标注方式很常见,因此编译器提供了一些规则,可以让我们在一些场景下无需去标注生命周期,既节省了敲击键盘的繁琐,又能提升可读性。 + +这种规则被称为生命周期消除规则( Elision ),该规则之所以存在,仅仅是因为这些场景太通用了,为了方便用户而已。事实上对于借用检查器而言,该有的生命周期一个都不能少,只不过对于用户而言,可以省去一些。 + + +10、🌟🌟 +```rust,editable +/* 移除所有可以消除的生命周期标注 */ + +fn nput<'a>(x: &'a i32) { + println!("`annotated_input`: {}", x); +} + +fn pass<'a>(x: &'a i32) -> &'a i32 { x } + +fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { + x +} + +struct Owner(i32); + +impl Owner { + fn add_one<'a>(&'a mut self) { self.0 += 1; } + fn print<'a>(&'a self) { + println!("`print`: {}", self.0); + } +} + +struct Person<'a> { + age: u8, + name: &'a str, +} + +enum Either<'a> { + Num(i32), + Ref(&'a i32), +} + +fn main() {} ``` \ No newline at end of file diff --git a/zh-CN/src/lifetime/intro.md b/zh-CN/src/lifetime/intro.md index 2615315..12a1fdb 100644 --- a/zh-CN/src/lifetime/intro.md +++ b/zh-CN/src/lifetime/intro.md @@ -1 +1,4 @@ -# Lifetime +# 生命周期 +学习资料: +- 简体中文: [Rust语言圣经 - 生命周期](https://course.rs/advance/lifetime/intro.html) +