diff --git a/.DS_Store b/.DS_Store index 59809baa..b1362080 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/book.toml b/book.toml index 2d6979dc..e98a863b 100644 --- a/book.toml +++ b/book.toml @@ -16,5 +16,5 @@ line-numbers = true [output.html.fold] enable = true -level = 1 +level = 2 diff --git a/course-codes/Readme.md b/course-codes/Readme.md new file mode 100644 index 00000000..06654ac7 --- /dev/null +++ b/course-codes/Readme.md @@ -0,0 +1 @@ +## 课程中留下的练习答案 diff --git a/course-codes/closure.md b/course-codes/closure.md new file mode 100644 index 00000000..ff8825a1 --- /dev/null +++ b/course-codes/closure.md @@ -0,0 +1,51 @@ +## 闭包 + +下面代码是Rust圣经课程中[闭包](http://course.rs/advance/functional-programing/closure.html#结构体中的闭包)章节的课内练习题答案: + +```rust +struct Cacher +where + T: Fn(E) -> E, + E: Copy +{ + query: T, + value: Option, +} + +impl Cacher +where + T: Fn(E) -> E, + E: Copy +{ + fn new(query: T) -> Cacher { + Cacher { + query, + value: None, + } + } + + fn value(&mut self, arg: E) -> E { + match self.value { + Some(v) => v, + None => { + let v = (self.query)(arg); + self.value = Some(v); + v + } + } + } +} +fn main() { + +} + +#[test] +fn call_with_different_values() { + let mut c = Cacher::new(|a| a); + + let v1 = c.value(1); + let v2 = c.value(2); + + assert_eq!(v2, 1); +} +``` \ No newline at end of file diff --git a/excercises/closure.md b/excercises/closure.md new file mode 100644 index 00000000..d2c81dc5 --- /dev/null +++ b/excercises/closure.md @@ -0,0 +1,2 @@ +# 闭包 + diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1de12e2..de525321 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -54,12 +54,13 @@ - [生命周期](advance/lifetime/intro.md) - [认识生命周期](advance/lifetime/basic.md) - [深入生命周期](advance/lifetime/advance.md) - - [迭代器(todo)](advance/iterator.md) + - [函数式编程 doing](advance/functional-programing/intro.md) + - [闭包closure](advance/functional-programing/closure.md) + - [迭代器(todo)](advance/functional-programing/iterator.md) - [自定义类型和动态类型 todo](advance/custom-type.md) - [格式化输出(todo)](advance/formatted-output.md) - [文档注释(todo)](advance/comment.md) - [包和模块(todo)](advance/crate-module.md) - - [函数式编程(todo)](advance/functional-programing.md) - [智能指针(todo)](advance/smart-pointer.md) - [全局变量](advance/global-variable.md) @@ -168,6 +169,7 @@ - [CPU缓存性能优化](performance/cpu-cache.md) - [堆和栈](performance/heap-stack.md) - [常用性能测试工具](performance/tools.md) + - [编译器 todo](compiler/intro.md) - [常见属性标记](compiler/attributes.md) - [优化编译速度](compiler/speed-up.md) diff --git a/src/advance/functional-programing.md b/src/advance/functional-programing.md deleted file mode 100644 index e748b8a4..00000000 --- a/src/advance/functional-programing.md +++ /dev/null @@ -1 +0,0 @@ -# functional-programing.md \ No newline at end of file diff --git a/src/advance/functional-programing/closure.md b/src/advance/functional-programing/closure.md new file mode 100644 index 00000000..3b77244a --- /dev/null +++ b/src/advance/functional-programing/closure.md @@ -0,0 +1,270 @@ +# 闭包closure + +关于闭包这个词语由来已久,自上世纪60年代就由`Scheme`语言引进,之后,被广泛用于函数式编程语言中,进入21世纪后,各种现代化的编程语言也都不约而同的把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包? + +闭包是**一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值**,例如: +```rust +fn main() { + let x = 1; + let sum = |y| x + y; + + assert_eq!(3, sum(2)); +} +``` + +上面的代码展示了非常简单的闭包`sum`,它拥有一个入参`y`,同时捕获了作用域中的`x`的值,因此调用`sum(2)`意味着将2(参数`y`)跟1(`x`)进行相加,最终返回它们的和:`3`。 + +可以看到`sum`非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。 + +## 使用闭包来简化代码 + +#### 传统函数实现 +想象一下,我们要进行健身,用代码怎么实现(写代码什么鬼,健身难道不应该去健身房嘛?答曰:健身太累了,还是虚拟健身好,点到为止)?这里是我的想法: +```rust +use std::thread; +use std::time::Duration; + +// 开始健身,好累,我得发出声音:muuuu... +fn muuuuu(intensity: u32) -> u32 { + println!("muuuu....."); + thread::sleep(Duration::from_secs(2)); + intensity +} + +fn workout(intensity: u32, random_number: u32) { + if intensity < 25 { + println!( + "今天活力满满, 先做 {} 个俯卧撑!", + muuuuu(intensity) + ); + println!( + "旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!", + muuuuu(intensity) + ); + } else { + if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); + } else { + println!( + "昨天练过度了,今天干干有氧, 跑步 {} 分钟!", + muuuuu(intensity) + ); + } + } +} + +fn main() { + // 强度 + let intensity = 10; + // 随机值用来决定某个选择 + let random_number = 7; + + // 开始健身 + workout(intensity, random_number); +} +``` + +可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用`muuuuu`函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用`muuuu`函数了,是不是得把所有`muuuu`都替换成,比如说`woooo`? 如果`muuuu`出现了几十次,那意味着我们要修改几十处地方。 + +#### 函数变量实现 +一个可行的办法是,把函数赋值给一个变量,然后通过变量调用: +```rust +fn workout(intensity: u32, random_number: u32) { + let action = muuuuu; + if intensity < 25 { + println!( + "今天活力满满, 先做 {} 个俯卧撑!", + action(intensity) + ); + println!( + "旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!", + action(intensity) + ); + } else { + if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); + } else { + println!( + "昨天练过度了,今天干干有氧, 跑步 {} 分钟!", + action(intensity) + ); + } + } +} +``` + + +经过上面修改后,所有的调用都通过`action`来完成,若未来声(动)音(作)变了,只要修改为`let action = woooo`即可。 + +但是问题又来了,若`intensity`也变了怎么办?例如变成`action(intensity + 1)`,那你又得哐哐哐修改几十处调用。 + +该怎么办?没太好的办法了,只能祭出大杀器:闭包。 + +#### 闭包实现 + +上面提到`intensity`要是变化怎么办,简单,使用闭包来捕获它,这是我们的拿手好戏: +```rust +fn workout(intensity: u32, random_number: u32) { + let action = || { + println!("muuuu....."); + thread::sleep(Duration::from_secs(2)); + intensity + }; + + if intensity < 25 { + println!( + "今天活力满满, 先做 {} 个俯卧撑!", + action() + ); + println!( + "旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!", + action() + ); + } else { + if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); + } else { + println!( + "昨天练过度了,今天干干有氧, 跑步 {} 分钟!", + action() + ); + } + } +} + +fn main() { + // 动作次数 + let intensity = 10; + // 随机值用来决定某个选择 + let random_number = 7; + + // 开始健身 + workout(intensity, random_number); +} +``` + +在上面代码中,无论你要修改什么,只要修改闭包`action`的实现即可,其它地方只负责调用,完美解决了我们的问题! + +Rust闭包在形式上借鉴了`Smalltalk`和`Ruby`语言,与函数最大的不同就是它的参数是通过`|parm1|`的形式进行声明,如果是多个参数就`|param1, param2,...|`, 下面给出闭包的形式定义: +```rust +|param1, param2,...| { + 语句1; + 语句2; + 返回表达式 +} +``` + +如果只有一个返回表达式的话,定义可以简化为: +```rust +|param1| 返回表达式 +``` + +上例中还有两点值得注意: +- **闭包中最后一行表达式返回的值,就是闭包执行后的返回值**,因此`action()`调用返回了`intensity`的值`10` +- `let action = ||...`只是把闭包赋值给变量`action`,并不是把闭包执行后的结果赋值给`action`,因此这里`action`就相当于闭包函数,可以跟函数一样进行调用:`action()` + +## 闭包的类型推导 +Rust是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式的去声明类型,但是显然函数并不在此列,必须手动为函数的所有参数和返回值指定类型,原因在于函数往往会作为API提供给你的用户,因此你的用户必须在使用时知道传入参数的类型和返回值类型。 + +与函数相反,闭包并不会作为API对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。 + +为了增加代码可读性,有时候我们会显式的给类型进行标注,出于同样的目的,也可以给闭包标注类型: +```rust +let sum = |x: i32, y: i32| -> i32 { + x + y +} +``` + +与之相比,不标注类型的闭包声明会更简洁些: `let sum = |x, y| x + y`, 需要注意的是,针对`sum`闭包,如果你不在后续代码中使用它,编译器会提示你为`x,y`添加类型标注,因为它缺乏必要的上下文: +```rust +let sum = |x, y| x + y; +let v = sum(1,2); +``` + +这里我们用使用了`sum`,同时把`1`传给了`x`,`2`传给了`y`,因此编译器才可以推导出`x,y`的类型为`i32`。 + +下面展示了同一个功能的函数和闭包实现形式: +```rust +fn add_one_v1 (x: u32) -> u32 { x + 1 } +let add_one_v2 = |x: u32| -> u32 { x + 1 }; +let add_one_v3 = |x| { x + 1 }; +let add_one_v4 = |x| x + 1 ; +``` + +可以看出第一行的函数和后面的闭包其实在形式上是非常接近的,同时三种不同的闭包也展示了三种不同的使用方式:省略参数、返回值和花括号对。 + +虽然类型推导很好用,但是它不是泛型,**当编译器推导出一种类型后,它就会一直使用该类型**: +```rust +let example_closure = |x| x; + +let s = example_closure(String::from("hello")); +let n = example_closure(5); +``` + +首先,在`s`中,编译器为`x`推导出类型`String`,但是紧接着`n`试图用`5`这个整型去调用闭包,跟编译器之前推导的`String`类型不符,因此报错: +```console +error[E0308]: mismatched types + --> src/main.rs:5:29 + | +5 | let n = example_closure(5); + | ^ + | | + | expected struct `String`, found integer // 期待String类型,却发现一个整数 + | help: try using a conversion method: `5.to_string()` +``` + +## 结构体中的闭包 +假设我们要实现一个简易缓存,功能是获取一个值,然后将其缓存起来,那么可以这样设计: +- 一个闭包用于获取值 +- 一个变量,用于存储该值 + +可以使用结构体来代表缓存对象,最终设计如下: +```rust +struct Cacher +where + T: Fn(u32) -> u32, +{ + query: T, + value: Option, +} +``` + +等等,我都跟着这本教程学完Rust基础了,为何还有我不认识的东东?`Fn(u32) -> u32`是什么鬼?别急,先回答你第一个问题:骚年,too young too naive,你以为Rust的语法特性就基础入门那一些嘛?太年轻了!如果是长征,你才刚到赤水河. + +其实,可以看的出这一长串是`T`的特征约束,再结合之前的已知信息:`query`是一个闭包,大概可以推测出,`Fn(u32) -> u32`是一个特征,用来表示`T`是一个闭包类型?Bingo,恭喜你,答对了! + +那为什么不用具体的类型来标注`query`呢?原因很简单,每一个闭包实例都有独属于自己的类型,甚至于两个签名一模一样的闭包,它们的类型都是不同的,因此你无法用一个统一的类型来标注`query`闭包。 + +而标准库提供的`Fn`系列特征,再结合特征约束,就很好的解决了这个问题. `T: Fn(u32) -> u32`意味着`query`的类型是`T`,该类型必须实现了相应的闭包特征`Fn(u32) -> u32`。从特征的角度来看它长得非常反直觉,但是如果从闭包的角度来看又极其符合直觉,不得不佩服Rust团队的鬼才设计。。。 + +特征`Fn(u32) -> u32`从表面来看,就对闭包形式进行了显而易见的限制:**该闭包拥有一个`u32`类型的参数,同时返回一个`u32`类型的值**. + +> 需要注意的是,其实Fn特征不仅仅适用于闭包,还适用于函数,因此上面的`query`字段除了使用闭包作为值外,还能使用一个具名的函数来作为它的值 + +接着,为缓存实现方法: +```rust +impl Cacher +where + T: Fn(u32) -> u32, +{ + fn new(query: T) -> Cacher { + Cacher { + query, + value: None, + } + } + + fn value(&mut self, arg: u32) -> u32 { + match self.value { + Some(v) => v, + None => { + let v = (self.query)(arg); + self.value = Some(v); + v + } + } + } +} +``` + +## 闭包的生命周期 diff --git a/src/advance/functional-programing/intro.md b/src/advance/functional-programing/intro.md new file mode 100644 index 00000000..49eca674 --- /dev/null +++ b/src/advance/functional-programing/intro.md @@ -0,0 +1,20 @@ +# 函数式编程 + +罗马不是一天建成的,编程语言亦是如此,每一门编程语言在借鉴前辈的同时,又提出了自己独有的特性,Rust即是如此。当站在巨人肩膀上时,一个人所能看到的就更高更远,恰好,我们看到了函数式语言的优秀特性,例如: +- 使用函数作为参数进行传递 +- 使用函数作为函数返回值 +- 将函数赋值给变量 + +见猎心喜,我们忍不住就借鉴了过来,于是你能看到本章的内容,天下语言一大。。。跑题了。 + +关于函数式编程到底是什么的争论由来已久,本章节并不会踏足这个泥潭,因此我们在这里主要关注的是函数式特性: +- 闭包closure +- 迭代器iterator +- 模式匹配 +- 枚举 + +其中后两个在前面章节我们已经深入学习过,因此本章的重点就是闭包和迭代器,**这些函数式特性可以让代码的可读性和易写性大幅提升**。对于Rust语言来说,掌握这两者就相当于你同时拥有了倚天剑屠龙刀,威力无穷。 + + + + diff --git a/src/advance/functional-programing/iterator.md b/src/advance/functional-programing/iterator.md new file mode 100644 index 00000000..00b2458a --- /dev/null +++ b/src/advance/functional-programing/iterator.md @@ -0,0 +1,4 @@ +# interator.md + + +https://doc.rust-lang.org/rust-by-example/error/iter_result.html \ No newline at end of file diff --git a/src/advance/iterator.md b/src/advance/iterator.md deleted file mode 100644 index 1141f7b7..00000000 --- a/src/advance/iterator.md +++ /dev/null @@ -1 +0,0 @@ -# interator.md \ No newline at end of file diff --git a/src/appendix/difficulties.md b/src/appendix/difficulties.md index cf756867..d564f235 100644 --- a/src/appendix/difficulties.md +++ b/src/appendix/difficulties.md @@ -2,11 +2,10 @@ 不可否认,Rust难点很多,而且知识点也很多,随着时间的进行,或多或少都会对一些难点逐渐模糊,这些难点无法通过目录章节名进行索引,因此集中放在此附录中,进行索引,方便读者朋友查阅。 -### ? -?主要用于简化错误传播,在[这里](../basic/result-error/result.md#传播界的大明星:)有详细讲解。 - -### () -元类型,见[这里](../basic/base-type/char-bool.md#元类型)和[这里](../basic/base-type/function.md#无返回值) - -### ! -[永不返回](../basic/base-type/function.md#永不返回的函数) \ No newline at end of file +| 难点及链接 | 简介 | +|----- | ------ | +| [?](../basic/result-error/result.md#传播界的大明星:) | 用于简化错误传播 | +| [()](../basic/base-type/function.md#无返回值) | 元类型 | +| [!](../basic/base-type/function.md#永不返回的函数) | 永不返回 | +| [@](../basic/match-pattern/all-patterns.md#绑定) | 为一个字段绑定另外一个变量 | +| ['a: 'b](../advance/lifetime/advance.md#生命周期约束) | 生命周期约束 | \ No newline at end of file diff --git a/src/basic/match-pattern/all-patterns.md b/src/basic/match-pattern/all-patterns.md index 2a00a147..6a7053b3 100644 --- a/src/basic/match-pattern/all-patterns.md +++ b/src/basic/match-pattern/all-patterns.md @@ -528,7 +528,7 @@ match x { 可以通过运行代码时的情况看出这一点:如果匹配守卫只作用于由 `|` 运算符指定的值列表的最后一个值,这个分支就会匹配且程序会打印出 `yes`。 -### `@` 绑定 +## @绑定 `@`(读作at)运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 `Message::Hello` 的 `id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。 @@ -552,7 +552,7 @@ match msg { } ``` -示例 18-29: 使用 `@` 在模式中绑定值的同时测试它 + 上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时将改值绑定到变量`id_variable`上。 @@ -562,4 +562,24 @@ match msg { 当你既想要限定分支范围,又想要使用分支的变量时,就可以用`@`来绑定到一个新的变量上,实现想要的功能。 +#### @新特性(Rust1.53新增) +考虑下面一段代码: +```rust +fn main() { + match 1 { + num @ 1 | 2 => { + println!("{}", num); + } + _ => {} + } +} +``` +编译不通过,是因为`num`没有绑定到所有的模式上,只绑定了模式`1`,你可能会试图通过这个方式来解决: +```rust +num @ (1 | 2) +``` + +但是,如果你用的是Rust1.53之前的版本,那该写法会报错,因为编译器不支持。 + + 至此,模式匹配的内容已经全部完结,复杂但是详尽,想要一次性全部记住属实不易,因此读者可以先留一个印象,等未来需要时,再来翻阅寻找具体的模式实现方式。 diff --git a/src/style-guide/code.md b/src/style-guide/code.md index c87d2bcd..315170fd 100644 --- a/src/style-guide/code.md +++ b/src/style-guide/code.md @@ -1 +1,4 @@ # 代码风格(todo) + + +https://www.reddit.com/r/rust/comments/rlsatb/generically_working_with_futuressinkstream/ \ No newline at end of file diff --git a/src/style-guide/naming.md b/src/style-guide/naming.md index de82c739..0eea6eb4 100644 --- a/src/style-guide/naming.md +++ b/src/style-guide/naming.md @@ -33,6 +33,10 @@ [RFC 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md [C-FEATURE]: #c-feature +## 特征命名 +特征的名称应该使用动词,而不是形容词或者名词,例如`Print`和`Draw`明显好于`Printable`和`Drawable`。 + + ## 类型转换要遵守`as_`, `to_`, `into_`命名惯例(C-CONV) 类型转换应该通过方法调用的方式实现,其中的前缀规则如下: diff --git a/src/test/ci.md b/src/test/ci.md index 1ef1bada..62d1472f 100644 --- a/src/test/ci.md +++ b/src/test/ci.md @@ -1 +1,75 @@ # 持续集成 + + +## github ci +https://github.com/julianandrews/sgf-render/blob/master/.github/workflows/build.yml + +```yml +name: build +on: + workflow_dispatch: {} +jobs: + build: + name: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [linux, macos, windows] + include: + - build: linux + os: ubuntu-18.04 + rust: nightly + target: x86_64-unknown-linux-musl + archive-name: sgf-render-linux.tar.gz + - build: macos + os: macos-latest + rust: nightly + target: x86_64-apple-darwin + archive-name: sgf-render-macos.tar.gz + - build: windows + os: windows-2019 + rust: nightly-x86_64-msvc + target: x86_64-pc-windows-msvc + archive-name: sgf-render-windows.7z + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + profile: minimal + override: true + target: ${{ matrix.target }} + + - name: Build binary + run: cargo build --verbose --release --target ${{ matrix.target }} + env: + RUST_BACKTRACE: 1 + + - name: Strip binary (linux and macos) + if: matrix.build == 'linux' || matrix.build == 'macos' + run: strip "target/${{ matrix.target }}/release/sgf-render" + + - name: Build archive + shell: bash + run: | + mkdir archive + cp LICENSE README.md archive/ + cd archive + if [ "${{ matrix.build }}" = "windows" ]; then + cp "../target/${{ matrix.target }}/release/sgf-render.exe" ./ + 7z a "${{ matrix.archive-name }}" LICENSE README.md sgf-render.exe + else + cp "../target/${{ matrix.target }}/release/sgf-render" ./ + tar -czf "${{ matrix.archive-name }}" LICENSE README.md sgf-render + fi + - name: Upload archive + uses: actions/upload-artifact@v1 + with: + name: ${{ matrix.archive-name }} + path: archive/${{ matrix.archive-name }} +``` \ No newline at end of file diff --git a/src/test/unit.md b/src/test/unit.md index 2fd14410..98605634 100644 --- a/src/test/unit.md +++ b/src/test/unit.md @@ -1 +1,5 @@ # 单元测试 + + + +https://www.reddit.com/r/rust/comments/rln220/question_prevent_racing_in_unit_tests/ \ No newline at end of file