From 69189b361ce577271dca2169caa985f2bd0b8822 Mon Sep 17 00:00:00 2001 From: Lion Guo <lion.guo@omnitracs.com> Date: Sat, 2 Apr 2022 07:30:32 +0000 Subject: [PATCH 01/34] Remove wrong file name, which block checkout on Win OS --- src/https:/github.com | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/https:/github.com diff --git a/src/https:/github.com b/src/https:/github.com deleted file mode 100644 index 83c831f0..00000000 --- a/src/https:/github.com +++ /dev/null @@ -1 +0,0 @@ -# test From 8757e1cdfa6b1f0cee1f1a9a8f24153f372b0f61 Mon Sep 17 00:00:00 2001 From: themanforfree <56149350+themanforfree@users.noreply.github.com> Date: Sat, 2 Apr 2022 23:39:56 +0800 Subject: [PATCH 02/34] fix wrong url --- src/first-try/hello-world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/first-try/hello-world.md b/src/first-try/hello-world.md index a14910fd..9121320e 100644 --- a/src/first-try/hello-world.md +++ b/src/first-try/hello-world.md @@ -4,7 +4,7 @@ ## 多国语言的"世界,你好" -还记得大明湖畔等你的 [VSCode IDE](https://course.rs/first-try/editor.md) 和通过 `Cargo` 创建的 [世界,你好](https://course.rs/first-try/cargo.html) 工程吗? +还记得大明湖畔等你的 [VSCode IDE](https://course.rs/first-try/editor.html) 和通过 `Cargo` 创建的 [世界,你好](https://course.rs/first-try/cargo.html) 工程吗? 现在使用 VSCode 打开 [上一节](https://course.rs/first-try/cargo.html) 中创建的 `world_hello` 工程,然后进入 `main.rs` 文件。(此文件是当前 Rust 工程的入口文件,和其它语言几无区别。) From bdaedb283616124c07c9eece528eb8cdc1fa4447 Mon Sep 17 00:00:00 2001 From: Allan Downey <AllanDowney@126.com> Date: Sun, 3 Apr 2022 10:09:37 +0800 Subject: [PATCH 03/34] update(index-list): add flow-control --- src/index-list.md | 48 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/index-list.md b/src/index-list.md index 11dd59f1..f50acd14 100644 --- a/src/index-list.md +++ b/src/index-list.md @@ -26,6 +26,7 @@ | [&] | 引用 | 常规引用是一个指针类型,指向了对象存储的内存地址 | | [\*] | 解引用 | 解出引用所指向的值 | | [@] | 变量绑定 | 为一个字段绑定另外一个变量 | +| `_` | | 忽略该值或者类型 | | ['a: 'b] | 生命周期约束 | 用来说明两个生命周期的长短 | | [{:?}] {:#?} | 打印结构体信息 | 使用 `#[derive(Debug)]` 派生实现 `Debug` 特征 | | A | | AIntroduction | @@ -63,12 +64,14 @@ | [变量作用域] | 所有权 | 作用域是一个变量在程序中有效的范围 | | [表达式] | | 进行求值,结尾无 `;`,有返回值 | | [bool 布尔] | 布尔类型 | `true` `false`,占用 1 字节 | +| [break] | 循环控制 | 直接跳出当前整个循环 | | B | KWB | BIntroduction | [变量遮蔽]: https://course.rs/basic/variable.html#变量遮蔽shadowing [变量作用域]: https://course.rs/basic/ownership/ownership.html#变量作用域 [bool 布尔]: https://course.rs/basic/base-type/char-bool.html#布尔bool [表达式]: https://course.rs/basic/base-type/statement-expression.html#表达式 +[break]: https://course.rs/basic/flow-control.html#break [back](#head) @@ -79,6 +82,7 @@ | [char 字符] | 字符类型 | 使用 `''` 表示,所有的 Unicode 值 | | [const 常量] | constant | `const MAX_POINTS: u32 = 100_000;` | | [Copy 拷贝] | 浅拷贝 | 任何基本类型的组合可以 `Copy`,不需要分配内存或某种形式资源的类型是可以 `Copy` 的。 | +| [continue] | 循环控制 | 跳过当前当次的循环,开始下次的循环 | | [Clone 克隆] | 深拷贝 | 需要复制堆上的数据时,可以使用 `.clone()` 方法 | | C | KWC | CIntroduction | @@ -86,6 +90,7 @@ [const 常量]: https://course.rs/basic/variable.html#变量和常量之间的差异 [copy 拷贝]: https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝 [clone 克隆]: https://course.rs/basic/ownership/ownership.html#克隆深拷贝 +[continue]: https://course.rs/basic/flow-control.html#continue [back](#head) @@ -110,12 +115,14 @@ ## F -| 名称 | 关键字 | 简介 | -| -------- | -------- | ------------------------ | -| [浮点数] | 数值类型 | `f32`<br>`f64`(默认类型) | -| F | KWF | FIntroduction | +| 名称 | 关键字 | 简介 | +| ---------- | -------- | ---------------------------- | +| [浮点数] | 数值类型 | `f32`<br>`f64`(默认类型) | +| [for 循环] | 循环控制 | `for item in &collection {}` | +| F | KWF | FIntroduction | [浮点数]: https://course.rs/basic/base-type/numbers.html#浮点类型 +[for 循环]: https://course.rs/basic/flow-control.html#for-循环 [back](#head) @@ -140,9 +147,14 @@ ## I -| 名称 | 关键字 | 简介 | -| ---- | ------ | ------------- | -| I | KWI | IIntroduction | +| 名称 | 关键字 | 简介 | +| --------- | -------- | -------------------------- | +| [if else] | 流程控制 | 根据条件执行不同的代码分支 | +| [else if] | 流程控制 | 处理多重条件 | +| I | KWI | IIntroduction | + +[if else]: https://course.rs/basic/flow-control.html#使用-if-来做分支控制 +[else if]: https://course.rs/basic/flow-control.html#使用-else-if-来处理多重条件 [back](#head) @@ -164,14 +176,17 @@ ## L -| 名称 | 关键字 | 简介 | -| --------- | -------- | ----------------------------- | -| [let] | 变量绑定 | `let x : u32 = 5;` | -| [let mut] | 可变变量 | `let mut x : u32 = 5; x = 9;` | -| L | KWL | LIntroduction | +| 名称 | 关键字 | 简介 | +| ----------- | -------- | ------------------------------ | +| [let] | 变量绑定 | `let x : u32 = 5;` | +| [let mut] | 可变变量 | `let mut x : u32 = 5; x = 9;` | +| [loop 循环] | 循环控制 | 无限循环,注意要配合 [`break`] | +| L | KWL | LIntroduction | [let]: https://course.rs/basic/variable.html#变量绑定 [let mut]: https://course.rs/basic/variable.html#变量可变性 +[`break`]: https://course.rs/basic/flow-control.html#break +[loop 循环]: https://course.rs/basic/flow-control.html#loop-循环 [back](#head) @@ -287,9 +302,12 @@ ## W -| 名称 | 关键字 | 简介 | -| ---- | ------ | ------------- | -| W | KWW | WIntroduction | +| 名称 | 关键字 | 简介 | +| ------------ | -------- | ------------------------------------------------------ | +| [while 循环] | 循环控制 | 当条件为 `true` 时,继续循环,条件为 `false`,跳出循环 | +| W | KWW | WIntroduction | + +[while 循环]: https://course.rs/basic/flow-control.html#while-循环 [back](#head) From 7c397856c099f0edc3c62f78065930635f58b5e2 Mon Sep 17 00:00:00 2001 From: Allan Downey <AllanDowney@126.com> Date: Sun, 3 Apr 2022 10:25:26 +0800 Subject: [PATCH 04/34] Update: unified format --- src/basic/flow-control.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/basic/flow-control.md b/src/basic/flow-control.md index e5f24560..d17544bc 100644 --- a/src/basic/flow-control.md +++ b/src/basic/flow-control.md @@ -6,7 +6,7 @@ ## 使用 if 来做分支控制 -> if else 无处不在 - 鲁迅 +> if else 无处不在 -- 鲁迅 但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支: @@ -56,7 +56,7 @@ error[E0308]: if and else have incompatible types found type `&str` ``` -#### 使用 else if 来处理多重条件 +## 使用 else if 来处理多重条件 可以将 `else if` 与 `if`、`else` 组合在一起实现更复杂的条件分支判断: @@ -82,20 +82,20 @@ fn main() { 如果代码中有大量的 `else if ` 会让代码变得极其丑陋,不过不用担心,下一章的 `match` 专门用以解决多分支模式匹配的问题。 -## 循环控制 +# 循环控制 循环无处不在,上到数钱,下到数年,你能想象的很多场景都存在循环,因此它也是流程控制中最重要的组成部分之一。 在 Rust 语言中有三种循环方式:`for`、`while` 和 `loop`,其中 `for` 循环是 Rust 循环王冠上的明珠。 -#### for 循环 +## for 循环 `for` 循环是 Rust 的大杀器: ```rust fn main() { for i in 1..=5 { - println!("{}",i); + println!("{}", i); } } ``` @@ -140,10 +140,10 @@ for item in &mut collection { ```rust fn main() { - let a = [4,3,2,1]; + let a = [4, 3, 2, 1]; // `.iter()` 方法把 `a` 数组变成一个迭代器 - for (i,v) in a.iter().enumerate() { - println!("第{}个元素是{}",i+1,v); + for (i, v) in a.iter().enumerate() { + println!("第{}个元素是{}", i + 1, v); } } ``` @@ -183,7 +183,7 @@ for item in collection { 由于 `for` 循环无需任何条件限制,也不需要通过索引来访问,因此是最安全也是最常用的,通过与下面的 `while` 的对比,我们能看到为什么 `for` 会更加安全。 -#### `continue` +## `continue` 使用 `continue` 可以跳过当前当次的循环,开始下次的循环: @@ -192,7 +192,7 @@ for item in collection { if i == 2 { continue; } - println!("{}",i); + println!("{}", i); } ``` @@ -203,7 +203,7 @@ for item in collection { 3 ``` -#### `break` +## `break` 使用 `break` 可以直接跳出当前整个循环: @@ -212,7 +212,7 @@ for item in collection { if i == 2 { break; } - println!("{}",i); + println!("{}", i); } ``` @@ -222,7 +222,7 @@ for item in collection { 1 ``` -#### while 循环 +## while 循环 如果你需要一个条件来循环,当该条件为 `true` 时,继续循环,条件为 `false`,跳出循环,那么 `while` 就非常适用: @@ -262,7 +262,7 @@ fn main() { if n > 5 { break } - println!("{}",n); + println!("{}", n); n+=1; } @@ -317,7 +317,7 @@ fn main() { 可以看出,`for` 并不会使用索引去访问数组,因此更安全也更简洁,同时避免 `运行时的边界检查`,性能更高。 -#### loop 循环 +## loop 循环 对于循环而言,`loop` 循环毋庸置疑,是适用面最高的,它可以适用于所有循环场景(虽然能用,但是在很多场景下, `for` 和 `while` 才是最优选择),因为 `loop` 就是一个简单的无限循环,你可以在内部实现逻辑通过 `break` 关键字来控制循环何时结束。 @@ -368,8 +368,6 @@ fn main() { - **break 可以单独使用,也可以带一个返回值**,有些类似 `return` - **loop 是一个表达式**,因此可以返回一个值 - ## 课后练习 > [Rust By Practice](https://zh.practice.rs/flow-control.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。 - From 93e73e68669f8e5cbccbfac71c94000f3fb8c9a0 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Sun, 3 Apr 2022 22:56:46 +0800 Subject: [PATCH 05/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E7=BA=BF=E7=A8=8B]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 9 +- src/cookbook/cocurrency/threads.md | 336 +++++++++++++++++++++++++++++ src/cookbook/compression/intro.md | 3 - 3 files changed, 342 insertions(+), 6 deletions(-) create mode 100644 src/cookbook/cocurrency/threads.md delete mode 100644 src/cookbook/compression/intro.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 89e1ba93..41a95322 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -270,9 +270,12 @@ - [命令行]() - [参数解析](cookbook/cmd/parsing.md) - [终端输出格式化](cookbook/cmd/ansi.md) - - [压缩](cookbook/compression/intro.md) + - [压缩]() - [使用.tar包](cookbook/compression/tar.md) - - [配置文件解析 todo](cookbook/config.md) + - [并发]() + - [线程](cookbook/cocurrency/threads.md) + + <!-- - [配置文件解析 todo](cookbook/config.md) - [编解码 todo](cookbook/encoding/intro.md) - [JSON](cookbook/encoding/json.md) - [CSV](cookbook/encoding/csv.md) @@ -291,7 +294,7 @@ - [时间日期](cookbook/date.md) - [开发调试 todo](cookbook/dev/intro.md) - [日志](cookbook/dev/logs.md) - - [性能分析](cookbook/dev/profile.md) + - [性能分析](cookbook/dev/profile.md) --> <!-- - [Rust区块链入门]() diff --git a/src/cookbook/cocurrency/threads.md b/src/cookbook/cocurrency/threads.md new file mode 100644 index 00000000..b980f9ca --- /dev/null +++ b/src/cookbook/cocurrency/threads.md @@ -0,0 +1,336 @@ +# 线程 + +### 生成一个临时性的线程 + +下面例子用到了 [crossbeam](cookbook/cocurrency/intro.md) 包,它提供了非常实用的、用于并发和并行编程的数据结构和函数。 + +[Scope::spawn](https://docs.rs/crossbeam/*/crossbeam/thread/struct.Scope.html#method.spawn) 会生成一个被限定了作用域的线程,该线程最大的特点就是:它会在传给 [crossbeam::scope](https://docs.rs/crossbeam/0.8.1/crossbeam/fn.scope.html) 的闭包函数返回前先行结束。得益于这个特点,子线程的创建使用就像是本地闭包函数调用,因此生成的线程内部可以使用外部环境中的变量! + + +```rust,editable +fn main() { + let arr = &[1, 25, -4, 10]; + let max = find_max(arr); + assert_eq!(max, Some(25)); +} + +// 将数组分成两个部分,并使用新的线程对它们进行处理 +fn find_max(arr: &[i32]) -> Option<i32> { + const THRESHOLD: usize = 2; + + if arr.len() <= THRESHOLD { + return arr.iter().cloned().max(); + } + + let mid = arr.len() / 2; + let (left, right) = arr.split_at(mid); + + crossbeam::scope(|s| { + let thread_l = s.spawn(|_| find_max(left)); + let thread_r = s.spawn(|_| find_max(right)); + + let max_l = thread_l.join().unwrap()?; + let max_r = thread_r.join().unwrap()?; + + Some(max_l.max(max_r)) + }).unwrap() +} +``` + +### 创建并行流水线 +下面我们使用 [crossbeam](https://docs.rs/crossbeam/latest/crossbeam/) 和 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 来创建一个并行流水线:流水线的两端分别是数据源和数据下沉( sink ),在流水线中间,有两个工作线程会从源头接收数据,对数据进行并行处理,最后将数据下沉。 + +- 消息通道( channel )是 [crossbeam_channel::bounded](https://docs.rs/crossbeam-channel/0.5.4/crossbeam_channel/fn.bounded.html),它只能缓存一条消息。当缓存满后,发送者继续调用 [crossbeam_channel::Sender::send] 发送消息时会阻塞,直到一个工作线程( 消费者 ) 拿走这条消息 +- 消费者获取消息时先到先得的策略,因此两个工作线程只有一个能取到消息,保证消息不会被重复消费、处理 +- 通过迭代器 [crossbeam_channel::Receiver::iter](https://docs.rs/crossbeam-channel/*/crossbeam_channel/struct.Receiver.html#method.iter) 读取消息会阻塞当前线程,直到新消息的到来或 channel 关闭 +- channel 只有在所有的发送者或消费者关闭后,才能被关闭。而其中一个消费者 `rcv2` 处于阻塞读取状态,无比被关闭,因此我们必须要关闭所有发送者: `drop(snd1);` `drop(snd2)` ,这样 channel 关闭后,主线程的 `rcv2` 才能从阻塞状态退出,最后整个程序结束。大家还是迷惑的话,可以看看这篇[文章](https://course.rs/practice/pitfalls/main-with-channel-blocked.html)。 + +```rust,editable +extern crate crossbeam; +extern crate crossbeam_channel; + +use std::thread; +use std::time::Duration; +use crossbeam_channel::bounded; + +fn main() { + let (snd1, rcv1) = bounded(1); + let (snd2, rcv2) = bounded(1); + let n_msgs = 4; + let n_workers = 2; + + crossbeam::scope(|s| { + // 生产者线程 + s.spawn(|_| { + for i in 0..n_msgs { + snd1.send(i).unwrap(); + println!("Source sent {}", i); + } + + // 关闭其中一个发送者 snd1 + // 该关闭操作对于结束最后的循环是必须的 + drop(snd1); + }); + + // 通过两个线程并行处理 + for _ in 0..n_workers { + // 从数据源接收数据,然后发送到下沉端 + let (sendr, recvr) = (snd2.clone(), rcv1.clone()); + // 生成单独的工作线程 + s.spawn(move |_| { + thread::sleep(Duration::from_millis(500)); + // 等待通道的关闭 + for msg in recvr.iter() { + println!("Worker {:?} received {}.", + thread::current().id(), msg); + sendr.send(msg * 2).unwrap(); + } + }); + } + // 关闭通道,如果不关闭,下沉端将永远无法结束循环 + drop(snd2); + + // 下沉端 + for msg in rcv2.iter() { + println!("Sink received {}", msg); + } + }).unwrap(); +} +``` + + +### 线程间传递数据 + +下面我们来看看 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 的单生产者单消费者( SPSC ) 使用场景。 + +```rust,editable +use std::{thread, time}; +use crossbeam_channel::unbounded; + +fn main() { + // unbounded 意味着 channel 可以存储任意多的消息 + let (snd, rcv) = unbounded(); + let n_msgs = 5; + crossbeam::scope(|s| { + s.spawn(|_| { + for i in 0..n_msgs { + snd.send(i).unwrap(); + thread::sleep(time::Duration::from_millis(100)); + } + }); + }).unwrap(); + for _ in 0..n_msgs { + let msg = rcv.recv().unwrap(); + println!("Received {}", msg); + } +} +``` + +### 维护全局可变的状态 + +[lazy_static]() 会创建一个全局的静态引用( static ref ),该引用使用了 `Mutex` 以支持可变性,因此我们可以在代码中对其进行修改。`Mutex` 能保证该全局状态同时只能被一个线程所访问。 + +```rust,editable +use error_chain::error_chain; +use lazy_static::lazy_static; +use std::sync::Mutex; + +error_chain!{ } + +lazy_static! { + static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new()); +} + +fn insert(fruit: &str) -> Result<()> { + let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; + db.push(fruit.to_string()); + Ok(()) +} + +fn main() -> Result<()> { + insert("apple")?; + insert("orange")?; + insert("peach")?; + { + let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; + + db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item)); + } + insert("grape")?; + Ok(()) +} +``` + +### 并行计算 iso 文件的 SHA256 + +下面的示例将为当前目录中的每一个 .iso 文件都计算一个 SHA256 sum。其中线程池中会初始化和 CPU 核心数一致的线程数,其中核心数是通过 [num_cpus::get](https://docs.rs/num_cpus/*/num_cpus/fn.get.html) 函数获取。 + +`Walkdir::new` 可以遍历当前的目录,然后调用 `execute` 来执行读操作和 SHA256 哈希计算。 + +```rust,editable + +use walkdir::WalkDir; +use std::fs::File; +use std::io::{BufReader, Read, Error}; +use std::path::Path; +use threadpool::ThreadPool; +use std::sync::mpsc::channel; +use ring::digest::{Context, Digest, SHA256}; + +// Verify the iso extension +fn is_iso(entry: &Path) -> bool { + match entry.extension() { + Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true, + _ => false, + } +} + +fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> { + let mut buf_reader = BufReader::new(File::open(&filepath)?); + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + + loop { + let count = buf_reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + + Ok((context.finish(), filepath)) +} + +fn main() -> Result<(), Error> { + let pool = ThreadPool::new(num_cpus::get()); + + let (tx, rx) = channel(); + + for entry in WalkDir::new("/home/user/Downloads") + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| !e.path().is_dir() && is_iso(e.path())) { + let path = entry.path().to_owned(); + let tx = tx.clone(); + pool.execute(move || { + let digest = compute_digest(path); + tx.send(digest).expect("Could not send data!"); + }); + } + + drop(tx); + for t in rx.iter() { + let (sha, path) = t?; + println!("{:?} {:?}", sha, path); + } + Ok(()) +} +``` + + +### 使用线程池来绘制分形 + +下面例子中将基于 [Julia Set]() 来绘制一个分形图片,其中使用到了线程池来做分布式计算。 + +<img src="https://cloud.githubusercontent.com/assets/221000/26546700/9be34e80-446b-11e7-81dc-dd9871614ea1.png" /> + +```rust,edtiable +# use error_chain::error_chain; +use std::sync::mpsc::{channel, RecvError}; +use threadpool::ThreadPool; +use num::complex::Complex; +use image::{ImageBuffer, Pixel, Rgb}; + +# +# error_chain! { +# foreign_links { +# MpscRecv(RecvError); +# Io(std::io::Error); +# } +# } +# +# // Function converting intensity values to RGB +# // Based on http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm +# fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> { +# let wave = wavelength as f32; +# +# let (r, g, b) = match wavelength { +# 380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0), +# 440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0), +# 490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)), +# 510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0), +# 580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0), +# 645..=780 => (1.0, 0.0, 0.0), +# _ => (0.0, 0.0, 0.0), +# }; +# +# let factor = match wavelength { +# 380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.), +# 701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.), +# _ => 1.0, +# }; +# +# let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor)); +# Rgb::from_channels(r, g, b, 0) +# } +# +# // Maps Julia set distance estimation to intensity values +# fn julia(c: Complex<f32>, x: u32, y: u32, width: u32, height: u32, max_iter: u32) -> u32 { +# let width = width as f32; +# let height = height as f32; +# +# let mut z = Complex { +# // scale and translate the point to image coordinates +# re: 3.0 * (x as f32 - 0.5 * width) / width, +# im: 2.0 * (y as f32 - 0.5 * height) / height, +# }; +# +# let mut i = 0; +# for t in 0..max_iter { +# if z.norm() >= 2.0 { +# break; +# } +# z = z * z + c; +# i = t; +# } +# i +# } +# +# // Normalizes color intensity values within RGB range +# fn normalize(color: f32, factor: f32) -> u8 { +# ((color * factor).powf(0.8) * 255.) as u8 +# } + +fn main() -> Result<()> { + let (width, height) = (1920, 1080); + // 为指定宽高的输出图片分配内存 + let mut img = ImageBuffer::new(width, height); + let iterations = 300; + + let c = Complex::new(-0.8, 0.156); + + let pool = ThreadPool::new(num_cpus::get()); + let (tx, rx) = channel(); + + for y in 0..height { + let tx = tx.clone(); + // execute 将每个像素作为单独的作业接收 + pool.execute(move || for x in 0..width { + let i = julia(c, x, y, width, height, iterations); + let pixel = wavelength_to_rgb(380 + i * 400 / iterations); + tx.send((x, y, pixel)).expect("Could not send data!"); + }); + } + + for _ in 0..(width * height) { + let (x, y, pixel) = rx.recv()?; + // 使用数据来设置像素的颜色 + img.put_pixel(x, y, pixel); + } + + // 输出图片内容到指定文件中 + let _ = img.save("output.png")?; + Ok(()) +} +``` \ No newline at end of file diff --git a/src/cookbook/compression/intro.md b/src/cookbook/compression/intro.md deleted file mode 100644 index aa2d4d8b..00000000 --- a/src/cookbook/compression/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -## 压缩 - -我们会对常用的压缩方法进行介绍,例如 `tar`, `gzip`, `lz4` 等。 \ No newline at end of file From d606e8c9b0f21d66e855575edffc4b1f0bcb72c9 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Sun, 3 Apr 2022 22:57:23 +0800 Subject: [PATCH 06/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E7=BA=BF=E7=A8=8B]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 内容变更记录.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/内容变更记录.md b/内容变更记录.md index 866c0e73..fe542a52 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -1,6 +1,10 @@ # ChangeLog 记录一些值得注意的变更。 +## 2022-04-03 + +- 新增章节:[Cookbook - 线程](http://course.rs/cookbook/cocurrency/threads.html) + ## 2022-04-01 - 新增章节: [Cookbook - 生成随机值](https://course.rs/cookbook/algos/randomness.html) From 3cc023f64e985dd0548c98ffe178126274bdf167 Mon Sep 17 00:00:00 2001 From: Allan Downey <AllanDowney@126.com> Date: Mon, 4 Apr 2022 03:25:54 +0800 Subject: [PATCH 07/34] update(index-list): add match and if let --- src/index-list.md | 71 +++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/index-list.md b/src/index-list.md index f50acd14..d4ae7d59 100644 --- a/src/index-list.md +++ b/src/index-list.md @@ -18,18 +18,18 @@ ## Sym -| 名称 | 关键字 | 简介 | -| ----------------------- | -------------- | ------------------------------------------------ | -| [?] | 错误传播 | 用于简化错误传播 | -| [()] | 单元类型 | 单元类型,无返回值 | -| `!` : [1 函数] [2 类型] | 永不返回 | 永不返回 | -| [&] | 引用 | 常规引用是一个指针类型,指向了对象存储的内存地址 | -| [\*] | 解引用 | 解出引用所指向的值 | -| [@] | 变量绑定 | 为一个字段绑定另外一个变量 | -| `_` | | 忽略该值或者类型 | -| ['a: 'b] | 生命周期约束 | 用来说明两个生命周期的长短 | -| [{:?}] {:#?} | 打印结构体信息 | 使用 `#[derive(Debug)]` 派生实现 `Debug` 特征 | -| A | | AIntroduction | +| 名称 | 关键字 | 简介 | +| ----------------------- | -------------- | ------------------------------------------------------------------------------------ | +| [?] | 错误传播 | 用于简化错误传播 | +| [()] | 单元类型 | 单元类型,无返回值 | +| `!` : [1 函数] [2 类型] | 永不返回 | 永不返回 | +| [&] | 引用 | 常规引用是一个指针类型,指向了对象存储的内存地址 | +| [\*] | 解引用 | 解出引用所指向的值 | +| [@] | 变量绑定 | 为一个字段绑定另外一个变量 | +| `_` : [2 模式匹配] | 忽略 | 1. 忽略该值或者类型,否则编译器会给你一个 `变量未使用的` 的警告<br>2. 模式匹配通配符 | +| ['a: 'b] | 生命周期约束 | 用来说明两个生命周期的长短 | +| [{:?}] {:#?} | 打印结构体信息 | 使用 `#[derive(Debug)]` 派生实现 `Debug` 特征 | +| A | | AIntroduction | [?]: https://course.rs/basic/result-error/result.html#传播界的大明星- [()]: https://course.rs/basic/base-type/function.html#无返回值 @@ -40,6 +40,7 @@ [@]: https://course.rs/basic/match-pattern/all-patterns.html#绑定 ['a: 'b]: https://course.rs/advance/lifetime/advance.html#生命周期约束-hrtb [{:?}]: https://course.rs/basic/compound-type/struct.html?search=#使用-derivedebug-来打印结构体的信息 +[2 模式匹配]: https://course.rs/basic/match-pattern/match-if-let.html#_-通配符 [back](#head) @@ -58,16 +59,18 @@ ## B -| 名称 | 关键字 | 简介 | -| ------------ | --------- | -------------------------------------- | -| [变量遮蔽] | shadowing | 允许声明相同的变量名,后者会遮蔽掉前者 | -| [变量作用域] | 所有权 | 作用域是一个变量在程序中有效的范围 | -| [表达式] | | 进行求值,结尾无 `;`,有返回值 | -| [bool 布尔] | 布尔类型 | `true` `false`,占用 1 字节 | -| [break] | 循环控制 | 直接跳出当前整个循环 | -| B | KWB | BIntroduction | +| 名称 | 关键字 | 简介 | +| ------------ | --------- | ------------------------------------------------------------------------------ | +| [变量遮蔽] | shadowing | 允许声明相同的变量名,后者会遮蔽掉前者 | +| [变量覆盖] | 模式匹配 | 无论是是 `match` 还是 `if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值 | +| [变量作用域] | 所有权 | 作用域是一个变量在程序中有效的范围 | +| [表达式] | | 进行求值,结尾无 `;`,有返回值 | +| [bool 布尔] | 布尔类型 | `true` `false`,占用 1 字节 | +| [break] | 循环控制 | 直接跳出当前整个循环 | +| B | KWB | BIntroduction | [变量遮蔽]: https://course.rs/basic/variable.html#变量遮蔽shadowing +[变量覆盖]: https://course.rs/basic/match-pattern/match-if-let.html#变量覆盖 [变量作用域]: https://course.rs/basic/ownership/ownership.html#变量作用域 [bool 布尔]: https://course.rs/basic/base-type/char-bool.html#布尔bool [表达式]: https://course.rs/basic/base-type/statement-expression.html#表达式 @@ -147,14 +150,16 @@ ## I -| 名称 | 关键字 | 简介 | -| --------- | -------- | -------------------------- | -| [if else] | 流程控制 | 根据条件执行不同的代码分支 | -| [else if] | 流程控制 | 处理多重条件 | -| I | KWI | IIntroduction | +| 名称 | 关键字 | 简介 | +| ------------- | -------- | --------------------------------------------------------------------- | +| [if else] | 流程控制 | 根据条件执行不同的代码分支 | +| [else if] | 流程控制 | 处理多重条件 | +| [if let 匹配] | 模式匹配 | 当你只要匹配一个条件,且忽略其他条件时就用 `if let`,否则都用 `match` | +| I | KWI | IIntroduction | [if else]: https://course.rs/basic/flow-control.html#使用-if-来做分支控制 [else if]: https://course.rs/basic/flow-control.html#使用-else-if-来处理多重条件 +[if let 匹配]: https://course.rs/basic/match-pattern/match-if-let.html#if-let-匹配 [back](#head) @@ -192,11 +197,17 @@ ## M -| 名称 | 关键字 | 简介 | -| ----------- | ---------- | ----------------------------------------------------- | -| [move 移动] | 转移所有权 | `let s2 = s1;`<br>`s1` 所有权转移给了 `s2`,`s1` 失效 | -| M | KWM | MIntroduction | - +| 名称 | 关键字 | 简介 | +| ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [模式绑定] | 模式匹配 | 从模式中取出绑定的值 | +| [match 匹配] | 模式匹配 | `match` 的匹配必须要穷举出所有可能,因此这里用 `_ ` 来代表未列出的所有可能性<br>`match` 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同 | +| [matches! 宏] | 模式匹配 | 将一个表达式跟模式进行匹配,然后返回匹配的结果 `true` 或 `false` | +| [move 移动] | 转移所有权 | `let s2 = s1;`<br>`s1` 所有权转移给了 `s2`,`s1` 失效 | +| M | KWM | MIntroduction | + +[模式绑定]: https://course.rs/basic/match-pattern/match-if-let.html#模式绑定 +[match 匹配]: https://course.rs/basic/match-pattern/match-if-let.html#match-匹配 +[matches! 宏]: https://course.rs/basic/match-pattern/match-if-let.html#matches宏 [move 移动]: https://course.rs/basic/ownership/ownership.html#转移所有权 [back](#head) From 3d24f52703a0074e38bebefc4d0110e6d5df3304 Mon Sep 17 00:00:00 2001 From: Allan Downey <AllanDowney@126.com> Date: Mon, 4 Apr 2022 03:39:20 +0800 Subject: [PATCH 08/34] update(index-list): add match Option(T) --- src/index-list.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/index-list.md b/src/index-list.md index d4ae7d59..5bc36313 100644 --- a/src/index-list.md +++ b/src/index-list.md @@ -222,12 +222,14 @@ ## O -| 名称 | 关键字 | 简介 | -| -------- | ----------- | ------------- | -| [Option] | Option 枚举 | 用于处理空值 | -| O | KWO | OIntroduction | +| 名称 | 关键字 | 简介 | +| ------------- | ----------- | --------------------------------------------------------------- | +| [Option] | Option 枚举 | 用于处理空值,**一个变量要么有值:`Some(T)`, 要么为空:`None`** | +| [Option 解构] | 模式匹配 | 可以通过 `match` 来实现 | +| O | KWO | OIntroduction | [option]: https://course.rs/basic/compound-type/enum.html#option-枚举用于处理空值 +[option 解构]: https://course.rs/basic/match-pattern/option.html#匹配-optiont [back](#head) From ddbd558611cbe02c8340047f5081ad5d6dbdd8ec Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 12:47:33 +0800 Subject: [PATCH 09/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20rayon]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 2 +- src/cookbook/cocurrency/parallel.md | 201 ++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/cookbook/cocurrency/parallel.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9b582c75..798f3074 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -274,7 +274,7 @@ - [使用.tar包](cookbook/compression/tar.md) - [并发]() - [线程](cookbook/cocurrency/threads.md) - + - [使用rayon并行处理数据](cookbook/cocurrency/parallel.md) <!-- - [配置文件解析 todo](cookbook/config.md) - [编解码 todo](cookbook/encoding/intro.md) - [JSON](cookbook/encoding/json.md) diff --git a/src/cookbook/cocurrency/parallel.md b/src/cookbook/cocurrency/parallel.md new file mode 100644 index 00000000..d1d5c521 --- /dev/null +++ b/src/cookbook/cocurrency/parallel.md @@ -0,0 +1,201 @@ +# 任务并行处理 + +### 并行修改数组中的元素 + +[rayon](https://docs.rs/rayon/1.5.1/rayon/index.html) 提供了一个 [par_iter_mut](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefMutIterator.html#tymethod.par_iter_mut) 方法用于并行化迭代一个数据集合。 + +```rust,editable +use rayon::prelude::*; + +fn main() { + let mut arr = [0, 7, 9, 11]; + arr.par_iter_mut().for_each(|p| *p -= 1); + println!("{:?}", arr); +} +``` + +### 并行测试集合中的元素是否满足给定的条件 + +[rayon::any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.any) 和 [rayon::all](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.all) 类似于 [std::any](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any) / [std::all](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all) ,但是是并行版本的。 + +- `rayon::any` 并行检查迭代器中是否有任何元素满足给定的条件,一旦发现符合条件的元素,就立即返回 +- `rayon::all` 并行检查迭代器中的所有元素是否满足给定的条件,一旦发现不满足条件的元素,就立即返回 + +```rust,editable +use rayon::prelude::*; + +fn main() { + let mut vec = vec![2, 4, 6, 8]; + + assert!(!vec.par_iter().any(|n| (*n % 2) != 0)); + assert!(vec.par_iter().all(|n| (*n % 2) == 0)); + assert!(!vec.par_iter().any(|n| *n > 8 )); + assert!(vec.par_iter().all(|n| *n <= 8 )); + + vec.push(9); + + assert!(vec.par_iter().any(|n| (*n % 2) != 0)); + assert!(!vec.par_iter().all(|n| (*n % 2) == 0)); + assert!(vec.par_iter().any(|n| *n > 8 )); + assert!(!vec.par_iter().all(|n| *n <= 8 )); +} +``` + +### 使用给定条件并行搜索 +下面例子使用 [par_iter](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefIterator.html#tymethod.par_iter) 和 [rayon::find_any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.find_any) 来并行搜索一个数组,直到找到任意一个满足条件的元素。 + +如果有多个元素满足条件,`rayon` 会返回第一个找到的元素,注意:第一个找到的元素未必是数组中的顺序最靠前的那个。 + +```rust,editable +use rayon::prelude::*; + +fn main() { + let v = vec![6, 2, 1, 9, 3, 8, 11]; + + // 这里使用了 `&&x` 的形式,大家可以在以下链接阅读更多 https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.find + let f1 = v.par_iter().find_any(|&&x| x == 9); + let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6); + let f3 = v.par_iter().find_any(|&&x| x > 8); + + assert_eq!(f1, Some(&9)); + assert_eq!(f2, Some(&8)); + assert!(f3 > Some(&8)); +} +``` + +### 对数组进行并行排序 +下面的例子将对字符串数组进行并行排序。 + +[par_sort_unstable](https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html#method.par_sort_unstable) 方法的排序性能往往要比[稳定的排序算法](https://docs.rs/rayon/1.5.1/rayon/slice/trait.ParallelSliceMut.html#method.par_sort)更高。 + + +```rust,editable +use rand::{Rng, thread_rng}; +use rand::distributions::Alphanumeric; +use rayon::prelude::*; + +fn main() { + let mut vec = vec![String::new(); 100_000]; + // 并行生成数组中的字符串 + vec.par_iter_mut().for_each(|p| { + let mut rng = thread_rng(); + *p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect() + }); + + // + vec.par_sort_unstable(); +} +``` + +### 并行化 Map-Reuduce + +下面例子使用 [rayon::filter](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.filter), [rayon::map](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.map), 和 [rayon::reduce](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.reduce) 来超过 30 岁的 `Person` 的平均年龄。 + +- `rayon::filter` 返回集合中所有满足给定条件的元素 +- `rayon::map` 对集合中的每一个元素执行一个操作,创建并返回新的迭代器,类似于[迭代器适配器](https://course.rs/advance/functional-programing/iterator.html#迭代器适配器) +- `rayon::reduce` 则迭代器的元素进行不停的聚合运算,直到获取一个最终结果,这个结果跟例子中 `rayon::sum` 获取的结果是相同的 + +```rust,editable +use rayon::prelude::*; + +struct Person { + age: u32, +} + +fn main() { + let v: Vec<Person> = vec![ + Person { age: 23 }, + Person { age: 19 }, + Person { age: 42 }, + Person { age: 17 }, + Person { age: 17 }, + Person { age: 31 }, + Person { age: 30 }, + ]; + + let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32; + let sum_over_30 = v.par_iter() + .map(|x| x.age) + .filter(|&x| x > 30) + .reduce(|| 0, |x, y| x + y); + + let alt_sum_30: u32 = v.par_iter() + .map(|x| x.age) + .filter(|&x| x > 30) + .sum(); + + let avg_over_30 = sum_over_30 as f32 / num_over_30; + let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30; + + assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON); + println!("The average age of people older than 30 is {}", avg_over_30); +} +``` + +### 并行生成缩略图 +下面例子将为目录中的所有图片并行生成缩略图,然后将结果存到新的目录 `thumbnails` 中。 + +[glob::glob_with](https://docs.rs/glob/*/glob/fn.glob_with.html) 可以找出当前目录下的所有 `.jpg` 文件,`rayon` 通过 [DynamicImage::resize](https://docs.rs/image/*/image/enum.DynamicImage.html#method.resize) 来并行调整图片的大小。 + +```rust,editable +# use error_chain::error_chain; + +use std::path::Path; +use std::fs::create_dir_all; + +# use error_chain::ChainedError; +use glob::{glob_with, MatchOptions}; +use image::{FilterType, ImageError}; +use rayon::prelude::*; + +# error_chain! { +# foreign_links { +# Image(ImageError); +# Io(std::io::Error); +# Glob(glob::PatternError); +# } +#} + +fn main() -> Result<()> { + let options: MatchOptions = Default::default(); + // 找到当前目录中的所有 `jpg` 文件 + let files: Vec<_> = glob_with("*.jpg", options)? + .filter_map(|x| x.ok()) + .collect(); + + if files.len() == 0 { + error_chain::bail!("No .jpg files found in current directory"); + } + + let thumb_dir = "thumbnails"; + create_dir_all(thumb_dir)?; + + println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir); + + let image_failures: Vec<_> = files + .par_iter() + .map(|path| { + make_thumbnail(path, thumb_dir, 300) + .map_err(|e| e.chain_err(|| path.display().to_string())) + }) + .filter_map(|x| x.err()) + .collect(); + + image_failures.iter().for_each(|x| println!("{}", x.display_chain())); + + println!("{} thumbnails saved successfully", files.len() - image_failures.len()); + Ok(()) +} + +fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<()> +where + PA: AsRef<Path>, + PB: AsRef<Path>, +{ + let img = image::open(original.as_ref())?; + let file_path = thumb_dir.as_ref().join(original); + + Ok(img.resize(longest_edge, longest_edge, FilterType::Nearest) + .save(file_path)?) +} +``` \ No newline at end of file From 3d12bd434adeb68e0ad748bb707c75ac489e4046 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 12:48:40 +0800 Subject: [PATCH 10/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20rayon]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 内容变更记录.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/内容变更记录.md b/内容变更记录.md index d1192fe0..4b301d2c 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -1,6 +1,11 @@ # ChangeLog 记录一些值得注意的变更。 +## 2022-04-04 + +- 新增章节: [Cookbook - 使用 rayon 并行处理数据](https://course.rs/cookbook/cocurrency/parallel.html) + + ## 2022-04-03 - 新增章节:[Cookbook - 线程](http://course.rs/cookbook/cocurrency/threads.html) From b7f873b5c9123bbad00eaf1c2613a60e681ce9a2 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 14:39:25 +0800 Subject: [PATCH 11/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E5=93=88=E5=B8=8C]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 9 ++++ src/cookbook/cryptography/encryption.md | 1 + src/cookbook/cryptography/hashing.md | 71 +++++++++++++++++++++++++ src/cookbook/database/postgres.md | 1 + src/cookbook/database/sqlite.md | 1 + src/cookbook/datastructures/bitfield.md | 1 + 6 files changed, 84 insertions(+) create mode 100644 src/cookbook/cryptography/encryption.md create mode 100644 src/cookbook/cryptography/hashing.md create mode 100644 src/cookbook/database/postgres.md create mode 100644 src/cookbook/database/sqlite.md create mode 100644 src/cookbook/datastructures/bitfield.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 798f3074..166218dd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -275,6 +275,15 @@ - [并发]() - [线程](cookbook/cocurrency/threads.md) - [使用rayon并行处理数据](cookbook/cocurrency/parallel.md) + - [密码学]() + - [哈希](cookbook/cryptography/hashing.md) + - [加密](cookbook/cryptography/encryption.md) + - [数据结构]() + - [位字段](cookbook/datastructures/bitfield.md) + - [数据库]() + - [SQLite](cookbook/database/sqlite.md) + - [Postgres](cookbook/database/postgres.md) + <!-- - [配置文件解析 todo](cookbook/config.md) - [编解码 todo](cookbook/encoding/intro.md) - [JSON](cookbook/encoding/json.md) diff --git a/src/cookbook/cryptography/encryption.md b/src/cookbook/cryptography/encryption.md new file mode 100644 index 00000000..c3e253eb --- /dev/null +++ b/src/cookbook/cryptography/encryption.md @@ -0,0 +1 @@ +# 加密 diff --git a/src/cookbook/cryptography/hashing.md b/src/cookbook/cryptography/hashing.md new file mode 100644 index 00000000..60a697c0 --- /dev/null +++ b/src/cookbook/cryptography/hashing.md @@ -0,0 +1,71 @@ +# 哈希 + +### 计算文件的 SHA-256 摘要 +写入一些数据到文件中,然后使用 [digest::Context](https://briansmith.org/rustdoc/ring/digest/struct.Context.html) 来计算文件内容的 SHA-256 摘要 [digest::Digest](https://briansmith.org/rustdoc/ring/digest/struct.Digest.html)。 + +```rust,editable +# use error_chain::error_chain; +use data_encoding::HEXUPPER; +use ring::digest::{Context, Digest, SHA256}; +use std::fs::File; +use std::io::{BufReader, Read, Write}; + +# error_chain! { +# foreign_links { +# Io(std::io::Error); +# Decode(data_encoding::DecodeError); +# } +# } + +fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> { + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + + Ok(context.finish()) +} + +fn main() -> Result<()> { + let path = "file.txt"; + + let mut output = File::create(path)?; + write!(output, "We will generate a digest of this text")?; + + let input = File::open(path)?; + let reader = BufReader::new(input); + let digest = sha256_digest(reader)?; + + println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref())); + + Ok(()) +} +``` + +### 使用 HMAC 摘要来签名和验证消息 +使用 [ring::hmac](https://briansmith.org/rustdoc/ring/hmac/) 创建一个字符串签名并检查该签名的正确性。 + +```rust,editable +use ring::{hmac, rand}; +use ring::rand::SecureRandom; +use ring::error::Unspecified; + +fn main() -> Result<(), Unspecified> { + let mut key_value = [0u8; 48]; + let rng = rand::SystemRandom::new(); + rng.fill(&mut key_value)?; + let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); + + let message = "Legitimate and important message."; + let signature = hmac::sign(&key, message.as_bytes()); + hmac::verify(&key, message.as_bytes(), signature.as_ref())?; + + Ok(()) +} +``` \ No newline at end of file diff --git a/src/cookbook/database/postgres.md b/src/cookbook/database/postgres.md new file mode 100644 index 00000000..a7be4c31 --- /dev/null +++ b/src/cookbook/database/postgres.md @@ -0,0 +1 @@ +# Postgres diff --git a/src/cookbook/database/sqlite.md b/src/cookbook/database/sqlite.md new file mode 100644 index 00000000..02122687 --- /dev/null +++ b/src/cookbook/database/sqlite.md @@ -0,0 +1 @@ +# SQLite diff --git a/src/cookbook/datastructures/bitfield.md b/src/cookbook/datastructures/bitfield.md new file mode 100644 index 00000000..7f16f82f --- /dev/null +++ b/src/cookbook/datastructures/bitfield.md @@ -0,0 +1 @@ +# 位字段 From 33e2dda9d3ce0744100934dd14cb0520fd286f52 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 15:04:20 +0800 Subject: [PATCH 12/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E5=8A=A0=E5=AF=86]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/cryptography/encryption.md | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/cookbook/cryptography/encryption.md b/src/cookbook/cryptography/encryption.md index c3e253eb..a728da46 100644 --- a/src/cookbook/cryptography/encryption.md +++ b/src/cookbook/cryptography/encryption.md @@ -1 +1,57 @@ # 加密 + +### 使用 PBKDF2 对密码进行哈希和加盐( salt ) +[ring::pbkdf2]() 可以对一个加盐密码进行哈希。 + +```rust,editable + +use data_encoding::HEXUPPER; +use ring::error::Unspecified; +use ring::rand::SecureRandom; +use ring::{digest, pbkdf2, rand}; +use std::num::NonZeroU32; + +fn main() -> Result<(), Unspecified> { + const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; + let n_iter = NonZeroU32::new(100_000).unwrap(); + let rng = rand::SystemRandom::new(); + + let mut salt = [0u8; CREDENTIAL_LEN]; + // 生成 salt: 将安全生成的随机数填入到字节数组中 + rng.fill(&mut salt)?; + + let password = "Guess Me If You Can!"; + let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN]; + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA512, + n_iter, + &salt, + password.as_bytes(), + &mut pbkdf2_hash, + ); + println!("Salt: {}", HEXUPPER.encode(&salt)); + println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash)); + + // `verify` 检查哈希是否正确 + let should_`succeed = pbkdf2::verify( + pbkdf2::PBKDF2_HMAC_SHA512, + n_iter, + &salt, + password.as_bytes(), + &pbkdf2_hash, + ); + let wrong_password = "Definitely not the correct password"; + let should_fail = pbkdf2::verify( + pbkdf2::PBKDF2_HMAC_SHA512, + n_iter, + &salt, + wrong_password.as_bytes(), + &pbkdf2_hash, + ); + + assert!(should_succeed.is_ok()); + assert!(!should_fail.is_ok()); + + Ok(()) +} +``` \ No newline at end of file From c6b6faa6a6b98947dbdf94253c885c9a99998635 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 16:11:30 +0800 Subject: [PATCH 13/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E4=BD=8D=E5=AD=97=E6=AE=B5]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/cryptography/encryption.md | 2 +- src/cookbook/datastructures/bitfield.md | 46 +++++++++++++++++++++++++ 内容变更记录.md | 2 ++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/cookbook/cryptography/encryption.md b/src/cookbook/cryptography/encryption.md index a728da46..becab266 100644 --- a/src/cookbook/cryptography/encryption.md +++ b/src/cookbook/cryptography/encryption.md @@ -1,7 +1,7 @@ # 加密 ### 使用 PBKDF2 对密码进行哈希和加盐( salt ) -[ring::pbkdf2]() 可以对一个加盐密码进行哈希。 +[ring::pbkdf2](https://briansmith.org/rustdoc/ring/pbkdf2/index.html) 可以对一个加盐密码进行哈希。 ```rust,editable diff --git a/src/cookbook/datastructures/bitfield.md b/src/cookbook/datastructures/bitfield.md index 7f16f82f..4c9c29e0 100644 --- a/src/cookbook/datastructures/bitfield.md +++ b/src/cookbook/datastructures/bitfield.md @@ -1 +1,47 @@ # 位字段 + +### 定义和操作位字段 +使用 [`bitflags!`](https://docs.rs/bitflags/1.3.2/bitflags/macro.bitflags.html) 宏可以帮助我们创建安全的位字段类型 `MyFlags`,然后为其实现基本的 `clear` 操作。以下代码展示了基本的位操作和格式化: +```rust,editable +use bitflags::bitflags; +use std::fmt; + +bitflags! { + struct MyFlags: u32 { + const FLAG_A = 0b00000001; + const FLAG_B = 0b00000010; + const FLAG_C = 0b00000100; + const FLAG_ABC = Self::FLAG_A.bits + | Self::FLAG_B.bits + | Self::FLAG_C.bits; + } +} + +impl MyFlags { + pub fn clear(&mut self) -> &mut MyFlags { + self.bits = 0; + self + } +} + +impl fmt::Display for MyFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:032b}", self.bits) + } +} + +fn main() { + let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C; + let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C; + assert_eq!((e1 | e2), MyFlags::FLAG_ABC); + assert_eq!((e1 & e2), MyFlags::FLAG_C); + assert_eq!((e1 - e2), MyFlags::FLAG_A); + assert_eq!(!e2, MyFlags::FLAG_A); + + let mut flags = MyFlags::FLAG_ABC; + assert_eq!(format!("{}", flags), "00000000000000000000000000000111"); + assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000"); + assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B"); + assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B"); +} +``` \ No newline at end of file diff --git a/内容变更记录.md b/内容变更记录.md index 4b301d2c..af513259 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -4,6 +4,8 @@ ## 2022-04-04 - 新增章节: [Cookbook - 使用 rayon 并行处理数据](https://course.rs/cookbook/cocurrency/parallel.html) +- 新增章节: [Cookbook - 加密](https://course.rs/cookbook/cryptography/encryption.html) +- 新增章节: [Cookbook - 哈希](https://course.rs/cookbook/cryptography/hashing.html) ## 2022-04-03 From f4cf63a55ed34ee4bbf49a64ec36ba55076231a7 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 16:12:30 +0800 Subject: [PATCH 14/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[C?= =?UTF-8?q?ookbook=20-=20=E4=BD=8D=E5=AD=97=E6=AE=B5]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 内容变更记录.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/内容变更记录.md b/内容变更记录.md index af513259..366527f9 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -6,7 +6,7 @@ - 新增章节: [Cookbook - 使用 rayon 并行处理数据](https://course.rs/cookbook/cocurrency/parallel.html) - 新增章节: [Cookbook - 加密](https://course.rs/cookbook/cryptography/encryption.html) - 新增章节: [Cookbook - 哈希](https://course.rs/cookbook/cryptography/hashing.html) - +- 新增章节: [Cookbook - 位字段](https://course.rs/cookbook/datastructures/bitfield.html) ## 2022-04-03 From 2d43a8e4d414b1792fdf663db56998e4bdeb87f9 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 16:17:28 +0800 Subject: [PATCH 15/34] update toc --- src/SUMMARY.md | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 166218dd..40047d2c 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -162,9 +162,29 @@ - [构建脚本 build.rs](cargo/reference/build-script/intro.md) - [构建脚本示例](cargo/reference/build-script/examples.md) - -# 高级专题 +# 应用实战 --- +- [CookBook doing](cookbook/intro.md) + - [实用算法](cookbook/algos/intro.md) + - [生成随机值](cookbook/algos/randomness.md) + - [Vec 排序](cookbook/algos/sorting.md) + - [命令行]() + - [参数解析](cookbook/cmd/parsing.md) + - [终端输出格式化](cookbook/cmd/ansi.md) + - [压缩]() + - [使用.tar包](cookbook/compression/tar.md) + - [并发]() + - [线程](cookbook/cocurrency/threads.md) + - [使用rayon并行处理数据](cookbook/cocurrency/parallel.md) + - [密码学]() + - [哈希](cookbook/cryptography/hashing.md) + - [加密](cookbook/cryptography/encryption.md) + - [数据结构]() + - [位字段](cookbook/datastructures/bitfield.md) + - [数据库]() + - [SQLite](cookbook/database/sqlite.md) + - [Postgres](cookbook/database/postgres.md) + - [Rust 最佳实践](practice/intro.md) - [对抗编译检查](practice/fight-with-compiler/intro.md) - [生命周期](practice/fight-with-compiler/lifetime/intro.md) @@ -231,6 +251,9 @@ - [栈上的链表](too-many-lists/advanced-lists/stack-allocated.md) +# 高级专题 +--- + - [Rust 性能优化 todo](profiling/intro.md) - [深入内存 todo](profiling/memory/intro.md) - [指针和引用 todo](profiling/memory/pointer-ref.md) @@ -263,26 +286,7 @@ - [HashMap todo](std/hashmap.md) - [Iterator 常用方法 todo](std/iterator.md) -- [CookBook doing](cookbook/intro.md) - - [实用算法](cookbook/algos/intro.md) - - [生成随机值](cookbook/algos/randomness.md) - - [Vec 排序](cookbook/algos/sorting.md) - - [命令行]() - - [参数解析](cookbook/cmd/parsing.md) - - [终端输出格式化](cookbook/cmd/ansi.md) - - [压缩]() - - [使用.tar包](cookbook/compression/tar.md) - - [并发]() - - [线程](cookbook/cocurrency/threads.md) - - [使用rayon并行处理数据](cookbook/cocurrency/parallel.md) - - [密码学]() - - [哈希](cookbook/cryptography/hashing.md) - - [加密](cookbook/cryptography/encryption.md) - - [数据结构]() - - [位字段](cookbook/datastructures/bitfield.md) - - [数据库]() - - [SQLite](cookbook/database/sqlite.md) - - [Postgres](cookbook/database/postgres.md) + <!-- - [配置文件解析 todo](cookbook/config.md) - [编解码 todo](cookbook/encoding/intro.md) From 22f95622c3aaf813b34d4721ddf52aba1be317f7 Mon Sep 17 00:00:00 2001 From: Allan Downey <AllanDowney@126.com> Date: Mon, 4 Apr 2022 17:56:00 +0800 Subject: [PATCH 16/34] update(index-list): add match list and match guard --- src/index-list.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index-list.md b/src/index-list.md index 5bc36313..f2f4047f 100644 --- a/src/index-list.md +++ b/src/index-list.md @@ -200,8 +200,10 @@ | 名称 | 关键字 | 简介 | | ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [模式绑定] | 模式匹配 | 从模式中取出绑定的值 | +| [全模式列表] | 模式匹配 | 列出了所有的模式匹配 | | [match 匹配] | 模式匹配 | `match` 的匹配必须要穷举出所有可能,因此这里用 `_ ` 来代表未列出的所有可能性<br>`match` 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同 | | [matches! 宏] | 模式匹配 | 将一个表达式跟模式进行匹配,然后返回匹配的结果 `true` 或 `false` | +| [match guard] | 匹配守卫 | 位于 `match` 分支模式之后的额外 `if` 条件,它能为分支模式提供更进一步的匹配条件 | | [move 移动] | 转移所有权 | `let s2 = s1;`<br>`s1` 所有权转移给了 `s2`,`s1` 失效 | | M | KWM | MIntroduction | @@ -209,6 +211,8 @@ [match 匹配]: https://course.rs/basic/match-pattern/match-if-let.html#match-匹配 [matches! 宏]: https://course.rs/basic/match-pattern/match-if-let.html#matches宏 [move 移动]: https://course.rs/basic/ownership/ownership.html#转移所有权 +[全模式列表]: https://course.rs/basic/match-pattern/all-patterns.html +[match guard]: https://course.rs/basic/match-pattern/all-patterns.html#匹配守卫提供的额外条件 [back](#head) From 52d0c47375bffe2320c4a4c7eb1f0eec3204308f Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 20:23:23 +0800 Subject: [PATCH 17/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=95=B0=E6=8D=AE=E5=BA=93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/database/postgres.md | 122 +++++++++++++++++++++++++++ src/cookbook/database/sqlite.md | 135 ++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) diff --git a/src/cookbook/database/postgres.md b/src/cookbook/database/postgres.md index a7be4c31..13bd37dd 100644 --- a/src/cookbook/database/postgres.md +++ b/src/cookbook/database/postgres.md @@ -1 +1,123 @@ # Postgres + +### 在数据库中创建表格 +我们通过 [postgres](https://docs.rs/postgres/0.17.2/postgres/) 来操作数据库。下面的例子有一个前提:数据库 `library` 已经存在,其中用户名和密码都是 `postgres`。 + +```rust,editable +use postgres::{Client, NoTls, Error}; + +fn main() -> Result<(), Error> { + // 连接到数据库 library + let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?; + + client.batch_execute(" + CREATE TABLE IF NOT EXISTS author ( + id SERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + country VARCHAR NOT NULL + ) + ")?; + + client.batch_execute(" + CREATE TABLE IF NOT EXISTS book ( + id SERIAL PRIMARY KEY, + title VARCHAR NOT NULL, + author_id INTEGER NOT NULL REFERENCES author + ) + ")?; + + Ok(()) + +} +``` + +### 插入和查询 + +```rust,editable +use postgres::{Client, NoTls, Error}; +use std::collections::HashMap; + +struct Author { + _id: i32, + name: String, + country: String +} + +fn main() -> Result<(), Error> { + let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", + NoTls)?; + + let mut authors = HashMap::new(); + authors.insert(String::from("Chinua Achebe"), "Nigeria"); + authors.insert(String::from("Rabindranath Tagore"), "India"); + authors.insert(String::from("Anita Nair"), "India"); + + for (key, value) in &authors { + let author = Author { + _id: 0, + name: key.to_string(), + country: value.to_string() + }; + + // 插入数据 + client.execute( + "INSERT INTO author (name, country) VALUES ($1, $2)", + &[&author.name, &author.country], + )?; + } + + // 查询数据 + for row in client.query("SELECT id, name, country FROM author", &[])? { + let author = Author { + _id: row.get(0), + name: row.get(1), + country: row.get(2), + }; + println!("Author {} is from {}", author.name, author.country); + } + + Ok(()) + +} +``` + +### 聚合数据 + +下面代码将使用降序的方式列出 [Museum of Modern Art]() 数据库中的前 7999 名艺术家的国籍分布. + +```rust,editable +use postgres::{Client, Error, NoTls}; + +struct Nation { + nationality: String, + count: i64, +} + +fn main() -> Result<(), Error> { + let mut client = Client::connect( + "postgresql://postgres:postgres@127.0.0.1/moma", + NoTls, + )?; + + for row in client.query + ("SELECT nationality, COUNT(nationality) AS count + FROM artists GROUP BY nationality ORDER BY count DESC", &[])? { + + let (nationality, count) : (Option<String>, Option<i64>) + = (row.get (0), row.get (1)); + + if nationality.is_some () && count.is_some () { + + let nation = Nation{ + nationality: nationality.unwrap(), + count: count.unwrap(), + }; + println!("{} {}", nation.nationality, nation.count); + + } + } + + Ok(()) +} +``` + diff --git a/src/cookbook/database/sqlite.md b/src/cookbook/database/sqlite.md index 02122687..0872fbf2 100644 --- a/src/cookbook/database/sqlite.md +++ b/src/cookbook/database/sqlite.md @@ -1 +1,136 @@ # SQLite + +### 创建 SQLite 数据库 + +使用 `rusqlite` 可以创建 SQLite 数据库,[Connection::open](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.open) 会尝试打开一个数据库,若不存在,则创建新的数据库。 + +> 这里创建的 `cats.db` 数据库将被后面的例子所使用 + + +```rust,editable +use rusqlite::{Connection, Result}; +use rusqlite::NO_PARAMS; + +fn main() -> Result<()> { + let conn = Connection::open("cats.db")?; + + conn.execute( + "create table if not exists cat_colors ( + id integer primary key, + name text not null unique + )", + NO_PARAMS, + )?; + conn.execute( + "create table if not exists cats ( + id integer primary key, + name text not null, + color_id integer not null references cat_colors(id) + )", + NO_PARAMS, + )?; + + Ok(()) +} +``` + +### 插入和查询 + +```rust,editable + +use rusqlite::NO_PARAMS; +use rusqlite::{Connection, Result}; +use std::collections::HashMap; + +#[derive(Debug)] +struct Cat { + name: String, + color: String, +} + +fn main() -> Result<()> { + // 打开第一个例子所创建的数据库 + let conn = Connection::open("cats.db")?; + + let mut cat_colors = HashMap::new(); + cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]); + cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]); + + for (color, catnames) in &cat_colors { + // 插入一条数据行 + conn.execute( + "INSERT INTO cat_colors (name) values (?1)", + &[&color.to_string()], + )?; + // 获取最近插入数据行的 id + let last_id: String = conn.last_insert_rowid().to_string(); + + for cat in catnames { + conn.execute( + "INSERT INTO cats (name, color_id) values (?1, ?2)", + &[&cat.to_string(), &last_id], + )?; + } + } + let mut stmt = conn.prepare( + "SELECT c.name, cc.name from cats c + INNER JOIN cat_colors cc + ON cc.id = c.color_id;", + )?; + + let cats = stmt.query_map(NO_PARAMS, |row| { + Ok(Cat { + name: row.get(0)?, + color: row.get(1)?, + }) + })?; + + for cat in cats { + println!("Found cat {:?}", cat); + } + + Ok(()) +} +``` + +### 使用事务 +使用 [Connection::transaction](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.transaction) 可以开始新的事务,若没有对事务进行显式地提交 [Transaction::commit](https://docs.rs/rusqlite/0.27.0/rusqlite/struct.Transaction.html#method.commit),则会进行回滚。 + +下面的例子中,`rolled_back_tx` 插入了重复的颜色名称,会发生回滚。 + +```rust,editable +use rusqlite::{Connection, Result, NO_PARAMS}; + +fn main() -> Result<()> { + // 打开第一个例子所创建的数据库 + let mut conn = Connection::open("cats.db")?; + + successful_tx(&mut conn)?; + + let res = rolled_back_tx(&mut conn); + assert!(res.is_err()); + + Ok(()) +} + +fn successful_tx(conn: &mut Connection) -> Result<()> { + let tx = conn.transaction()?; + + tx.execute("delete from cat_colors", NO_PARAMS)?; + tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; + tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; + + tx.commit() +} + +fn rolled_back_tx(conn: &mut Connection) -> Result<()> { + let tx = conn.transaction()?; + + tx.execute("delete from cat_colors", NO_PARAMS)?; + tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; + tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; + tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; + + tx.commit() +} +``` \ No newline at end of file From 8f44ee5ea03a70fb480b648ed63a390683416fa4 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 20:24:10 +0800 Subject: [PATCH 18/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=95=B0=E6=8D=AE=E5=BA=93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 内容变更记录.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/内容变更记录.md b/内容变更记录.md index 366527f9..9b9b38ca 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -7,6 +7,8 @@ - 新增章节: [Cookbook - 加密](https://course.rs/cookbook/cryptography/encryption.html) - 新增章节: [Cookbook - 哈希](https://course.rs/cookbook/cryptography/hashing.html) - 新增章节: [Cookbook - 位字段](https://course.rs/cookbook/datastructures/bitfield.html) +- 新增章节: [Cookbook - 数据库](https://course.rs/cookbook/database/sqlite.html) + ## 2022-04-03 From cc7281e0ffb725cbf5e863262f7240cbdd4960bc Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 20:44:04 +0800 Subject: [PATCH 19/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=97=B6=E9=97=B4=E8=AE=A1=E7=AE=97=E5=92=8C=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 9 ++++ src/cookbook/datetime/duration.md | 69 ++++++++++++++++++++++++++++ src/cookbook/datetime/parsing.md | 1 + src/cookbook/devtools/build-tools.md | 1 + src/cookbook/devtools/config-log.md | 1 + src/cookbook/devtools/log.md | 1 + src/cookbook/devtools/version.md | 1 + 7 files changed, 83 insertions(+) create mode 100644 src/cookbook/datetime/duration.md create mode 100644 src/cookbook/datetime/parsing.md create mode 100644 src/cookbook/devtools/build-tools.md create mode 100644 src/cookbook/devtools/config-log.md create mode 100644 src/cookbook/devtools/log.md create mode 100644 src/cookbook/devtools/version.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 40047d2c..ab32cbe2 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -184,6 +184,15 @@ - [数据库]() - [SQLite](cookbook/database/sqlite.md) - [Postgres](cookbook/database/postgres.md) + - [日期和时间]() + - [时间计算和转换](cookbook/datetime/duration.md) + - [解析和显示](cookbook/datetime/parsing.md) + - [开发者工具]() + - [日志](cookbook/devtools/log.md) + - [配置日志](cookbook/devtools/config-log.md) + - [版本](cookbook/devtools/version.md) + - [构建时工具](cookbook/devtools/build-tools.md) + - [Rust 最佳实践](practice/intro.md) - [对抗编译检查](practice/fight-with-compiler/intro.md) diff --git a/src/cookbook/datetime/duration.md b/src/cookbook/datetime/duration.md new file mode 100644 index 00000000..4f14bff9 --- /dev/null +++ b/src/cookbook/datetime/duration.md @@ -0,0 +1,69 @@ +# 时间计算和转换 + +### 测量某段代码的耗时 +测量从 [time::Instant::now](https://doc.rust-lang.org/std/time/struct.Instant.html#method.now) 开始所经过的时间 [time::Instant::elapsed](https://doc.rust-lang.org/std/time/struct.Instant.html#method.elapsed). + +```rust,editable +use std::time::{Duration, Instant}; + +fn main() { + let start = Instant::now(); + expensive_function(); + let duration = start.elapsed(); + + println!("Time elapsed in expensive_function() is: {:?}", duration); +} +``` + +### 对日期和时间进行计算 +使用 [DateTime::checked_add_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_add_signed) 计算和显示从现在开始两周后的日期和时间,然后再计算一天前的日期 [DateTime::checked_sub_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_sub_signed)。 + +[DateTime::format](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format) 所支持的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/*/chrono/format/strftime/index.html) 找到. + +```rust,editable +use chrono::{DateTime, Duration, Utc}; + +fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> { + date_time.checked_sub_signed(Duration::days(1)) +} + +fn main() { + let now = Utc::now(); + println!("{}", now); + + let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2)) + .and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1))) + .and_then(day_earlier); + + match almost_three_weeks_from_now { + Some(x) => println!("{}", x), + None => eprintln!("Almost three weeks from now overflows!"), + } + + match now.checked_add_signed(Duration::max_value()) { + Some(x) => println!("{}", x), + None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."), + } +} +``` + +### 将本地时间转换成其它时区 +使用 [offset::Local::now](https://docs.rs/chrono/*/chrono/offset/struct.Local.html#method.now) 获取本地时间并进行显示,接着,使用 [DateTime::from_utc](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.from_utc) 将它转换成 UTC 标准时间。最后,再使用 [offset::FixedOffset](https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html) 将 UTC 时间转换成 UTC+8 和 UTC-2 的时间。 + +```rust,editable +use chrono::{DateTime, FixedOffset, Local, Utc}; + +fn main() { + let local_time = Local::now(); + let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc); + let china_timezone = FixedOffset::east(8 * 3600); + let rio_timezone = FixedOffset::west(2 * 3600); + println!("Local time now is {}", local_time); + println!("UTC time now is {}", utc_time); + println!( + "Time in Hong Kong now is {}", + utc_time.with_timezone(&china_timezone) + ); + println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone)); +} +``` \ No newline at end of file diff --git a/src/cookbook/datetime/parsing.md b/src/cookbook/datetime/parsing.md new file mode 100644 index 00000000..7a3afa08 --- /dev/null +++ b/src/cookbook/datetime/parsing.md @@ -0,0 +1 @@ +# 解析和显示 diff --git a/src/cookbook/devtools/build-tools.md b/src/cookbook/devtools/build-tools.md new file mode 100644 index 00000000..d5694492 --- /dev/null +++ b/src/cookbook/devtools/build-tools.md @@ -0,0 +1 @@ +# 构建时工具 diff --git a/src/cookbook/devtools/config-log.md b/src/cookbook/devtools/config-log.md new file mode 100644 index 00000000..e0099ddb --- /dev/null +++ b/src/cookbook/devtools/config-log.md @@ -0,0 +1 @@ +# 配置日志 diff --git a/src/cookbook/devtools/log.md b/src/cookbook/devtools/log.md new file mode 100644 index 00000000..65c2dbdd --- /dev/null +++ b/src/cookbook/devtools/log.md @@ -0,0 +1 @@ +# 日志 diff --git a/src/cookbook/devtools/version.md b/src/cookbook/devtools/version.md new file mode 100644 index 00000000..51dd098e --- /dev/null +++ b/src/cookbook/devtools/version.md @@ -0,0 +1 @@ +# 版本 From 8e6aed737fec6c41e5da33b3f43f3ce2e12fbafb Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 21:13:23 +0800 Subject: [PATCH 20/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=97=B6=E9=97=B4=E8=A7=A3=E6=9E=90=E5=92=8C=E6=98=BE?= =?UTF-8?q?=E7=A4=BA]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/datetime/parsing.md | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/cookbook/datetime/parsing.md b/src/cookbook/datetime/parsing.md index 7a3afa08..aee55576 100644 --- a/src/cookbook/datetime/parsing.md +++ b/src/cookbook/datetime/parsing.md @@ -1 +1,114 @@ # 解析和显示 + +### 检查日期和时间 +通过 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 获取当前的 UTC 时间: +- [Timelike](https://docs.rs/chrono/*/chrono/trait.Timelike.html), 时/分/秒 +- [Datelike](https://docs.rs/chrono/*/chrono/trait.Datelike.html), 年/月/日 + +```rust,editable +use chrono::{Datelike, Timelike, Utc}; + +fn main() { + let now = Utc::now(); + + let (is_pm, hour) = now.hour12(); + println!( + "The current UTC time is {:02}:{:02}:{:02} {}", + hour, + now.minute(), + now.second(), + if is_pm { "PM" } else { "AM" } + ); + println!( + "And there have been {} seconds since midnight", + now.num_seconds_from_midnight() + ); + + let (is_common_era, year) = now.year_ce(); + println!( + "The current UTC date is {}-{:02}-{:02} {:?} ({})", + year, + now.month(), + now.day(), + now.weekday(), + if is_common_era { "CE" } else { "BCE" } + ); + println!( + "And the Common Era began {} days ago", + now.num_days_from_ce() + ); +} +``` + +### 日期和时间戳的相互转换 + +```rust,editable +use chrono::{NaiveDate, NaiveDateTime}; + +fn main() { + // 生成一个具体的日期时间 + let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44); + println!( + "Number of seconds between 1970-01-01 00:00:00 and {} is {}.", + // 打印日期和日期对应的时间戳 + date_time, date_time.timestamp()); + + // 计算从 1970 1月1日 0:00:00 UTC 开始,10亿秒后是什么日期时间 + let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0); + println!( + "Date after a billion seconds since 1970-01-01 00:00:00 was {}.", + date_time_after_a_billion_seconds); +} +``` + +### 显示格式化的日期和时间 +通过 [Utc::now](https://docs.rs/chrono/*/chrono/offset/struct.Utc.html#method.now) 可以获取当前的 UTC 时间。 + +```rust,editable +use chrono::{DateTime, Utc}; + +fn main() { + let now: DateTime<Utc> = Utc::now(); + + println!("UTC now is: {}", now); + // 使用 RFC 2822 格式显示当前时间 + println!("UTC now in RFC 2822 is: {}", now.to_rfc2822()); + // 使用 RFC 3339 格式显示当前时间 + println!("UTC now in RFC 3339 is: {}", now.to_rfc3339()); + // 使用自定义格式显示当前时间 + println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y")); +} +``` + +### 将字符串解析为 DateTime 结构体 +我们可以将多种格式的日期时间字符串转换成 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 结构体。[DateTime::parse_from_str](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.parse_from_str) 使用的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html) 找到. + +只有当能唯一的标识出日期和时间时,才能创建 `DateTime`。如果要在没有时区的情况下解析日期或时间,你需要使用 [`NativeDate`](https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html) 等函数。 + +```rust,editable +use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime}; +use chrono::format::ParseError; + + +fn main() -> Result<(), ParseError> { + let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?; + println!("{}", rfc2822); + + let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?; + println!("{}", rfc3339); + + let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?; + println!("{}", custom); + + let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?; + println!("{}", time_only); + + let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?; + println!("{}", date_only); + + let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?; + println!("{}", no_timezone); + + Ok(()) +} +``` \ No newline at end of file From 933c7164fe03b1b5fd2196eaac1345128df0381a Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 21:52:12 +0800 Subject: [PATCH 21/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=97=A5=E5=BF=97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/devtools/log.md | 126 +++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/cookbook/devtools/log.md b/src/cookbook/devtools/log.md index 65c2dbdd..adc6616a 100644 --- a/src/cookbook/devtools/log.md +++ b/src/cookbook/devtools/log.md @@ -1 +1,127 @@ # 日志 + +## log 包 +[log](https://docs.rs/crate/log/0.4.16) 提供了日志相关的实用工具。 + +### 在控制台打印 debug 信息 +`env_logger` 通过环境变量来配置日志。[log::debug!](https://docs.rs/log/0.4.16/log/macro.debug.html) 使用起来跟 [std::fmt](https://doc.rust-lang.org/std/fmt/) 中的格式化字符串很像。 + +```rust +fn execute_query(query: &str) { + log::debug!("Executing query: {}", query); +} + +fn main() { + env_logger::init(); + + execute_query("DROP TABLE students"); +} +``` + +如果大家运行代码,会发现没有任何日志输出,原因是默认的日志级别是 `error`,因此我们需要通过 `RUST_LOG` 环境变量来设置下新的日志级别: +```shell +$ RUST_LOG=debug cargo run +``` + +然后你将成功看到以下输出: +```shell +DEBUG:main: Executing query: DROP TABLE students +``` + +### 将错误日志输出到控制台 +下面我们通过 [log::error!](https://docs.rs/log/0.4.16/log/macro.error.html) 将错误日志输出到标准错误 `stderr`。 + +```rust +fn execute_query(_query: &str) -> Result<(), &'static str> { + Err("I'm afraid I can't do that") +} + +fn main() { + env_logger::init(); + + let response = execute_query("DROP TABLE students"); + if let Err(err) = response { + log::error!("Failed to execute query: {}", err); + } +} +``` + +### 将错误输出到标准输出 stdout +默认的错误会输出到标准错误输出 `stderr`,下面我们通过自定的配置来让错误输出到标准输出 `stdout`。 + +```rust,editable +use env_logger::{Builder, Target}; + +fn main() { + Builder::new() + .target(Target::Stdout) + .init(); + + log::error!("This error has been printed to Stdout"); +} +``` + +### 使用自定义 logger +下面的代码将实现一个自定义 logger `ConsoleLogger`,输出到标准输出 `stdout`。为了使用日志宏,`ConsoleLogger` 需要实现 [log::Log](https://docs.rs/log/*/log/trait.Log.html) 特征,然后使用 [log::set_logger](https://docs.rs/log/*/log/fn.set_logger.html) 来安装使用。 + +```rust,editable +use log::{Record, Level, Metadata, LevelFilter, SetLoggerError}; + +static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger; + +struct ConsoleLogger; + +impl log::Log for ConsoleLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + println!("Rust says: {} - {}", record.level(), record.args()); + } + } + + fn flush(&self) {} +} + +fn main() -> Result<(), SetLoggerError> { + log::set_logger(&CONSOLE_LOGGER)?; + log::set_max_level(LevelFilter::Info); + + log::info!("hello log"); + log::warn!("warning"); + log::error!("oops"); + Ok(()) +} +``` + +### 输出到 Unix syslog +下面的代码将使用 [syslog](https://docs.rs/crate/syslog/6.0.1) 包将日志输出到 [Unix Syslog](https://www.gnu.org/software/libc/manual/html_node/Overview-of-Syslog.html). + +```rust,editable +#[cfg(target_os = "linux")] +#[cfg(target_os = "linux")] +use syslog::{Facility, Error}; + +#[cfg(target_os = "linux")] +fn main() -> Result<(), Error> { + // 初始化 logger + syslog::init(Facility::LOG_USER, + log::LevelFilter::Debug, + // 可选的应用名称 + Some("My app name"))?; + log::debug!("this is a debug {}", "message"); + log::error!("this is an error!"); + Ok(()) +} + +#[cfg(not(target_os = "linux"))] +fn main() { + println!("So far, only Linux systems are supported."); +} +``` + + +## tracing +@todo \ No newline at end of file From 71fc75974e9a5745922e32ec1c5a5a781614a04a Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 22:13:35 +0800 Subject: [PATCH 22/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E9=85=8D=E7=BD=AE=E6=97=A5=E5=BF=97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book.toml | 2 +- src/cookbook/devtools/config-log.md | 139 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/book.toml b/book.toml index 5e439c80..22f16de7 100644 --- a/book.toml +++ b/book.toml @@ -13,7 +13,7 @@ edit-url-template = "https://github.com/sunface/rust-course/edit/main/{path}" [output.html.playground] editable = true copy-js = true -line-numbers = true +# line-numbers = true [output.html.fold] enable = true diff --git a/src/cookbook/devtools/config-log.md b/src/cookbook/devtools/config-log.md index e0099ddb..1121571f 100644 --- a/src/cookbook/devtools/config-log.md +++ b/src/cookbook/devtools/config-log.md @@ -1 +1,140 @@ # 配置日志 + +### 为每个模块开启独立的日志级别 +下面代码创建了模块 `foo` 和嵌套模块 `foo::bar`,并通过 [RUST_LOG](https://docs.rs/env_logger/*/env_logger/#enabling-logging) 环境变量对各自的日志级别进行了控制。 + +```rust,editable +mod foo { + mod bar { + pub fn run() { + log::warn!("[bar] warn"); + log::info!("[bar] info"); + log::debug!("[bar] debug"); + } + } + + pub fn run() { + log::warn!("[foo] warn"); + log::info!("[foo] info"); + log::debug!("[foo] debug"); + bar::run(); + } +} + +fn main() { + env_logger::init(); + log::warn!("[root] warn"); + log::info!("[root] info"); + log::debug!("[root] debug"); + foo::run(); +} +``` + +要让环境变量生效,首先需要通过 `env_logger::init()` 开启相关的支持。然后通过以下命令来运行程序: +```shell +RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test +``` + +此时的默认日志级别被设置为 `warn`,但我们还将 `foo` 模块级别设置为 `info`, `foo::bar` 模块日志级别设置为 `debug`。 + +```bash +WARN:test: [root] warn +WARN:test::foo: [foo] warn +INFO:test::foo: [foo] info +WARN:test::foo::bar: [bar] warn +INFO:test::foo::bar: [bar] info +DEBUG:test::foo::bar: [bar] debug +``` + +### 使用自定义环境变量来设置日志 + +[Builder](https://docs.rs/env_logger/*/env_logger/struct.Builder.html) 将对日志进行配置,以下代码使用 `MY_APP_LOG` 来替代 `RUST_LOG` 环境变量: + +```rust,editable +use std::env; +use env_logger::Builder; + +fn main() { + Builder::new() + .parse(&env::var("MY_APP_LOG").unwrap_or_default()) + .init(); + + log::info!("informational message"); + log::warn!("warning message"); + log::error!("this is an error {}", "message"); +} +``` + +### 在日志中包含时间戳 + +```rust,editable +use std::io::Write; +use chrono::Local; +use env_logger::Builder; +use log::LevelFilter; + +fn main() { + Builder::new() + .format(|buf, record| { + writeln!(buf, + "{} [{}] - {}", + Local::now().format("%Y-%m-%dT%H:%M:%S"), + record.level(), + record.args() + ) + }) + .filter(None, LevelFilter::Info) + .init(); + + log::warn!("warn"); + log::info!("info"); + log::debug!("debug"); +} +``` + +以下是 `stderr` 的输出: +```shell +2022-03-22T21:57:06 [WARN] - warn +2022-03-22T21:57:06 [INFO] - info +``` + +### 将日志输出到指定文件 +[log4rs](https://docs.rs/log4rs/) 可以帮我们将日志输出指定的位置,它可以使用外部 YAML 文件或 `builder` 的方式进行配置。 + +```rust,editable +# use error_chain::error_chain; + +use log::LevelFilter; +use log4rs::append::file::FileAppender; +use log4rs::encode::pattern::PatternEncoder; +use log4rs::config::{Appender, Config, Root}; + +#error_chain! { +# foreign_links { +# Io(std::io::Error); +# LogConfig(log4rs::config::Errors); +# SetLogger(log::SetLoggerError); +# } +#} + +fn main() -> Result<()> { + // 创建日志配置,并指定输出的位置 + let logfile = FileAppender::builder() + // 编码模式的详情参见: https://docs.rs/log4rs/1.0.0/log4rs/encode/pattern/index.html + .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) + .build("log/output.log")?; + + let config = Config::builder() + .appender(Appender::builder().build("logfile", Box::new(logfile))) + .build(Root::builder() + .appender("logfile") + .build(LevelFilter::Info))?; + + log4rs::init_config(config)?; + + log::info!("Hello, world!"); + + Ok(()) +} + +``` \ No newline at end of file From f5f102134c7883e0c7aee4456abb31aa261a772a Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 22:49:44 +0800 Subject: [PATCH 23/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E7=89=88=E6=9C=AC=E5=8F=B7]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/devtools/version.md | 185 +++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/src/cookbook/devtools/version.md b/src/cookbook/devtools/version.md index 51dd098e..70ee24e3 100644 --- a/src/cookbook/devtools/version.md +++ b/src/cookbook/devtools/version.md @@ -1 +1,186 @@ # 版本 + +### 解析并增加版本号 +下面例子使用 [Version::parse](https://docs.rs/semver/*/semver/struct.Version.html#method.parse) 将一个字符串转换成 [semver::Version](https://docs.rs/semver/*/semver/struct.Version.html) 版本号,然后将它的 patch, minor, major 版本号都增加 1。 + +注意,为了符合[语义化版本的说明](http://semver.org),增加 `minor` 版本时,`patch` 版本会被重设为 `0`,当增加 `major` 版本时,`minor` 和 `patch` 都将被重设为 `0`。 + +```rust,editable +use semver::{Version, SemVerError}; + +fn main() -> Result<(), SemVerError> { + let mut parsed_version = Version::parse("0.2.6")?; + + assert_eq!( + parsed_version, + Version { + major: 0, + minor: 2, + patch: 6, + pre: vec![], + build: vec![], + } + ); + + parsed_version.increment_patch(); + assert_eq!(parsed_version.to_string(), "0.2.7"); + println!("New patch release: v{}", parsed_version); + + parsed_version.increment_minor(); + assert_eq!(parsed_version.to_string(), "0.3.0"); + println!("New minor release: v{}", parsed_version); + + parsed_version.increment_major(); + assert_eq!(parsed_version.to_string(), "1.0.0"); + println!("New major release: v{}", parsed_version); + + Ok(()) +} +``` + +### 解析一个复杂的版本号字符串 +这里的版本号字符串还将包含 `SemVer` 中定义的预发布和构建元信息。 + +值得注意的是,为了符合 `SemVer` 的规则,构建元信息虽然会被解析,但是在做版本号比较时,该信息会被忽略。换而言之,即使两个版本号的构建字符串不同,它们的版本号依然可能相同。 + +```rust,editable +use semver::{Identifier, Version, SemVerError}; + +fn main() -> Result<(), SemVerError> { + let version_str = "1.0.49-125+g72ee7853"; + let parsed_version = Version::parse(version_str)?; + + assert_eq!( + parsed_version, + Version { + major: 1, + minor: 0, + patch: 49, + pre: vec![Identifier::Numeric(125)], + build: vec![], + } + ); + assert_eq!( + parsed_version.build, + vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] + ); + + let serialized_version = parsed_version.to_string(); + assert_eq!(&serialized_version, version_str); + + Ok(()) +} +``` + +### 检查给定的版本号是否是预发布 +下面例子给出两个版本号,然后通过 [is_prerelease](https://docs.rs/semver/1.0.7/semver/struct.Version.html#method.is_prerelease) 判断哪个是预发布的版本号。 + +```rust,editable +use semver::{Version, SemVerError}; + +fn main() -> Result<(), SemVerError> { + let version_1 = Version::parse("1.0.0-alpha")?; + let version_2 = Version::parse("1.0.0")?; + + assert!(version_1.is_prerelease()); + assert!(!version_2.is_prerelease()); + + Ok(()) +} +``` + +### 找出给定范围内的最新版本 +下面例子给出了一个版本号列表,我们需要找到其中最新的版本。 + +```rust,editable +#use error_chain::error_chain; + +use semver::{Version, VersionReq}; + +#error_chain! { +# foreign_links { +# SemVer(semver::SemVerError); +# SemVerReq(semver::ReqParseError); +# } +3} + +fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>> +where + I: IntoIterator<Item = &'a str>, +{ + let vreq = VersionReq::parse(version_req_str)?; + + Ok( + iterable + .into_iter() + .filter_map(|s| Version::parse(s).ok()) + .filter(|s| vreq.matches(s)) + .max(), + ) +} + +fn main() -> Result<()> { + assert_eq!( + find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, + Some(Version::parse("1.0.0")?) + ); + + assert_eq!( + find_max_matching_version( + ">1.2.3-alpha.3", + vec![ + "1.2.3-alpha.3", + "1.2.3-alpha.4", + "1.2.3-alpha.10", + "1.2.3-beta.4", + "3.4.5-alpha.9", + ] + )?, + Some(Version::parse("1.2.3-beta.4")?) + ); + + Ok(()) +} +``` + +### 检查外部命令的版本号兼容性 +下面将通过 [Command](https://doc.rust-lang.org/std/process/struct.Command.html) 来执行系统命令 `git --version`,并对该系统命令返回的 `git` 版本号进行解析。 + +```rust,editable +#use error_chain::error_chain; + +use std::process::Command; +use semver::{Version, VersionReq}; + +#error_chain! { +# foreign_links { +# Io(std::io::Error); +# Utf8(std::string::FromUtf8Error); +# SemVer(semver::SemVerError); +# SemVerReq(semver::ReqParseError); +# } +#} + +fn main() -> Result<()> { + let version_constraint = "> 1.12.0"; + let version_test = VersionReq::parse(version_constraint)?; + let output = Command::new("git").arg("--version").output()?; + + if !output.status.success() { + error_chain::bail!("Command executed with failing error code"); + } + + let stdout = String::from_utf8(output.stdout)?; + let version = stdout.split(" ").last().ok_or_else(|| { + "Invalid command output" + })?; + let parsed_version = Version::parse(version)?; + + if !version_test.matches(&parsed_version) { + error_chain::bail!("Command version lower than minimum supported version (found {}, need {})", + parsed_version, version_constraint); + } + + Ok(()) +} +``` \ No newline at end of file From 327e7c42f0b616fd4b67ee6b5de4f6032a7fa5e4 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 22:51:30 +0800 Subject: [PATCH 24/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E7=89=88=E6=9C=AC=E5=8F=B7]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 2 +- src/cookbook/devtools/version.md | 2 +- 内容变更记录.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ab32cbe2..51a325e9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -190,7 +190,7 @@ - [开发者工具]() - [日志](cookbook/devtools/log.md) - [配置日志](cookbook/devtools/config-log.md) - - [版本](cookbook/devtools/version.md) + - [版本号](cookbook/devtools/version.md) - [构建时工具](cookbook/devtools/build-tools.md) diff --git a/src/cookbook/devtools/version.md b/src/cookbook/devtools/version.md index 70ee24e3..2d9ff997 100644 --- a/src/cookbook/devtools/version.md +++ b/src/cookbook/devtools/version.md @@ -1,4 +1,4 @@ -# 版本 +# 版本号 ### 解析并增加版本号 下面例子使用 [Version::parse](https://docs.rs/semver/*/semver/struct.Version.html#method.parse) 将一个字符串转换成 [semver::Version](https://docs.rs/semver/*/semver/struct.Version.html) 版本号,然后将它的 patch, minor, major 版本号都增加 1。 diff --git a/内容变更记录.md b/内容变更记录.md index 9b9b38ca..1cd8c8f0 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -8,7 +8,7 @@ - 新增章节: [Cookbook - 哈希](https://course.rs/cookbook/cryptography/hashing.html) - 新增章节: [Cookbook - 位字段](https://course.rs/cookbook/datastructures/bitfield.html) - 新增章节: [Cookbook - 数据库](https://course.rs/cookbook/database/sqlite.html) - +- 新增章节: [Cookbook - 开发者工具](https://course.rs/cookbook/devtools/log.html) ## 2022-04-03 From ff35468af6f43ae19a3c87b394a423d43709003d Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Mon, 4 Apr 2022 23:22:03 +0800 Subject: [PATCH 25/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=20[Cookbook?= =?UTF-8?q?=20-=20=E6=9E=84=E5=BB=BA=E5=B7=A5=E5=85=B7]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cookbook/devtools/build-tools.md | 191 +++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/src/cookbook/devtools/build-tools.md b/src/cookbook/devtools/build-tools.md index d5694492..b3bdc6ad 100644 --- a/src/cookbook/devtools/build-tools.md +++ b/src/cookbook/devtools/build-tools.md @@ -1 +1,192 @@ # 构建时工具 +本章节的内容是关于构建工具的,如果大家没有听说过 `build.rs` 文件,强烈建议先看看[这里](https://course.rs/cargo/reference/build-script/intro.html)了解下何为构建工具。 + +### 编译并静态链接一个 C 库 + +[cc](https://docs.rs/cc/latest/cc/) 包能帮助我们更好地跟 C/C++/汇编进行交互:它提供了简单的 API 可以将外部的库编译成静态库( .a ),然后通过 `rustc` 进行静态链接。 + +下面的例子中,我们将在 Rust 代码中使用 C 的代码: *src/hello.c*。在开始编译 Rust 的项目代码前,`build.rs` 构建脚本将先被执行。通过 cc 包,一个静态的库可以被生成( *libhello.a* ),然后该库将被 Rust的代码所使用:通过 `extern` 声明外部函数签名的方式来使用。 + +由于例子中的 C 代码很简单,因此只需要将一个文件传递给 [cc::Build](https://docs.rs/cc/*/cc/struct.Build.html)。如果大家需要更复杂的构建,`cc::Build` 还提供了通过 [include](https://docs.rs/cc/*/cc/struct.Build.html#method.include) 来包含路径的方式,以及额外的编译标志( [flags](https://docs.rs/cc/1.0.73/cc/struct.Build.html#method.flag) )。 + +*Cargo.toml* + +```toml +[package] +... +build = "build.rs" + +[build-dependencies] +cc = "1" + +[dependencies] +error-chain = "0.11" +``` + +*build.rs* + +```rust +fn main() { + cc::Build::new() + .file("src/hello.c") + .compile("hello"); // outputs `libhello.a` +} +``` + +*src/hello.c* + +```C +#include <stdio.h> + + +void hello() { + printf("Hello from C!\n"); +} + +void greet(const char* name) { + printf("Hello, %s!\n", name); +} +``` + +*src/main.rs* + +```rust +use error_chain::error_chain; +use std::ffi::CString; +use std::os::raw::c_char; + +error_chain! { + foreign_links { + NulError(::std::ffi::NulError); + Io(::std::io::Error); + } +} +fn prompt(s: &str) -> Result<String> { + use std::io::Write; + print!("{}", s); + std::io::stdout().flush()?; + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + Ok(input.trim().to_string()) +} + +extern { + fn hello(); + fn greet(name: *const c_char); +} + +fn main() -> Result<()> { + unsafe { hello() } + let name = prompt("What's your name? ")?; + let c_name = CString::new(name)?; + unsafe { greet(c_name.as_ptr()) } + Ok(()) +} +``` + +### 编译并静态链接一个 C++ 库 +链接到 C++ 库跟之前的方式非常相似。主要的区别在于链接到 C++ 库时,你需要通过构建方法 [cpp(true)](https://docs.rs/cc/*/cc/struct.Build.html#method.cpp) 来指定一个 C++ 编译器,然后在 C++ 的代码顶部添加 `extern "C"` 来阻止 C++ 编译器对库名进行名称重整( name mangling )。 + +*Cargo.toml* + +```toml +[package] +... +build = "build.rs" + +[build-dependencies] +cc = "1" +``` + +*build.rs* + +```rust +fn main() { + cc::Build::new() + .cpp(true) + .file("src/foo.cpp") + .compile("foo"); +} +``` + +*src/foo.cpp* + +```c++ +extern "C" { + int multiply(int x, int y); +} + +int multiply(int x, int y) { + return x*y; +} +``` + +*src/main.rs* + +```rust +extern { + fn multiply(x : i32, y : i32) -> i32; +} + +fn main(){ + unsafe { + println!("{}", multiply(5,7)); + } +} +``` + +### 为 C 库创建自定义的 define + +[cc::Build::define](https://docs.rs/cc/*/cc/struct.Build.html#method.define) 可以让我们使用自定义的 define 来构建 C 库。 + +以下示例在构建脚本 `build.rs` 中动态定义了一个 define,然后在运行时打印出 **Welcome to foo - version 1.0.2**。Cargo 会设置一些[环境变量](https://doc.rust-lang.org/cargo/reference/environment-variables.html),它们对于自定义的 define 会有所帮助。 + +*Cargo.toml* + +```toml +[package] +... +version = "1.0.2" +build = "build.rs" + +[build-dependencies] +cc = "1" +``` + +*build.rs* +```rust +fn main() { + cc::Build::new() + .define("APP_NAME", "\"foo\"") + .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) + .define("WELCOME", None) + .file("src/foo.c") + .compile("foo"); +} +``` + +*src/foo.c* +```C +#include <stdio.h> + +void print_app_info() { +#ifdef WELCOME + printf("Welcome to "); +#endif + printf("%s - version %s\n", APP_NAME, VERSION); +} +``` + +*src/main.rs* +```rust +extern { + fn print_app_info(); +} + +fn main(){ + unsafe { + print_app_info(); + } +} +``` + From 96834bf18be0b480885923c7361b2d0308e3d277 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 13:26:06 +0800 Subject: [PATCH 26/34] update readme.md --- README.md | 4 +- src/SUMMARY.md | 30 +-- src/cookbook/algos/intro.md | 4 - src/cookbook/algos/randomness.md | 155 ----------- src/cookbook/algos/sorting.md | 84 ------ src/cookbook/cmd/ansi.md | 50 ---- src/cookbook/cmd/parsing.md | 70 ----- src/cookbook/cocurrency/parallel.md | 201 -------------- src/cookbook/cocurrency/threads.md | 336 ------------------------ src/cookbook/compression/tar.md | 77 ------ src/cookbook/config.md | 1 - src/cookbook/crypto.md | 1 - src/cookbook/cryptography/encryption.md | 57 ---- src/cookbook/cryptography/hashing.md | 71 ----- src/cookbook/database.md | 1 - src/cookbook/database/postgres.md | 123 --------- src/cookbook/database/sqlite.md | 136 ---------- src/cookbook/datastructures/bitfield.md | 47 ---- src/cookbook/date.md | 1 - src/cookbook/datetime/duration.md | 69 ----- src/cookbook/datetime/parsing.md | 114 -------- src/cookbook/dev/intro.md | 1 - src/cookbook/dev/logs.md | 1 - src/cookbook/dev/profile.md | 1 - src/cookbook/devtools/build-tools.md | 192 -------------- src/cookbook/devtools/config-log.md | 140 ---------- src/cookbook/devtools/log.md | 127 --------- src/cookbook/devtools/version.md | 186 ------------- src/cookbook/encoding/csv.md | 1 - src/cookbook/encoding/intro.md | 1 - src/cookbook/encoding/json.md | 1 - src/cookbook/encoding/protobuf.md | 1 - src/cookbook/file/dir.md | 1 - src/cookbook/file/file.md | 1 - src/cookbook/file/intro.md | 1 - src/cookbook/intro.md | 6 + src/cookbook/protocol/grpc.md | 1 - src/cookbook/protocol/http.md | 1 - src/cookbook/protocol/intro.md | 1 - src/cookbook/protocol/tcp.md | 1 - src/cookbook/protocol/udp.md | 1 - src/cookbook/regexp.md | 1 - 42 files changed, 9 insertions(+), 2290 deletions(-) delete mode 100644 src/cookbook/algos/intro.md delete mode 100644 src/cookbook/algos/randomness.md delete mode 100644 src/cookbook/algos/sorting.md delete mode 100644 src/cookbook/cmd/ansi.md delete mode 100644 src/cookbook/cmd/parsing.md delete mode 100644 src/cookbook/cocurrency/parallel.md delete mode 100644 src/cookbook/cocurrency/threads.md delete mode 100644 src/cookbook/compression/tar.md delete mode 100644 src/cookbook/config.md delete mode 100644 src/cookbook/crypto.md delete mode 100644 src/cookbook/cryptography/encryption.md delete mode 100644 src/cookbook/cryptography/hashing.md delete mode 100644 src/cookbook/database.md delete mode 100644 src/cookbook/database/postgres.md delete mode 100644 src/cookbook/database/sqlite.md delete mode 100644 src/cookbook/datastructures/bitfield.md delete mode 100644 src/cookbook/date.md delete mode 100644 src/cookbook/datetime/duration.md delete mode 100644 src/cookbook/datetime/parsing.md delete mode 100644 src/cookbook/dev/intro.md delete mode 100644 src/cookbook/dev/logs.md delete mode 100644 src/cookbook/dev/profile.md delete mode 100644 src/cookbook/devtools/build-tools.md delete mode 100644 src/cookbook/devtools/config-log.md delete mode 100644 src/cookbook/devtools/log.md delete mode 100644 src/cookbook/devtools/version.md delete mode 100644 src/cookbook/encoding/csv.md delete mode 100644 src/cookbook/encoding/intro.md delete mode 100644 src/cookbook/encoding/json.md delete mode 100644 src/cookbook/encoding/protobuf.md delete mode 100644 src/cookbook/file/dir.md delete mode 100644 src/cookbook/file/file.md delete mode 100644 src/cookbook/file/intro.md delete mode 100644 src/cookbook/protocol/grpc.md delete mode 100644 src/cookbook/protocol/http.md delete mode 100644 src/cookbook/protocol/intro.md delete mode 100644 src/cookbook/protocol/tcp.md delete mode 100644 src/cookbook/protocol/udp.md delete mode 100644 src/cookbook/regexp.md diff --git a/README.md b/README.md index 45482ff4..25b9919d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ - **规避陷阱和对抗编译器**,只有真的上手写过一长段时间 Rust 项目,才知道该如何规避常见的陷阱以及解决一些难搞的编译器错误,而本书将帮助你大大缩短这个过程,提前规避这些问题 -- **Cook Book**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 +- **[Cookbook](https://cookbook.rs)**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 -- **配套练习题**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + [Rust语言实战](https://github.com/sunface/rust-by-practice)* 双剑合璧,给你最极致的学习体验 +- **[配套练习题](https://github.com/sunface/rust-by-practice)**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + Rust语言实战* 双剑合璧,给你最极致的学习体验 总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。 diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 51a325e9..7358fe75 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -164,35 +164,7 @@ # 应用实战 --- -- [CookBook doing](cookbook/intro.md) - - [实用算法](cookbook/algos/intro.md) - - [生成随机值](cookbook/algos/randomness.md) - - [Vec 排序](cookbook/algos/sorting.md) - - [命令行]() - - [参数解析](cookbook/cmd/parsing.md) - - [终端输出格式化](cookbook/cmd/ansi.md) - - [压缩]() - - [使用.tar包](cookbook/compression/tar.md) - - [并发]() - - [线程](cookbook/cocurrency/threads.md) - - [使用rayon并行处理数据](cookbook/cocurrency/parallel.md) - - [密码学]() - - [哈希](cookbook/cryptography/hashing.md) - - [加密](cookbook/cryptography/encryption.md) - - [数据结构]() - - [位字段](cookbook/datastructures/bitfield.md) - - [数据库]() - - [SQLite](cookbook/database/sqlite.md) - - [Postgres](cookbook/database/postgres.md) - - [日期和时间]() - - [时间计算和转换](cookbook/datetime/duration.md) - - [解析和显示](cookbook/datetime/parsing.md) - - [开发者工具]() - - [日志](cookbook/devtools/log.md) - - [配置日志](cookbook/devtools/config-log.md) - - [版本号](cookbook/devtools/version.md) - - [构建时工具](cookbook/devtools/build-tools.md) - +- [CookBook](cookbook/intro.md) - [Rust 最佳实践](practice/intro.md) - [对抗编译检查](practice/fight-with-compiler/intro.md) diff --git a/src/cookbook/algos/intro.md b/src/cookbook/algos/intro.md deleted file mode 100644 index ace373bf..00000000 --- a/src/cookbook/algos/intro.md +++ /dev/null @@ -1,4 +0,0 @@ -# 实用算法 -本章将收集一些在实战中经常使用的算法 API。 - -> Note: 这里没有具体的算法实现,都是关于如何应用的 \ No newline at end of file diff --git a/src/cookbook/algos/randomness.md b/src/cookbook/algos/randomness.md deleted file mode 100644 index 04241a7e..00000000 --- a/src/cookbook/algos/randomness.md +++ /dev/null @@ -1,155 +0,0 @@ -# 生成随机值 - -### 生成随机数 - -使用 [rand::thread_rng](https://docs.rs/rand/*/rand/fn.thread_rng.html) 可以获取一个随机数生成器 [rand::Rng](https://docs.rs/rand/0.8.5/rand/trait.Rng.html) ,该生成器需要在每个线程都初始化一个。 - -整数的随机分布范围等于类型的取值范围,但是浮点数只分布在 `[0, 1)` 区间内。 - -```rust,editable -use rand::Rng; - -fn main() { - let mut rng = rand::thread_rng(); - - let n1: u8 = rng.gen(); - let n2: u16 = rng.gen(); - println!("Random u8: {}", n1); - println!("Random u16: {}", n2); - println!("Random u32: {}", rng.gen::<u32>()); - println!("Random i32: {}", rng.gen::<i32>()); - println!("Random float: {}", rng.gen::<f64>()); -} -``` - -### 指定范围生成随机数 - -使用 [Rng::gen_range](https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html) 生成 [0, 10) 区间内的随机数( 右开区间,不包括 `10` )。 -```rust,editable -use rand::Rng; - -fn main() { - let mut rng = rand::thread_rng(); - println!("Integer: {}", rng.gen_range(0..10)); - println!("Float: {}", rng.gen_range(0.0..10.0)); -} -``` - -[Uniform](https://docs.rs/rand/*/rand/distributions/uniform/struct.Uniform.html) 可以用于生成<ruby>均匀分布<rt>uniform distribution</rt></ruby>的随机数。当需要在同一个范围内重复生成随机数时,该方法虽然和之前的方法效果一样,但会更快一些。 - -```rust,editable -use rand::distributions::{Distribution, Uniform}; - -fn main() { - let mut rng = rand::thread_rng(); - let die = Uniform::from(1..7); - - loop { - let throw = die.sample(&mut rng); - println!("Roll the die: {}", throw); - if throw == 6 { - break; - } - } -} -``` - -### 使用指定分布来生成随机数 - -默认情况下,`rand` 包使用均匀分布来生成随机数,而 [rand_distr](https://docs.rs/rand_distr/*/rand_distr/index.html) 包提供了其它类型的分布方式。 - -首先,你需要获取想要使用的分布的实例,然后在 [rand::Rng](https://docs.rs/rand/*/rand/trait.Rng.html) 的帮助下使用 [Distribution::sample](https://docs.rs/rand/*/rand/distributions/trait.Distribution.html#tymethod.sample) 对该实例进行取样。 - -如果想要查询可用的分布列表,可以访问[这里](https://docs.rs/rand_distr/*/rand_distr/index.html),下面的示例中我们将使用 [Normal](https://docs.rs/rand_distr/0.4.3/rand_distr/struct.Normal.html) 分布: -```rust,editable -use rand_distr::{Distribution, Normal, NormalError}; -use rand::thread_rng; - -fn main() -> Result<(), NormalError> { - let mut rng = thread_rng(); - let normal = Normal::new(2.0, 3.0)?; - let v = normal.sample(&mut rng); - println!("{} is from a N(2, 9) distribution", v); - Ok(()) -} -``` - -### 在自定义类型中生成随机值 - - -使用 [Distribution](https://docs.rs/rand/*/rand/distributions/trait.Distribution.html) 特征包裹我们的自定义类型,并为 [Standard](https://docs.rs/rand/*/rand/distributions/struct.Standard.html) 实现该特征,可以为自定义类型的指定字段生成随机数。 - - -```rust,editable -use rand::Rng; -use rand::distributions::{Distribution, Standard}; - -#[derive(Debug)] -struct Point { - x: i32, - y: i32, -} - -impl Distribution<Point> for Standard { - fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point { - let (rand_x, rand_y) = rng.gen(); - Point { - x: rand_x, - y: rand_y, - } - } -} - -fn main() { - let mut rng = rand::thread_rng(); - - // 生成一个随机的 Point - let rand_point: Point = rng.gen(); - println!("Random Point: {:?}", rand_point); - - // 通过类型暗示( hint )生成一个随机的元组 - let rand_tuple = rng.gen::<(i32, bool, f64)>(); - println!("Random tuple: {:?}", rand_tuple); -} -``` - -### 生成随机的字符串(A-Z, a-z, 0-9) -通过 [Alphanumeric](https://docs.rs/rand/0.8.5/rand/distributions/struct.Alphanumeric.html) 采样来生成随机的 ASCII 字符串,包含从 `A-Z, a-z, 0-9` 的字符。 - -```rust,editble -use rand::{thread_rng, Rng}; -use rand::distributions::Alphanumeric; - -fn main() { - let rand_string: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(30) - .map(char::from) - .collect(); - - println!("{}", rand_string); -} -``` - -### 生成随机的字符串( 用户指定 ASCII 字符 ) -通过 [gen_string](https://docs.rs/rand/0.8.5/rand/trait.Rng.html#method.gen_range) 生成随机的 ASCII 字符串,包含用户指定的字符。 - -```rust,editable -fn main() { - use rand::Rng; - const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789)(*&^%$#@!~"; - const PASSWORD_LEN: usize = 30; - let mut rng = rand::thread_rng(); - - let password: String = (0..PASSWORD_LEN) - .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect(); - - println!("{:?}", password); -} -``` diff --git a/src/cookbook/algos/sorting.md b/src/cookbook/algos/sorting.md deleted file mode 100644 index 76e44e8c..00000000 --- a/src/cookbook/algos/sorting.md +++ /dev/null @@ -1,84 +0,0 @@ -## Vector 排序 - - -### 对整数 Vector 排序 - -以下示例使用 [Vec::sort](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort) 来排序,如果大家希望获得更高的性能,可以使用 [Vec::sort_unstable](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_unstable),但是该方法无法保留相等元素的顺序。 - -```rust,editable -fn main() { - let mut vec = vec![1, 5, 10, 2, 15]; - - vec.sort(); - - assert_eq!(vec, vec![1, 2, 5, 10, 15]); -} -``` - -### 对浮点数 Vector 排序 - -浮点数数组可以使用 [Vec::sort_by](https://doc.rust-lang.org/std/primitive.slice.html#method.sort_by) 和 [PartialOrd::partial_cmp](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html#tymethod.partial_cmp) 进行排序。 - -```rust,editable -fn main() { - let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0]; - - vec.sort_by(|a, b| a.partial_cmp(b).unwrap()); - - assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]); -} -``` - -### 对结构体 Vector 排序 - -以下示例中的结构体 `Person` 将实现基于字段 `name` 和 `age` 的自然排序。为了让 `Person` 变为可排序的,我们需要为其派生 `Eq、PartialEq、Ord、PartialOrd` 特征,关于这几个特征的详情,请见[这里](https://course.rs/advance/confonding/eq.html)。 - -当然,还可以使用 [vec:sort_by](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_by) 方法配合一个自定义比较函数,只按照 `age` 的维度对 `Person` 数组排序。 - -```rust,editable -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -struct Person { - name: String, - age: u32 -} - -impl Person { - pub fn new(name: String, age: u32) -> Self { - Person { - name, - age - } - } -} - -fn main() { - let mut people = vec![ - Person::new("Zoe".to_string(), 25), - Person::new("Al".to_string(), 60), - Person::new("John".to_string(), 1), - ]; - - // 通过派生后的自然顺序(Name and age)排序 - people.sort(); - - assert_eq!( - people, - vec![ - Person::new("Al".to_string(), 60), - Person::new("John".to_string(), 1), - Person::new("Zoe".to_string(), 25), - ]); - - // 只通过 age 排序 - people.sort_by(|a, b| b.age.cmp(&a.age)); - - assert_eq!( - people, - vec![ - Person::new("Al".to_string(), 60), - Person::new("Zoe".to_string(), 25), - Person::new("John".to_string(), 1), - ]); - -} -``` \ No newline at end of file diff --git a/src/cookbook/cmd/ansi.md b/src/cookbook/cmd/ansi.md deleted file mode 100644 index 87c18682..00000000 --- a/src/cookbook/cmd/ansi.md +++ /dev/null @@ -1,50 +0,0 @@ -# ANSI 终端 - -[ansi_term](https://crates.io/crates/ansi_term) 包可以帮我们控制终端上的输出样式,例如使用颜色文字、控制输出格式等,当然,前提是在 ANSI 终端上。 - -`ansi_term` 中有两个主要数据结构:[ANSIString](https://docs.rs/ansi_term/0.12.1/ansi_term/type.ANSIString.html) 和 [Style](https://docs.rs/ansi_term/0.12.1/ansi_term/struct.Style.html)。 - -`Style` 用于控制样式:颜色、加粗、闪烁等,而前者是一个带有样式的字符串。 - -## 颜色字体 - -```rust,editable -use ansi_term::Colour; - -fn main() { - println!("This is {} in color, {} in color and {} in color", - Colour::Red.paint("red"), - Colour::Blue.paint("blue"), - Colour::Green.paint("green")); -} -``` - -## 加粗字体 - -比颜色复杂的样式构建需要使用 `Style` 结构体: -```rust,editable -use ansi_term::Style; - -fn main() { - println!("{} and this is not", - Style::new().bold().paint("This is Bold")); -} -``` - -## 加粗和颜色 - -`Colour` 实现了很多跟 `Style` 类似的函数,因此可以实现链式调用。 - -```rust,editable -use ansi_term::Colour; -use ansi_term::Style; - -fn main(){ - println!("{}, {} and {}", - Colour::Yellow.paint("This is colored"), - Style::new().bold().paint("this is bold"), - // Colour 也可以使用 bold 方法进行加粗 - Colour::Yellow.bold().paint("this is bold and colored")); -} -``` - diff --git a/src/cookbook/cmd/parsing.md b/src/cookbook/cmd/parsing.md deleted file mode 100644 index b0356aef..00000000 --- a/src/cookbook/cmd/parsing.md +++ /dev/null @@ -1,70 +0,0 @@ -# 参数解析 - -## Clap -下面的程序给出了使用 `clap` 来解析命令行参数的样式结构,如果大家想了解更多,在 `clap` [文档](https://docs.rs/clap/)中还给出了另外两种初始化一个应用的方式。 - -在下面的构建中,`value_of` 将获取通过 `with_name` 解析出的值。`short` 和 `long` 用于设置用户输入的长短命令格式,例如短命令 `-f` 和长命令 `--file`。 - -```rust,editable -use clap::{Arg, App}; - -fn main() { - let matches = App::new("My Test Program") - .version("0.1.0") - .author("Hackerman Jones <hckrmnjones@hack.gov>") - .about("Teaches argument parsing") - .arg(Arg::with_name("file") - .short("f") - .long("file") - .takes_value(true) - .help("A cool file")) - .arg(Arg::with_name("num") - .short("n") - .long("number") - .takes_value(true) - .help("Five less than your favorite number")) - .get_matches(); - - let myfile = matches.value_of("file").unwrap_or("input.txt"); - println!("The file passed is: {}", myfile); - - let num_str = matches.value_of("num"); - match num_str { - None => println!("No idea what your favorite number is."), - Some(s) => { - match s.parse::<i32>() { - Ok(n) => println!("Your favorite number must be {}.", n + 5), - Err(_) => println!("That's not a number! {}", s), - } - } - } -} -``` - -`clap` 针对上面提供的构建样式,会自动帮我们生成相应的使用方式说明。例如,上面代码生成的使用说明如下: -```shell -My Test Program 0.1.0 -Hackerman Jones <hckrmnjones@hack.gov> -Teaches argument parsing - -USAGE: - testing [OPTIONS] - -FLAGS: - -h, --help Prints help information - -V, --version Prints version information - -OPTIONS: - -f, --file <file> A cool file - -n, --number <num> Five less than your favorite number -``` - -最后,再使用一些参数来运行下我们的代码: -```shell -$ cargo run -- -f myfile.txt -n 251 -The file passed is: myfile.txt -Your favorite number must be 256. -``` - -## Structopt -@todo \ No newline at end of file diff --git a/src/cookbook/cocurrency/parallel.md b/src/cookbook/cocurrency/parallel.md deleted file mode 100644 index d1d5c521..00000000 --- a/src/cookbook/cocurrency/parallel.md +++ /dev/null @@ -1,201 +0,0 @@ -# 任务并行处理 - -### 并行修改数组中的元素 - -[rayon](https://docs.rs/rayon/1.5.1/rayon/index.html) 提供了一个 [par_iter_mut](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefMutIterator.html#tymethod.par_iter_mut) 方法用于并行化迭代一个数据集合。 - -```rust,editable -use rayon::prelude::*; - -fn main() { - let mut arr = [0, 7, 9, 11]; - arr.par_iter_mut().for_each(|p| *p -= 1); - println!("{:?}", arr); -} -``` - -### 并行测试集合中的元素是否满足给定的条件 - -[rayon::any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.any) 和 [rayon::all](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.all) 类似于 [std::any](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any) / [std::all](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all) ,但是是并行版本的。 - -- `rayon::any` 并行检查迭代器中是否有任何元素满足给定的条件,一旦发现符合条件的元素,就立即返回 -- `rayon::all` 并行检查迭代器中的所有元素是否满足给定的条件,一旦发现不满足条件的元素,就立即返回 - -```rust,editable -use rayon::prelude::*; - -fn main() { - let mut vec = vec![2, 4, 6, 8]; - - assert!(!vec.par_iter().any(|n| (*n % 2) != 0)); - assert!(vec.par_iter().all(|n| (*n % 2) == 0)); - assert!(!vec.par_iter().any(|n| *n > 8 )); - assert!(vec.par_iter().all(|n| *n <= 8 )); - - vec.push(9); - - assert!(vec.par_iter().any(|n| (*n % 2) != 0)); - assert!(!vec.par_iter().all(|n| (*n % 2) == 0)); - assert!(vec.par_iter().any(|n| *n > 8 )); - assert!(!vec.par_iter().all(|n| *n <= 8 )); -} -``` - -### 使用给定条件并行搜索 -下面例子使用 [par_iter](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefIterator.html#tymethod.par_iter) 和 [rayon::find_any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.find_any) 来并行搜索一个数组,直到找到任意一个满足条件的元素。 - -如果有多个元素满足条件,`rayon` 会返回第一个找到的元素,注意:第一个找到的元素未必是数组中的顺序最靠前的那个。 - -```rust,editable -use rayon::prelude::*; - -fn main() { - let v = vec![6, 2, 1, 9, 3, 8, 11]; - - // 这里使用了 `&&x` 的形式,大家可以在以下链接阅读更多 https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.find - let f1 = v.par_iter().find_any(|&&x| x == 9); - let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6); - let f3 = v.par_iter().find_any(|&&x| x > 8); - - assert_eq!(f1, Some(&9)); - assert_eq!(f2, Some(&8)); - assert!(f3 > Some(&8)); -} -``` - -### 对数组进行并行排序 -下面的例子将对字符串数组进行并行排序。 - -[par_sort_unstable](https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html#method.par_sort_unstable) 方法的排序性能往往要比[稳定的排序算法](https://docs.rs/rayon/1.5.1/rayon/slice/trait.ParallelSliceMut.html#method.par_sort)更高。 - - -```rust,editable -use rand::{Rng, thread_rng}; -use rand::distributions::Alphanumeric; -use rayon::prelude::*; - -fn main() { - let mut vec = vec![String::new(); 100_000]; - // 并行生成数组中的字符串 - vec.par_iter_mut().for_each(|p| { - let mut rng = thread_rng(); - *p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect() - }); - - // - vec.par_sort_unstable(); -} -``` - -### 并行化 Map-Reuduce - -下面例子使用 [rayon::filter](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.filter), [rayon::map](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.map), 和 [rayon::reduce](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.reduce) 来超过 30 岁的 `Person` 的平均年龄。 - -- `rayon::filter` 返回集合中所有满足给定条件的元素 -- `rayon::map` 对集合中的每一个元素执行一个操作,创建并返回新的迭代器,类似于[迭代器适配器](https://course.rs/advance/functional-programing/iterator.html#迭代器适配器) -- `rayon::reduce` 则迭代器的元素进行不停的聚合运算,直到获取一个最终结果,这个结果跟例子中 `rayon::sum` 获取的结果是相同的 - -```rust,editable -use rayon::prelude::*; - -struct Person { - age: u32, -} - -fn main() { - let v: Vec<Person> = vec![ - Person { age: 23 }, - Person { age: 19 }, - Person { age: 42 }, - Person { age: 17 }, - Person { age: 17 }, - Person { age: 31 }, - Person { age: 30 }, - ]; - - let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32; - let sum_over_30 = v.par_iter() - .map(|x| x.age) - .filter(|&x| x > 30) - .reduce(|| 0, |x, y| x + y); - - let alt_sum_30: u32 = v.par_iter() - .map(|x| x.age) - .filter(|&x| x > 30) - .sum(); - - let avg_over_30 = sum_over_30 as f32 / num_over_30; - let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30; - - assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON); - println!("The average age of people older than 30 is {}", avg_over_30); -} -``` - -### 并行生成缩略图 -下面例子将为目录中的所有图片并行生成缩略图,然后将结果存到新的目录 `thumbnails` 中。 - -[glob::glob_with](https://docs.rs/glob/*/glob/fn.glob_with.html) 可以找出当前目录下的所有 `.jpg` 文件,`rayon` 通过 [DynamicImage::resize](https://docs.rs/image/*/image/enum.DynamicImage.html#method.resize) 来并行调整图片的大小。 - -```rust,editable -# use error_chain::error_chain; - -use std::path::Path; -use std::fs::create_dir_all; - -# use error_chain::ChainedError; -use glob::{glob_with, MatchOptions}; -use image::{FilterType, ImageError}; -use rayon::prelude::*; - -# error_chain! { -# foreign_links { -# Image(ImageError); -# Io(std::io::Error); -# Glob(glob::PatternError); -# } -#} - -fn main() -> Result<()> { - let options: MatchOptions = Default::default(); - // 找到当前目录中的所有 `jpg` 文件 - let files: Vec<_> = glob_with("*.jpg", options)? - .filter_map(|x| x.ok()) - .collect(); - - if files.len() == 0 { - error_chain::bail!("No .jpg files found in current directory"); - } - - let thumb_dir = "thumbnails"; - create_dir_all(thumb_dir)?; - - println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir); - - let image_failures: Vec<_> = files - .par_iter() - .map(|path| { - make_thumbnail(path, thumb_dir, 300) - .map_err(|e| e.chain_err(|| path.display().to_string())) - }) - .filter_map(|x| x.err()) - .collect(); - - image_failures.iter().for_each(|x| println!("{}", x.display_chain())); - - println!("{} thumbnails saved successfully", files.len() - image_failures.len()); - Ok(()) -} - -fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<()> -where - PA: AsRef<Path>, - PB: AsRef<Path>, -{ - let img = image::open(original.as_ref())?; - let file_path = thumb_dir.as_ref().join(original); - - Ok(img.resize(longest_edge, longest_edge, FilterType::Nearest) - .save(file_path)?) -} -``` \ No newline at end of file diff --git a/src/cookbook/cocurrency/threads.md b/src/cookbook/cocurrency/threads.md deleted file mode 100644 index b980f9ca..00000000 --- a/src/cookbook/cocurrency/threads.md +++ /dev/null @@ -1,336 +0,0 @@ -# 线程 - -### 生成一个临时性的线程 - -下面例子用到了 [crossbeam](cookbook/cocurrency/intro.md) 包,它提供了非常实用的、用于并发和并行编程的数据结构和函数。 - -[Scope::spawn](https://docs.rs/crossbeam/*/crossbeam/thread/struct.Scope.html#method.spawn) 会生成一个被限定了作用域的线程,该线程最大的特点就是:它会在传给 [crossbeam::scope](https://docs.rs/crossbeam/0.8.1/crossbeam/fn.scope.html) 的闭包函数返回前先行结束。得益于这个特点,子线程的创建使用就像是本地闭包函数调用,因此生成的线程内部可以使用外部环境中的变量! - - -```rust,editable -fn main() { - let arr = &[1, 25, -4, 10]; - let max = find_max(arr); - assert_eq!(max, Some(25)); -} - -// 将数组分成两个部分,并使用新的线程对它们进行处理 -fn find_max(arr: &[i32]) -> Option<i32> { - const THRESHOLD: usize = 2; - - if arr.len() <= THRESHOLD { - return arr.iter().cloned().max(); - } - - let mid = arr.len() / 2; - let (left, right) = arr.split_at(mid); - - crossbeam::scope(|s| { - let thread_l = s.spawn(|_| find_max(left)); - let thread_r = s.spawn(|_| find_max(right)); - - let max_l = thread_l.join().unwrap()?; - let max_r = thread_r.join().unwrap()?; - - Some(max_l.max(max_r)) - }).unwrap() -} -``` - -### 创建并行流水线 -下面我们使用 [crossbeam](https://docs.rs/crossbeam/latest/crossbeam/) 和 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 来创建一个并行流水线:流水线的两端分别是数据源和数据下沉( sink ),在流水线中间,有两个工作线程会从源头接收数据,对数据进行并行处理,最后将数据下沉。 - -- 消息通道( channel )是 [crossbeam_channel::bounded](https://docs.rs/crossbeam-channel/0.5.4/crossbeam_channel/fn.bounded.html),它只能缓存一条消息。当缓存满后,发送者继续调用 [crossbeam_channel::Sender::send] 发送消息时会阻塞,直到一个工作线程( 消费者 ) 拿走这条消息 -- 消费者获取消息时先到先得的策略,因此两个工作线程只有一个能取到消息,保证消息不会被重复消费、处理 -- 通过迭代器 [crossbeam_channel::Receiver::iter](https://docs.rs/crossbeam-channel/*/crossbeam_channel/struct.Receiver.html#method.iter) 读取消息会阻塞当前线程,直到新消息的到来或 channel 关闭 -- channel 只有在所有的发送者或消费者关闭后,才能被关闭。而其中一个消费者 `rcv2` 处于阻塞读取状态,无比被关闭,因此我们必须要关闭所有发送者: `drop(snd1);` `drop(snd2)` ,这样 channel 关闭后,主线程的 `rcv2` 才能从阻塞状态退出,最后整个程序结束。大家还是迷惑的话,可以看看这篇[文章](https://course.rs/practice/pitfalls/main-with-channel-blocked.html)。 - -```rust,editable -extern crate crossbeam; -extern crate crossbeam_channel; - -use std::thread; -use std::time::Duration; -use crossbeam_channel::bounded; - -fn main() { - let (snd1, rcv1) = bounded(1); - let (snd2, rcv2) = bounded(1); - let n_msgs = 4; - let n_workers = 2; - - crossbeam::scope(|s| { - // 生产者线程 - s.spawn(|_| { - for i in 0..n_msgs { - snd1.send(i).unwrap(); - println!("Source sent {}", i); - } - - // 关闭其中一个发送者 snd1 - // 该关闭操作对于结束最后的循环是必须的 - drop(snd1); - }); - - // 通过两个线程并行处理 - for _ in 0..n_workers { - // 从数据源接收数据,然后发送到下沉端 - let (sendr, recvr) = (snd2.clone(), rcv1.clone()); - // 生成单独的工作线程 - s.spawn(move |_| { - thread::sleep(Duration::from_millis(500)); - // 等待通道的关闭 - for msg in recvr.iter() { - println!("Worker {:?} received {}.", - thread::current().id(), msg); - sendr.send(msg * 2).unwrap(); - } - }); - } - // 关闭通道,如果不关闭,下沉端将永远无法结束循环 - drop(snd2); - - // 下沉端 - for msg in rcv2.iter() { - println!("Sink received {}", msg); - } - }).unwrap(); -} -``` - - -### 线程间传递数据 - -下面我们来看看 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 的单生产者单消费者( SPSC ) 使用场景。 - -```rust,editable -use std::{thread, time}; -use crossbeam_channel::unbounded; - -fn main() { - // unbounded 意味着 channel 可以存储任意多的消息 - let (snd, rcv) = unbounded(); - let n_msgs = 5; - crossbeam::scope(|s| { - s.spawn(|_| { - for i in 0..n_msgs { - snd.send(i).unwrap(); - thread::sleep(time::Duration::from_millis(100)); - } - }); - }).unwrap(); - for _ in 0..n_msgs { - let msg = rcv.recv().unwrap(); - println!("Received {}", msg); - } -} -``` - -### 维护全局可变的状态 - -[lazy_static]() 会创建一个全局的静态引用( static ref ),该引用使用了 `Mutex` 以支持可变性,因此我们可以在代码中对其进行修改。`Mutex` 能保证该全局状态同时只能被一个线程所访问。 - -```rust,editable -use error_chain::error_chain; -use lazy_static::lazy_static; -use std::sync::Mutex; - -error_chain!{ } - -lazy_static! { - static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new()); -} - -fn insert(fruit: &str) -> Result<()> { - let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; - db.push(fruit.to_string()); - Ok(()) -} - -fn main() -> Result<()> { - insert("apple")?; - insert("orange")?; - insert("peach")?; - { - let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; - - db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item)); - } - insert("grape")?; - Ok(()) -} -``` - -### 并行计算 iso 文件的 SHA256 - -下面的示例将为当前目录中的每一个 .iso 文件都计算一个 SHA256 sum。其中线程池中会初始化和 CPU 核心数一致的线程数,其中核心数是通过 [num_cpus::get](https://docs.rs/num_cpus/*/num_cpus/fn.get.html) 函数获取。 - -`Walkdir::new` 可以遍历当前的目录,然后调用 `execute` 来执行读操作和 SHA256 哈希计算。 - -```rust,editable - -use walkdir::WalkDir; -use std::fs::File; -use std::io::{BufReader, Read, Error}; -use std::path::Path; -use threadpool::ThreadPool; -use std::sync::mpsc::channel; -use ring::digest::{Context, Digest, SHA256}; - -// Verify the iso extension -fn is_iso(entry: &Path) -> bool { - match entry.extension() { - Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true, - _ => false, - } -} - -fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> { - let mut buf_reader = BufReader::new(File::open(&filepath)?); - let mut context = Context::new(&SHA256); - let mut buffer = [0; 1024]; - - loop { - let count = buf_reader.read(&mut buffer)?; - if count == 0 { - break; - } - context.update(&buffer[..count]); - } - - Ok((context.finish(), filepath)) -} - -fn main() -> Result<(), Error> { - let pool = ThreadPool::new(num_cpus::get()); - - let (tx, rx) = channel(); - - for entry in WalkDir::new("/home/user/Downloads") - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| !e.path().is_dir() && is_iso(e.path())) { - let path = entry.path().to_owned(); - let tx = tx.clone(); - pool.execute(move || { - let digest = compute_digest(path); - tx.send(digest).expect("Could not send data!"); - }); - } - - drop(tx); - for t in rx.iter() { - let (sha, path) = t?; - println!("{:?} {:?}", sha, path); - } - Ok(()) -} -``` - - -### 使用线程池来绘制分形 - -下面例子中将基于 [Julia Set]() 来绘制一个分形图片,其中使用到了线程池来做分布式计算。 - -<img src="https://cloud.githubusercontent.com/assets/221000/26546700/9be34e80-446b-11e7-81dc-dd9871614ea1.png" /> - -```rust,edtiable -# use error_chain::error_chain; -use std::sync::mpsc::{channel, RecvError}; -use threadpool::ThreadPool; -use num::complex::Complex; -use image::{ImageBuffer, Pixel, Rgb}; - -# -# error_chain! { -# foreign_links { -# MpscRecv(RecvError); -# Io(std::io::Error); -# } -# } -# -# // Function converting intensity values to RGB -# // Based on http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm -# fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> { -# let wave = wavelength as f32; -# -# let (r, g, b) = match wavelength { -# 380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0), -# 440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0), -# 490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)), -# 510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0), -# 580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0), -# 645..=780 => (1.0, 0.0, 0.0), -# _ => (0.0, 0.0, 0.0), -# }; -# -# let factor = match wavelength { -# 380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.), -# 701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.), -# _ => 1.0, -# }; -# -# let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor)); -# Rgb::from_channels(r, g, b, 0) -# } -# -# // Maps Julia set distance estimation to intensity values -# fn julia(c: Complex<f32>, x: u32, y: u32, width: u32, height: u32, max_iter: u32) -> u32 { -# let width = width as f32; -# let height = height as f32; -# -# let mut z = Complex { -# // scale and translate the point to image coordinates -# re: 3.0 * (x as f32 - 0.5 * width) / width, -# im: 2.0 * (y as f32 - 0.5 * height) / height, -# }; -# -# let mut i = 0; -# for t in 0..max_iter { -# if z.norm() >= 2.0 { -# break; -# } -# z = z * z + c; -# i = t; -# } -# i -# } -# -# // Normalizes color intensity values within RGB range -# fn normalize(color: f32, factor: f32) -> u8 { -# ((color * factor).powf(0.8) * 255.) as u8 -# } - -fn main() -> Result<()> { - let (width, height) = (1920, 1080); - // 为指定宽高的输出图片分配内存 - let mut img = ImageBuffer::new(width, height); - let iterations = 300; - - let c = Complex::new(-0.8, 0.156); - - let pool = ThreadPool::new(num_cpus::get()); - let (tx, rx) = channel(); - - for y in 0..height { - let tx = tx.clone(); - // execute 将每个像素作为单独的作业接收 - pool.execute(move || for x in 0..width { - let i = julia(c, x, y, width, height, iterations); - let pixel = wavelength_to_rgb(380 + i * 400 / iterations); - tx.send((x, y, pixel)).expect("Could not send data!"); - }); - } - - for _ in 0..(width * height) { - let (x, y, pixel) = rx.recv()?; - // 使用数据来设置像素的颜色 - img.put_pixel(x, y, pixel); - } - - // 输出图片内容到指定文件中 - let _ = img.save("output.png")?; - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/compression/tar.md b/src/cookbook/compression/tar.md deleted file mode 100644 index 992d8ddc..00000000 --- a/src/cookbook/compression/tar.md +++ /dev/null @@ -1,77 +0,0 @@ -# 使用tar包 - -## 解压 tar 包 -以下代码将解压缩( [GzDecoder](https://docs.rs/flate2/*/flate2/read/struct.GzDecoder.html) )当前目录中的 `archive.tar.gz` ,并将所有文件抽取出( [Archive::unpack](https://docs.rs/tar/*/tar/struct.Archive.html#method.unpack) )来后当入到当前目录中。 - -```rust,editable -use std::fs::File; -use flate2::read::GzDecoder; -use tar::Archive; - -fn main() -> Result<(), std::io::Error> { - let path = "archive.tar.gz"; - - let tar_gz = File::open(path)?; - let tar = GzDecoder::new(tar_gz); - let mut archive = Archive::new(tar); - archive.unpack(".")?; - - Ok(()) -} -``` - -## 将目录压缩成 tar 包 -以下代码将 `/var/log` 目录压缩成 `archive.tar.gz`: - -- 创建一个 [File](https://doc.rust-lang.org/std/fs/struct.File.html) 文件,并使用 [GzEncoder](https://docs.rs/flate2/*/flate2/write/struct.GzEncoder.html) 和 [tar::Builder](https://docs.rs/tar/*/tar/struct.Builder.html) 对其进行包裹 -- 通过 [Builder::append_dir_all](https://docs.rs/tar/*/tar/struct.Builder.html#method.append_dir_all) 将 `/var/log` 目录下的所有内容添加到压缩文件中,该文件在 `backup/logs` 目录下。 -- [GzEncoder](https://docs.rs/flate2/*/flate2/write/struct.GzEncoder.html) 负责在写入压缩文件 `archive.tar.gz` 之前对数据进行压缩。 - -```rust,editable -use std::fs::File; -use flate2::Compression; -use flate2::write::GzEncoder; - -fn main() -> Result<(), std::io::Error> { - let tar_gz = File::create("archive.tar.gz")?; - let enc = GzEncoder::new(tar_gz, Compression::default()); - let mut tar = tar::Builder::new(enc); - tar.append_dir_all("backup/logs", "/var/log")?; - Ok(()) -} -``` - -## 解压的同时删除指定的文件前缀 -遍历目录中的文件 [Archive::entries](https://docs.rs/tar/*/tar/struct.Archive.html#method.entries),若解压前的文件名包含 `bundle/logs` 前缀,需要将前缀从文件名移除( [Path::strip_prefix](https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix) )后,再解压。 - - - -```rust,editable -use std::fs::File; -use std::path::PathBuf; -use flate2::read::GzDecoder; -use tar::Archive; - -fn main() -> Result<()> { - let file = File::open("archive.tar.gz")?; - let mut archive = Archive::new(GzDecoder::new(file)); - let prefix = "bundle/logs"; - - println!("Extracted the following files:"); - archive - .entries()? // 获取压缩档案中的文件条目列表 - .filter_map(|e| e.ok()) - // 对每个文件条目进行 map 处理 - .map(|mut entry| -> Result<PathBuf> { - // 将文件路径名中的前缀移除,获取一个新的路径名 - let path = entry.path()?.strip_prefix(prefix)?.to_owned(); - // 将内容解压到新的路径名中 - entry.unpack(&path)?; - Ok(path) - }) - .filter_map(|e| e.ok()) - .for_each(|x| println!("> {}", x.display())); - - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/config.md b/src/cookbook/config.md deleted file mode 100644 index 33eb3d73..00000000 --- a/src/cookbook/config.md +++ /dev/null @@ -1 +0,0 @@ -# 配置文件 todo diff --git a/src/cookbook/crypto.md b/src/cookbook/crypto.md deleted file mode 100644 index f9455099..00000000 --- a/src/cookbook/crypto.md +++ /dev/null @@ -1 +0,0 @@ -# 加密解密 todo diff --git a/src/cookbook/cryptography/encryption.md b/src/cookbook/cryptography/encryption.md deleted file mode 100644 index becab266..00000000 --- a/src/cookbook/cryptography/encryption.md +++ /dev/null @@ -1,57 +0,0 @@ -# 加密 - -### 使用 PBKDF2 对密码进行哈希和加盐( salt ) -[ring::pbkdf2](https://briansmith.org/rustdoc/ring/pbkdf2/index.html) 可以对一个加盐密码进行哈希。 - -```rust,editable - -use data_encoding::HEXUPPER; -use ring::error::Unspecified; -use ring::rand::SecureRandom; -use ring::{digest, pbkdf2, rand}; -use std::num::NonZeroU32; - -fn main() -> Result<(), Unspecified> { - const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; - let n_iter = NonZeroU32::new(100_000).unwrap(); - let rng = rand::SystemRandom::new(); - - let mut salt = [0u8; CREDENTIAL_LEN]; - // 生成 salt: 将安全生成的随机数填入到字节数组中 - rng.fill(&mut salt)?; - - let password = "Guess Me If You Can!"; - let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN]; - pbkdf2::derive( - pbkdf2::PBKDF2_HMAC_SHA512, - n_iter, - &salt, - password.as_bytes(), - &mut pbkdf2_hash, - ); - println!("Salt: {}", HEXUPPER.encode(&salt)); - println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash)); - - // `verify` 检查哈希是否正确 - let should_`succeed = pbkdf2::verify( - pbkdf2::PBKDF2_HMAC_SHA512, - n_iter, - &salt, - password.as_bytes(), - &pbkdf2_hash, - ); - let wrong_password = "Definitely not the correct password"; - let should_fail = pbkdf2::verify( - pbkdf2::PBKDF2_HMAC_SHA512, - n_iter, - &salt, - wrong_password.as_bytes(), - &pbkdf2_hash, - ); - - assert!(should_succeed.is_ok()); - assert!(!should_fail.is_ok()); - - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/cryptography/hashing.md b/src/cookbook/cryptography/hashing.md deleted file mode 100644 index 60a697c0..00000000 --- a/src/cookbook/cryptography/hashing.md +++ /dev/null @@ -1,71 +0,0 @@ -# 哈希 - -### 计算文件的 SHA-256 摘要 -写入一些数据到文件中,然后使用 [digest::Context](https://briansmith.org/rustdoc/ring/digest/struct.Context.html) 来计算文件内容的 SHA-256 摘要 [digest::Digest](https://briansmith.org/rustdoc/ring/digest/struct.Digest.html)。 - -```rust,editable -# use error_chain::error_chain; -use data_encoding::HEXUPPER; -use ring::digest::{Context, Digest, SHA256}; -use std::fs::File; -use std::io::{BufReader, Read, Write}; - -# error_chain! { -# foreign_links { -# Io(std::io::Error); -# Decode(data_encoding::DecodeError); -# } -# } - -fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> { - let mut context = Context::new(&SHA256); - let mut buffer = [0; 1024]; - - loop { - let count = reader.read(&mut buffer)?; - if count == 0 { - break; - } - context.update(&buffer[..count]); - } - - Ok(context.finish()) -} - -fn main() -> Result<()> { - let path = "file.txt"; - - let mut output = File::create(path)?; - write!(output, "We will generate a digest of this text")?; - - let input = File::open(path)?; - let reader = BufReader::new(input); - let digest = sha256_digest(reader)?; - - println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref())); - - Ok(()) -} -``` - -### 使用 HMAC 摘要来签名和验证消息 -使用 [ring::hmac](https://briansmith.org/rustdoc/ring/hmac/) 创建一个字符串签名并检查该签名的正确性。 - -```rust,editable -use ring::{hmac, rand}; -use ring::rand::SecureRandom; -use ring::error::Unspecified; - -fn main() -> Result<(), Unspecified> { - let mut key_value = [0u8; 48]; - let rng = rand::SystemRandom::new(); - rng.fill(&mut key_value)?; - let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); - - let message = "Legitimate and important message."; - let signature = hmac::sign(&key, message.as_bytes()); - hmac::verify(&key, message.as_bytes(), signature.as_ref())?; - - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/database.md b/src/cookbook/database.md deleted file mode 100644 index de785679..00000000 --- a/src/cookbook/database.md +++ /dev/null @@ -1 +0,0 @@ -# 数据库访问 todo diff --git a/src/cookbook/database/postgres.md b/src/cookbook/database/postgres.md deleted file mode 100644 index 13bd37dd..00000000 --- a/src/cookbook/database/postgres.md +++ /dev/null @@ -1,123 +0,0 @@ -# Postgres - -### 在数据库中创建表格 -我们通过 [postgres](https://docs.rs/postgres/0.17.2/postgres/) 来操作数据库。下面的例子有一个前提:数据库 `library` 已经存在,其中用户名和密码都是 `postgres`。 - -```rust,editable -use postgres::{Client, NoTls, Error}; - -fn main() -> Result<(), Error> { - // 连接到数据库 library - let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?; - - client.batch_execute(" - CREATE TABLE IF NOT EXISTS author ( - id SERIAL PRIMARY KEY, - name VARCHAR NOT NULL, - country VARCHAR NOT NULL - ) - ")?; - - client.batch_execute(" - CREATE TABLE IF NOT EXISTS book ( - id SERIAL PRIMARY KEY, - title VARCHAR NOT NULL, - author_id INTEGER NOT NULL REFERENCES author - ) - ")?; - - Ok(()) - -} -``` - -### 插入和查询 - -```rust,editable -use postgres::{Client, NoTls, Error}; -use std::collections::HashMap; - -struct Author { - _id: i32, - name: String, - country: String -} - -fn main() -> Result<(), Error> { - let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", - NoTls)?; - - let mut authors = HashMap::new(); - authors.insert(String::from("Chinua Achebe"), "Nigeria"); - authors.insert(String::from("Rabindranath Tagore"), "India"); - authors.insert(String::from("Anita Nair"), "India"); - - for (key, value) in &authors { - let author = Author { - _id: 0, - name: key.to_string(), - country: value.to_string() - }; - - // 插入数据 - client.execute( - "INSERT INTO author (name, country) VALUES ($1, $2)", - &[&author.name, &author.country], - )?; - } - - // 查询数据 - for row in client.query("SELECT id, name, country FROM author", &[])? { - let author = Author { - _id: row.get(0), - name: row.get(1), - country: row.get(2), - }; - println!("Author {} is from {}", author.name, author.country); - } - - Ok(()) - -} -``` - -### 聚合数据 - -下面代码将使用降序的方式列出 [Museum of Modern Art]() 数据库中的前 7999 名艺术家的国籍分布. - -```rust,editable -use postgres::{Client, Error, NoTls}; - -struct Nation { - nationality: String, - count: i64, -} - -fn main() -> Result<(), Error> { - let mut client = Client::connect( - "postgresql://postgres:postgres@127.0.0.1/moma", - NoTls, - )?; - - for row in client.query - ("SELECT nationality, COUNT(nationality) AS count - FROM artists GROUP BY nationality ORDER BY count DESC", &[])? { - - let (nationality, count) : (Option<String>, Option<i64>) - = (row.get (0), row.get (1)); - - if nationality.is_some () && count.is_some () { - - let nation = Nation{ - nationality: nationality.unwrap(), - count: count.unwrap(), - }; - println!("{} {}", nation.nationality, nation.count); - - } - } - - Ok(()) -} -``` - diff --git a/src/cookbook/database/sqlite.md b/src/cookbook/database/sqlite.md deleted file mode 100644 index 0872fbf2..00000000 --- a/src/cookbook/database/sqlite.md +++ /dev/null @@ -1,136 +0,0 @@ -# SQLite - -### 创建 SQLite 数据库 - -使用 `rusqlite` 可以创建 SQLite 数据库,[Connection::open](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.open) 会尝试打开一个数据库,若不存在,则创建新的数据库。 - -> 这里创建的 `cats.db` 数据库将被后面的例子所使用 - - -```rust,editable -use rusqlite::{Connection, Result}; -use rusqlite::NO_PARAMS; - -fn main() -> Result<()> { - let conn = Connection::open("cats.db")?; - - conn.execute( - "create table if not exists cat_colors ( - id integer primary key, - name text not null unique - )", - NO_PARAMS, - )?; - conn.execute( - "create table if not exists cats ( - id integer primary key, - name text not null, - color_id integer not null references cat_colors(id) - )", - NO_PARAMS, - )?; - - Ok(()) -} -``` - -### 插入和查询 - -```rust,editable - -use rusqlite::NO_PARAMS; -use rusqlite::{Connection, Result}; -use std::collections::HashMap; - -#[derive(Debug)] -struct Cat { - name: String, - color: String, -} - -fn main() -> Result<()> { - // 打开第一个例子所创建的数据库 - let conn = Connection::open("cats.db")?; - - let mut cat_colors = HashMap::new(); - cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]); - cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]); - - for (color, catnames) in &cat_colors { - // 插入一条数据行 - conn.execute( - "INSERT INTO cat_colors (name) values (?1)", - &[&color.to_string()], - )?; - // 获取最近插入数据行的 id - let last_id: String = conn.last_insert_rowid().to_string(); - - for cat in catnames { - conn.execute( - "INSERT INTO cats (name, color_id) values (?1, ?2)", - &[&cat.to_string(), &last_id], - )?; - } - } - let mut stmt = conn.prepare( - "SELECT c.name, cc.name from cats c - INNER JOIN cat_colors cc - ON cc.id = c.color_id;", - )?; - - let cats = stmt.query_map(NO_PARAMS, |row| { - Ok(Cat { - name: row.get(0)?, - color: row.get(1)?, - }) - })?; - - for cat in cats { - println!("Found cat {:?}", cat); - } - - Ok(()) -} -``` - -### 使用事务 -使用 [Connection::transaction](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.transaction) 可以开始新的事务,若没有对事务进行显式地提交 [Transaction::commit](https://docs.rs/rusqlite/0.27.0/rusqlite/struct.Transaction.html#method.commit),则会进行回滚。 - -下面的例子中,`rolled_back_tx` 插入了重复的颜色名称,会发生回滚。 - -```rust,editable -use rusqlite::{Connection, Result, NO_PARAMS}; - -fn main() -> Result<()> { - // 打开第一个例子所创建的数据库 - let mut conn = Connection::open("cats.db")?; - - successful_tx(&mut conn)?; - - let res = rolled_back_tx(&mut conn); - assert!(res.is_err()); - - Ok(()) -} - -fn successful_tx(conn: &mut Connection) -> Result<()> { - let tx = conn.transaction()?; - - tx.execute("delete from cat_colors", NO_PARAMS)?; - tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; - tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; - - tx.commit() -} - -fn rolled_back_tx(conn: &mut Connection) -> Result<()> { - let tx = conn.transaction()?; - - tx.execute("delete from cat_colors", NO_PARAMS)?; - tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; - tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; - tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; - - tx.commit() -} -``` \ No newline at end of file diff --git a/src/cookbook/datastructures/bitfield.md b/src/cookbook/datastructures/bitfield.md deleted file mode 100644 index 4c9c29e0..00000000 --- a/src/cookbook/datastructures/bitfield.md +++ /dev/null @@ -1,47 +0,0 @@ -# 位字段 - -### 定义和操作位字段 -使用 [`bitflags!`](https://docs.rs/bitflags/1.3.2/bitflags/macro.bitflags.html) 宏可以帮助我们创建安全的位字段类型 `MyFlags`,然后为其实现基本的 `clear` 操作。以下代码展示了基本的位操作和格式化: -```rust,editable -use bitflags::bitflags; -use std::fmt; - -bitflags! { - struct MyFlags: u32 { - const FLAG_A = 0b00000001; - const FLAG_B = 0b00000010; - const FLAG_C = 0b00000100; - const FLAG_ABC = Self::FLAG_A.bits - | Self::FLAG_B.bits - | Self::FLAG_C.bits; - } -} - -impl MyFlags { - pub fn clear(&mut self) -> &mut MyFlags { - self.bits = 0; - self - } -} - -impl fmt::Display for MyFlags { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:032b}", self.bits) - } -} - -fn main() { - let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C; - let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C; - assert_eq!((e1 | e2), MyFlags::FLAG_ABC); - assert_eq!((e1 & e2), MyFlags::FLAG_C); - assert_eq!((e1 - e2), MyFlags::FLAG_A); - assert_eq!(!e2, MyFlags::FLAG_A); - - let mut flags = MyFlags::FLAG_ABC; - assert_eq!(format!("{}", flags), "00000000000000000000000000000111"); - assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000"); - assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B"); - assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B"); -} -``` \ No newline at end of file diff --git a/src/cookbook/date.md b/src/cookbook/date.md deleted file mode 100644 index a434528a..00000000 --- a/src/cookbook/date.md +++ /dev/null @@ -1 +0,0 @@ -# 时间日期 diff --git a/src/cookbook/datetime/duration.md b/src/cookbook/datetime/duration.md deleted file mode 100644 index 4f14bff9..00000000 --- a/src/cookbook/datetime/duration.md +++ /dev/null @@ -1,69 +0,0 @@ -# 时间计算和转换 - -### 测量某段代码的耗时 -测量从 [time::Instant::now](https://doc.rust-lang.org/std/time/struct.Instant.html#method.now) 开始所经过的时间 [time::Instant::elapsed](https://doc.rust-lang.org/std/time/struct.Instant.html#method.elapsed). - -```rust,editable -use std::time::{Duration, Instant}; - -fn main() { - let start = Instant::now(); - expensive_function(); - let duration = start.elapsed(); - - println!("Time elapsed in expensive_function() is: {:?}", duration); -} -``` - -### 对日期和时间进行计算 -使用 [DateTime::checked_add_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_add_signed) 计算和显示从现在开始两周后的日期和时间,然后再计算一天前的日期 [DateTime::checked_sub_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_sub_signed)。 - -[DateTime::format](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format) 所支持的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/*/chrono/format/strftime/index.html) 找到. - -```rust,editable -use chrono::{DateTime, Duration, Utc}; - -fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> { - date_time.checked_sub_signed(Duration::days(1)) -} - -fn main() { - let now = Utc::now(); - println!("{}", now); - - let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2)) - .and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1))) - .and_then(day_earlier); - - match almost_three_weeks_from_now { - Some(x) => println!("{}", x), - None => eprintln!("Almost three weeks from now overflows!"), - } - - match now.checked_add_signed(Duration::max_value()) { - Some(x) => println!("{}", x), - None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."), - } -} -``` - -### 将本地时间转换成其它时区 -使用 [offset::Local::now](https://docs.rs/chrono/*/chrono/offset/struct.Local.html#method.now) 获取本地时间并进行显示,接着,使用 [DateTime::from_utc](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.from_utc) 将它转换成 UTC 标准时间。最后,再使用 [offset::FixedOffset](https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html) 将 UTC 时间转换成 UTC+8 和 UTC-2 的时间。 - -```rust,editable -use chrono::{DateTime, FixedOffset, Local, Utc}; - -fn main() { - let local_time = Local::now(); - let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc); - let china_timezone = FixedOffset::east(8 * 3600); - let rio_timezone = FixedOffset::west(2 * 3600); - println!("Local time now is {}", local_time); - println!("UTC time now is {}", utc_time); - println!( - "Time in Hong Kong now is {}", - utc_time.with_timezone(&china_timezone) - ); - println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone)); -} -``` \ No newline at end of file diff --git a/src/cookbook/datetime/parsing.md b/src/cookbook/datetime/parsing.md deleted file mode 100644 index aee55576..00000000 --- a/src/cookbook/datetime/parsing.md +++ /dev/null @@ -1,114 +0,0 @@ -# 解析和显示 - -### 检查日期和时间 -通过 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 获取当前的 UTC 时间: -- [Timelike](https://docs.rs/chrono/*/chrono/trait.Timelike.html), 时/分/秒 -- [Datelike](https://docs.rs/chrono/*/chrono/trait.Datelike.html), 年/月/日 - -```rust,editable -use chrono::{Datelike, Timelike, Utc}; - -fn main() { - let now = Utc::now(); - - let (is_pm, hour) = now.hour12(); - println!( - "The current UTC time is {:02}:{:02}:{:02} {}", - hour, - now.minute(), - now.second(), - if is_pm { "PM" } else { "AM" } - ); - println!( - "And there have been {} seconds since midnight", - now.num_seconds_from_midnight() - ); - - let (is_common_era, year) = now.year_ce(); - println!( - "The current UTC date is {}-{:02}-{:02} {:?} ({})", - year, - now.month(), - now.day(), - now.weekday(), - if is_common_era { "CE" } else { "BCE" } - ); - println!( - "And the Common Era began {} days ago", - now.num_days_from_ce() - ); -} -``` - -### 日期和时间戳的相互转换 - -```rust,editable -use chrono::{NaiveDate, NaiveDateTime}; - -fn main() { - // 生成一个具体的日期时间 - let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44); - println!( - "Number of seconds between 1970-01-01 00:00:00 and {} is {}.", - // 打印日期和日期对应的时间戳 - date_time, date_time.timestamp()); - - // 计算从 1970 1月1日 0:00:00 UTC 开始,10亿秒后是什么日期时间 - let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0); - println!( - "Date after a billion seconds since 1970-01-01 00:00:00 was {}.", - date_time_after_a_billion_seconds); -} -``` - -### 显示格式化的日期和时间 -通过 [Utc::now](https://docs.rs/chrono/*/chrono/offset/struct.Utc.html#method.now) 可以获取当前的 UTC 时间。 - -```rust,editable -use chrono::{DateTime, Utc}; - -fn main() { - let now: DateTime<Utc> = Utc::now(); - - println!("UTC now is: {}", now); - // 使用 RFC 2822 格式显示当前时间 - println!("UTC now in RFC 2822 is: {}", now.to_rfc2822()); - // 使用 RFC 3339 格式显示当前时间 - println!("UTC now in RFC 3339 is: {}", now.to_rfc3339()); - // 使用自定义格式显示当前时间 - println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y")); -} -``` - -### 将字符串解析为 DateTime 结构体 -我们可以将多种格式的日期时间字符串转换成 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 结构体。[DateTime::parse_from_str](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.parse_from_str) 使用的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html) 找到. - -只有当能唯一的标识出日期和时间时,才能创建 `DateTime`。如果要在没有时区的情况下解析日期或时间,你需要使用 [`NativeDate`](https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html) 等函数。 - -```rust,editable -use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime}; -use chrono::format::ParseError; - - -fn main() -> Result<(), ParseError> { - let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?; - println!("{}", rfc2822); - - let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?; - println!("{}", rfc3339); - - let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?; - println!("{}", custom); - - let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?; - println!("{}", time_only); - - let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?; - println!("{}", date_only); - - let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?; - println!("{}", no_timezone); - - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/dev/intro.md b/src/cookbook/dev/intro.md deleted file mode 100644 index f0cc50f0..00000000 --- a/src/cookbook/dev/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 开发调试 diff --git a/src/cookbook/dev/logs.md b/src/cookbook/dev/logs.md deleted file mode 100644 index 65c2dbdd..00000000 --- a/src/cookbook/dev/logs.md +++ /dev/null @@ -1 +0,0 @@ -# 日志 diff --git a/src/cookbook/dev/profile.md b/src/cookbook/dev/profile.md deleted file mode 100644 index 124678e8..00000000 --- a/src/cookbook/dev/profile.md +++ /dev/null @@ -1 +0,0 @@ -# 性能分析 diff --git a/src/cookbook/devtools/build-tools.md b/src/cookbook/devtools/build-tools.md deleted file mode 100644 index b3bdc6ad..00000000 --- a/src/cookbook/devtools/build-tools.md +++ /dev/null @@ -1,192 +0,0 @@ -# 构建时工具 -本章节的内容是关于构建工具的,如果大家没有听说过 `build.rs` 文件,强烈建议先看看[这里](https://course.rs/cargo/reference/build-script/intro.html)了解下何为构建工具。 - -### 编译并静态链接一个 C 库 - -[cc](https://docs.rs/cc/latest/cc/) 包能帮助我们更好地跟 C/C++/汇编进行交互:它提供了简单的 API 可以将外部的库编译成静态库( .a ),然后通过 `rustc` 进行静态链接。 - -下面的例子中,我们将在 Rust 代码中使用 C 的代码: *src/hello.c*。在开始编译 Rust 的项目代码前,`build.rs` 构建脚本将先被执行。通过 cc 包,一个静态的库可以被生成( *libhello.a* ),然后该库将被 Rust的代码所使用:通过 `extern` 声明外部函数签名的方式来使用。 - -由于例子中的 C 代码很简单,因此只需要将一个文件传递给 [cc::Build](https://docs.rs/cc/*/cc/struct.Build.html)。如果大家需要更复杂的构建,`cc::Build` 还提供了通过 [include](https://docs.rs/cc/*/cc/struct.Build.html#method.include) 来包含路径的方式,以及额外的编译标志( [flags](https://docs.rs/cc/1.0.73/cc/struct.Build.html#method.flag) )。 - -*Cargo.toml* - -```toml -[package] -... -build = "build.rs" - -[build-dependencies] -cc = "1" - -[dependencies] -error-chain = "0.11" -``` - -*build.rs* - -```rust -fn main() { - cc::Build::new() - .file("src/hello.c") - .compile("hello"); // outputs `libhello.a` -} -``` - -*src/hello.c* - -```C -#include <stdio.h> - - -void hello() { - printf("Hello from C!\n"); -} - -void greet(const char* name) { - printf("Hello, %s!\n", name); -} -``` - -*src/main.rs* - -```rust -use error_chain::error_chain; -use std::ffi::CString; -use std::os::raw::c_char; - -error_chain! { - foreign_links { - NulError(::std::ffi::NulError); - Io(::std::io::Error); - } -} -fn prompt(s: &str) -> Result<String> { - use std::io::Write; - print!("{}", s); - std::io::stdout().flush()?; - let mut input = String::new(); - std::io::stdin().read_line(&mut input)?; - Ok(input.trim().to_string()) -} - -extern { - fn hello(); - fn greet(name: *const c_char); -} - -fn main() -> Result<()> { - unsafe { hello() } - let name = prompt("What's your name? ")?; - let c_name = CString::new(name)?; - unsafe { greet(c_name.as_ptr()) } - Ok(()) -} -``` - -### 编译并静态链接一个 C++ 库 -链接到 C++ 库跟之前的方式非常相似。主要的区别在于链接到 C++ 库时,你需要通过构建方法 [cpp(true)](https://docs.rs/cc/*/cc/struct.Build.html#method.cpp) 来指定一个 C++ 编译器,然后在 C++ 的代码顶部添加 `extern "C"` 来阻止 C++ 编译器对库名进行名称重整( name mangling )。 - -*Cargo.toml* - -```toml -[package] -... -build = "build.rs" - -[build-dependencies] -cc = "1" -``` - -*build.rs* - -```rust -fn main() { - cc::Build::new() - .cpp(true) - .file("src/foo.cpp") - .compile("foo"); -} -``` - -*src/foo.cpp* - -```c++ -extern "C" { - int multiply(int x, int y); -} - -int multiply(int x, int y) { - return x*y; -} -``` - -*src/main.rs* - -```rust -extern { - fn multiply(x : i32, y : i32) -> i32; -} - -fn main(){ - unsafe { - println!("{}", multiply(5,7)); - } -} -``` - -### 为 C 库创建自定义的 define - -[cc::Build::define](https://docs.rs/cc/*/cc/struct.Build.html#method.define) 可以让我们使用自定义的 define 来构建 C 库。 - -以下示例在构建脚本 `build.rs` 中动态定义了一个 define,然后在运行时打印出 **Welcome to foo - version 1.0.2**。Cargo 会设置一些[环境变量](https://doc.rust-lang.org/cargo/reference/environment-variables.html),它们对于自定义的 define 会有所帮助。 - -*Cargo.toml* - -```toml -[package] -... -version = "1.0.2" -build = "build.rs" - -[build-dependencies] -cc = "1" -``` - -*build.rs* -```rust -fn main() { - cc::Build::new() - .define("APP_NAME", "\"foo\"") - .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) - .define("WELCOME", None) - .file("src/foo.c") - .compile("foo"); -} -``` - -*src/foo.c* -```C -#include <stdio.h> - -void print_app_info() { -#ifdef WELCOME - printf("Welcome to "); -#endif - printf("%s - version %s\n", APP_NAME, VERSION); -} -``` - -*src/main.rs* -```rust -extern { - fn print_app_info(); -} - -fn main(){ - unsafe { - print_app_info(); - } -} -``` - diff --git a/src/cookbook/devtools/config-log.md b/src/cookbook/devtools/config-log.md deleted file mode 100644 index 1121571f..00000000 --- a/src/cookbook/devtools/config-log.md +++ /dev/null @@ -1,140 +0,0 @@ -# 配置日志 - -### 为每个模块开启独立的日志级别 -下面代码创建了模块 `foo` 和嵌套模块 `foo::bar`,并通过 [RUST_LOG](https://docs.rs/env_logger/*/env_logger/#enabling-logging) 环境变量对各自的日志级别进行了控制。 - -```rust,editable -mod foo { - mod bar { - pub fn run() { - log::warn!("[bar] warn"); - log::info!("[bar] info"); - log::debug!("[bar] debug"); - } - } - - pub fn run() { - log::warn!("[foo] warn"); - log::info!("[foo] info"); - log::debug!("[foo] debug"); - bar::run(); - } -} - -fn main() { - env_logger::init(); - log::warn!("[root] warn"); - log::info!("[root] info"); - log::debug!("[root] debug"); - foo::run(); -} -``` - -要让环境变量生效,首先需要通过 `env_logger::init()` 开启相关的支持。然后通过以下命令来运行程序: -```shell -RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test -``` - -此时的默认日志级别被设置为 `warn`,但我们还将 `foo` 模块级别设置为 `info`, `foo::bar` 模块日志级别设置为 `debug`。 - -```bash -WARN:test: [root] warn -WARN:test::foo: [foo] warn -INFO:test::foo: [foo] info -WARN:test::foo::bar: [bar] warn -INFO:test::foo::bar: [bar] info -DEBUG:test::foo::bar: [bar] debug -``` - -### 使用自定义环境变量来设置日志 - -[Builder](https://docs.rs/env_logger/*/env_logger/struct.Builder.html) 将对日志进行配置,以下代码使用 `MY_APP_LOG` 来替代 `RUST_LOG` 环境变量: - -```rust,editable -use std::env; -use env_logger::Builder; - -fn main() { - Builder::new() - .parse(&env::var("MY_APP_LOG").unwrap_or_default()) - .init(); - - log::info!("informational message"); - log::warn!("warning message"); - log::error!("this is an error {}", "message"); -} -``` - -### 在日志中包含时间戳 - -```rust,editable -use std::io::Write; -use chrono::Local; -use env_logger::Builder; -use log::LevelFilter; - -fn main() { - Builder::new() - .format(|buf, record| { - writeln!(buf, - "{} [{}] - {}", - Local::now().format("%Y-%m-%dT%H:%M:%S"), - record.level(), - record.args() - ) - }) - .filter(None, LevelFilter::Info) - .init(); - - log::warn!("warn"); - log::info!("info"); - log::debug!("debug"); -} -``` - -以下是 `stderr` 的输出: -```shell -2022-03-22T21:57:06 [WARN] - warn -2022-03-22T21:57:06 [INFO] - info -``` - -### 将日志输出到指定文件 -[log4rs](https://docs.rs/log4rs/) 可以帮我们将日志输出指定的位置,它可以使用外部 YAML 文件或 `builder` 的方式进行配置。 - -```rust,editable -# use error_chain::error_chain; - -use log::LevelFilter; -use log4rs::append::file::FileAppender; -use log4rs::encode::pattern::PatternEncoder; -use log4rs::config::{Appender, Config, Root}; - -#error_chain! { -# foreign_links { -# Io(std::io::Error); -# LogConfig(log4rs::config::Errors); -# SetLogger(log::SetLoggerError); -# } -#} - -fn main() -> Result<()> { - // 创建日志配置,并指定输出的位置 - let logfile = FileAppender::builder() - // 编码模式的详情参见: https://docs.rs/log4rs/1.0.0/log4rs/encode/pattern/index.html - .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) - .build("log/output.log")?; - - let config = Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .build(Root::builder() - .appender("logfile") - .build(LevelFilter::Info))?; - - log4rs::init_config(config)?; - - log::info!("Hello, world!"); - - Ok(()) -} - -``` \ No newline at end of file diff --git a/src/cookbook/devtools/log.md b/src/cookbook/devtools/log.md deleted file mode 100644 index adc6616a..00000000 --- a/src/cookbook/devtools/log.md +++ /dev/null @@ -1,127 +0,0 @@ -# 日志 - -## log 包 -[log](https://docs.rs/crate/log/0.4.16) 提供了日志相关的实用工具。 - -### 在控制台打印 debug 信息 -`env_logger` 通过环境变量来配置日志。[log::debug!](https://docs.rs/log/0.4.16/log/macro.debug.html) 使用起来跟 [std::fmt](https://doc.rust-lang.org/std/fmt/) 中的格式化字符串很像。 - -```rust -fn execute_query(query: &str) { - log::debug!("Executing query: {}", query); -} - -fn main() { - env_logger::init(); - - execute_query("DROP TABLE students"); -} -``` - -如果大家运行代码,会发现没有任何日志输出,原因是默认的日志级别是 `error`,因此我们需要通过 `RUST_LOG` 环境变量来设置下新的日志级别: -```shell -$ RUST_LOG=debug cargo run -``` - -然后你将成功看到以下输出: -```shell -DEBUG:main: Executing query: DROP TABLE students -``` - -### 将错误日志输出到控制台 -下面我们通过 [log::error!](https://docs.rs/log/0.4.16/log/macro.error.html) 将错误日志输出到标准错误 `stderr`。 - -```rust -fn execute_query(_query: &str) -> Result<(), &'static str> { - Err("I'm afraid I can't do that") -} - -fn main() { - env_logger::init(); - - let response = execute_query("DROP TABLE students"); - if let Err(err) = response { - log::error!("Failed to execute query: {}", err); - } -} -``` - -### 将错误输出到标准输出 stdout -默认的错误会输出到标准错误输出 `stderr`,下面我们通过自定的配置来让错误输出到标准输出 `stdout`。 - -```rust,editable -use env_logger::{Builder, Target}; - -fn main() { - Builder::new() - .target(Target::Stdout) - .init(); - - log::error!("This error has been printed to Stdout"); -} -``` - -### 使用自定义 logger -下面的代码将实现一个自定义 logger `ConsoleLogger`,输出到标准输出 `stdout`。为了使用日志宏,`ConsoleLogger` 需要实现 [log::Log](https://docs.rs/log/*/log/trait.Log.html) 特征,然后使用 [log::set_logger](https://docs.rs/log/*/log/fn.set_logger.html) 来安装使用。 - -```rust,editable -use log::{Record, Level, Metadata, LevelFilter, SetLoggerError}; - -static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger; - -struct ConsoleLogger; - -impl log::Log for ConsoleLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() <= Level::Info - } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - println!("Rust says: {} - {}", record.level(), record.args()); - } - } - - fn flush(&self) {} -} - -fn main() -> Result<(), SetLoggerError> { - log::set_logger(&CONSOLE_LOGGER)?; - log::set_max_level(LevelFilter::Info); - - log::info!("hello log"); - log::warn!("warning"); - log::error!("oops"); - Ok(()) -} -``` - -### 输出到 Unix syslog -下面的代码将使用 [syslog](https://docs.rs/crate/syslog/6.0.1) 包将日志输出到 [Unix Syslog](https://www.gnu.org/software/libc/manual/html_node/Overview-of-Syslog.html). - -```rust,editable -#[cfg(target_os = "linux")] -#[cfg(target_os = "linux")] -use syslog::{Facility, Error}; - -#[cfg(target_os = "linux")] -fn main() -> Result<(), Error> { - // 初始化 logger - syslog::init(Facility::LOG_USER, - log::LevelFilter::Debug, - // 可选的应用名称 - Some("My app name"))?; - log::debug!("this is a debug {}", "message"); - log::error!("this is an error!"); - Ok(()) -} - -#[cfg(not(target_os = "linux"))] -fn main() { - println!("So far, only Linux systems are supported."); -} -``` - - -## tracing -@todo \ No newline at end of file diff --git a/src/cookbook/devtools/version.md b/src/cookbook/devtools/version.md deleted file mode 100644 index 2d9ff997..00000000 --- a/src/cookbook/devtools/version.md +++ /dev/null @@ -1,186 +0,0 @@ -# 版本号 - -### 解析并增加版本号 -下面例子使用 [Version::parse](https://docs.rs/semver/*/semver/struct.Version.html#method.parse) 将一个字符串转换成 [semver::Version](https://docs.rs/semver/*/semver/struct.Version.html) 版本号,然后将它的 patch, minor, major 版本号都增加 1。 - -注意,为了符合[语义化版本的说明](http://semver.org),增加 `minor` 版本时,`patch` 版本会被重设为 `0`,当增加 `major` 版本时,`minor` 和 `patch` 都将被重设为 `0`。 - -```rust,editable -use semver::{Version, SemVerError}; - -fn main() -> Result<(), SemVerError> { - let mut parsed_version = Version::parse("0.2.6")?; - - assert_eq!( - parsed_version, - Version { - major: 0, - minor: 2, - patch: 6, - pre: vec![], - build: vec![], - } - ); - - parsed_version.increment_patch(); - assert_eq!(parsed_version.to_string(), "0.2.7"); - println!("New patch release: v{}", parsed_version); - - parsed_version.increment_minor(); - assert_eq!(parsed_version.to_string(), "0.3.0"); - println!("New minor release: v{}", parsed_version); - - parsed_version.increment_major(); - assert_eq!(parsed_version.to_string(), "1.0.0"); - println!("New major release: v{}", parsed_version); - - Ok(()) -} -``` - -### 解析一个复杂的版本号字符串 -这里的版本号字符串还将包含 `SemVer` 中定义的预发布和构建元信息。 - -值得注意的是,为了符合 `SemVer` 的规则,构建元信息虽然会被解析,但是在做版本号比较时,该信息会被忽略。换而言之,即使两个版本号的构建字符串不同,它们的版本号依然可能相同。 - -```rust,editable -use semver::{Identifier, Version, SemVerError}; - -fn main() -> Result<(), SemVerError> { - let version_str = "1.0.49-125+g72ee7853"; - let parsed_version = Version::parse(version_str)?; - - assert_eq!( - parsed_version, - Version { - major: 1, - minor: 0, - patch: 49, - pre: vec![Identifier::Numeric(125)], - build: vec![], - } - ); - assert_eq!( - parsed_version.build, - vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] - ); - - let serialized_version = parsed_version.to_string(); - assert_eq!(&serialized_version, version_str); - - Ok(()) -} -``` - -### 检查给定的版本号是否是预发布 -下面例子给出两个版本号,然后通过 [is_prerelease](https://docs.rs/semver/1.0.7/semver/struct.Version.html#method.is_prerelease) 判断哪个是预发布的版本号。 - -```rust,editable -use semver::{Version, SemVerError}; - -fn main() -> Result<(), SemVerError> { - let version_1 = Version::parse("1.0.0-alpha")?; - let version_2 = Version::parse("1.0.0")?; - - assert!(version_1.is_prerelease()); - assert!(!version_2.is_prerelease()); - - Ok(()) -} -``` - -### 找出给定范围内的最新版本 -下面例子给出了一个版本号列表,我们需要找到其中最新的版本。 - -```rust,editable -#use error_chain::error_chain; - -use semver::{Version, VersionReq}; - -#error_chain! { -# foreign_links { -# SemVer(semver::SemVerError); -# SemVerReq(semver::ReqParseError); -# } -3} - -fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>> -where - I: IntoIterator<Item = &'a str>, -{ - let vreq = VersionReq::parse(version_req_str)?; - - Ok( - iterable - .into_iter() - .filter_map(|s| Version::parse(s).ok()) - .filter(|s| vreq.matches(s)) - .max(), - ) -} - -fn main() -> Result<()> { - assert_eq!( - find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, - Some(Version::parse("1.0.0")?) - ); - - assert_eq!( - find_max_matching_version( - ">1.2.3-alpha.3", - vec![ - "1.2.3-alpha.3", - "1.2.3-alpha.4", - "1.2.3-alpha.10", - "1.2.3-beta.4", - "3.4.5-alpha.9", - ] - )?, - Some(Version::parse("1.2.3-beta.4")?) - ); - - Ok(()) -} -``` - -### 检查外部命令的版本号兼容性 -下面将通过 [Command](https://doc.rust-lang.org/std/process/struct.Command.html) 来执行系统命令 `git --version`,并对该系统命令返回的 `git` 版本号进行解析。 - -```rust,editable -#use error_chain::error_chain; - -use std::process::Command; -use semver::{Version, VersionReq}; - -#error_chain! { -# foreign_links { -# Io(std::io::Error); -# Utf8(std::string::FromUtf8Error); -# SemVer(semver::SemVerError); -# SemVerReq(semver::ReqParseError); -# } -#} - -fn main() -> Result<()> { - let version_constraint = "> 1.12.0"; - let version_test = VersionReq::parse(version_constraint)?; - let output = Command::new("git").arg("--version").output()?; - - if !output.status.success() { - error_chain::bail!("Command executed with failing error code"); - } - - let stdout = String::from_utf8(output.stdout)?; - let version = stdout.split(" ").last().ok_or_else(|| { - "Invalid command output" - })?; - let parsed_version = Version::parse(version)?; - - if !version_test.matches(&parsed_version) { - error_chain::bail!("Command version lower than minimum supported version (found {}, need {})", - parsed_version, version_constraint); - } - - Ok(()) -} -``` \ No newline at end of file diff --git a/src/cookbook/encoding/csv.md b/src/cookbook/encoding/csv.md deleted file mode 100644 index 19f2031e..00000000 --- a/src/cookbook/encoding/csv.md +++ /dev/null @@ -1 +0,0 @@ -# CSV diff --git a/src/cookbook/encoding/intro.md b/src/cookbook/encoding/intro.md deleted file mode 100644 index 0e61f3d0..00000000 --- a/src/cookbook/encoding/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 编解码 diff --git a/src/cookbook/encoding/json.md b/src/cookbook/encoding/json.md deleted file mode 100644 index 02ed5a24..00000000 --- a/src/cookbook/encoding/json.md +++ /dev/null @@ -1 +0,0 @@ -# JSON diff --git a/src/cookbook/encoding/protobuf.md b/src/cookbook/encoding/protobuf.md deleted file mode 100644 index 4fdb7ce0..00000000 --- a/src/cookbook/encoding/protobuf.md +++ /dev/null @@ -1 +0,0 @@ -# protobuf diff --git a/src/cookbook/file/dir.md b/src/cookbook/file/dir.md deleted file mode 100644 index c996c7a0..00000000 --- a/src/cookbook/file/dir.md +++ /dev/null @@ -1 +0,0 @@ -# 目录操作 diff --git a/src/cookbook/file/file.md b/src/cookbook/file/file.md deleted file mode 100644 index 9286f56a..00000000 --- a/src/cookbook/file/file.md +++ /dev/null @@ -1 +0,0 @@ -# 文件读写 diff --git a/src/cookbook/file/intro.md b/src/cookbook/file/intro.md deleted file mode 100644 index 0f8e6d56..00000000 --- a/src/cookbook/file/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 文件系统 todo diff --git a/src/cookbook/intro.md b/src/cookbook/intro.md index 12f5f0e6..45419e9a 100644 --- a/src/cookbook/intro.md +++ b/src/cookbook/intro.md @@ -2,6 +2,12 @@ 对于开发者而言,CookBook 是非常实用的,几乎每一门编程语言都是如此。原因无他:聪明的开发者大部分时间不是在复制粘贴就是在复制粘贴的路上。而 CookBook 恰恰为各种实用场景提供了可供直接复制粘贴的代码,例如文件操作、随机数生成、命令行解析等等, +由于这本书的章节非常多,为了不影响大家的整体阅读体验,请访问以下地址阅读。 + +- 在线阅读: <a href="https://cookbook.rs" target="_blank">https://cookbook.rs</a> + +<br /> + > CookBook 的部分内容翻译自 [Rust CookBook](https://rust-lang-nursery.github.io/rust-cookbook/intro.html),但是内容并不相同,因为我们对部分内容进行了整合,最重要的是增加了大量实用库和代码片段 diff --git a/src/cookbook/protocol/grpc.md b/src/cookbook/protocol/grpc.md deleted file mode 100644 index 37b724e9..00000000 --- a/src/cookbook/protocol/grpc.md +++ /dev/null @@ -1 +0,0 @@ -# gRPC diff --git a/src/cookbook/protocol/http.md b/src/cookbook/protocol/http.md deleted file mode 100644 index 03fe6a2d..00000000 --- a/src/cookbook/protocol/http.md +++ /dev/null @@ -1 +0,0 @@ -# HTTP diff --git a/src/cookbook/protocol/intro.md b/src/cookbook/protocol/intro.md deleted file mode 100644 index aaab51db..00000000 --- a/src/cookbook/protocol/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 网络通信 todo diff --git a/src/cookbook/protocol/tcp.md b/src/cookbook/protocol/tcp.md deleted file mode 100644 index fd208cbf..00000000 --- a/src/cookbook/protocol/tcp.md +++ /dev/null @@ -1 +0,0 @@ -# TCP diff --git a/src/cookbook/protocol/udp.md b/src/cookbook/protocol/udp.md deleted file mode 100644 index 2827b96c..00000000 --- a/src/cookbook/protocol/udp.md +++ /dev/null @@ -1 +0,0 @@ -# UDP diff --git a/src/cookbook/regexp.md b/src/cookbook/regexp.md deleted file mode 100644 index b79ef0b1..00000000 --- a/src/cookbook/regexp.md +++ /dev/null @@ -1 +0,0 @@ -# 正则表达式 todo From c302fe19362dce09f4c5db399dec1addc28a35fe Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 13:30:54 +0800 Subject: [PATCH 27/34] update readme.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 25b9919d..f9f4b0ac 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,14 @@ 总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。 -## 配套学习资源 +## Rustt 翻译计划 + +想要获取更多关于 Rust 的高质量技术文章、学习资料和新闻资讯嘛?请订阅 [Rustt 翻译计划](https://rustt.org)。 + +## Rust语言周刊 + +每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态。与 Rustt 翻译计划不同,周刊不区分中英文,只是把最新产生的内容分门别类的呈现给广大读者。 -- [Rust语言实战](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D -- [Rust语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态 -- [Rust酷库推荐](https://github.com/sunface/fancy-rust),优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust,它能带给你全新的体验和选择 -- [Rustt 翻译组](https://rustt.org),目前最专业的 Rust 翻译组织,这里有 Rust 技术文章、学习教程和新闻资讯的高质量中文翻译 ## 🏅 贡献者 From 47c37d1a85ae0e247cea6fe9dee093c5b86f5c11 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 13:40:07 +0800 Subject: [PATCH 28/34] update toc --- book.toml | 2 +- src/SUMMARY.md | 5 +- src/{cookbook/intro.md => cookbook.md} | 0 src/rust-weekly.md | 68 ++++++++++++++++++++++++++ src/rustt.md | 13 +++++ 5 files changed, 85 insertions(+), 3 deletions(-) rename src/{cookbook/intro.md => cookbook.md} (100%) create mode 100644 src/rust-weekly.md create mode 100644 src/rustt.md diff --git a/book.toml b/book.toml index 22f16de7..0191baba 100644 --- a/book.toml +++ b/book.toml @@ -17,7 +17,7 @@ copy-js = true [output.html.fold] enable = true -level = 2 +level = 1 [rust] edition = "2021" #在线运行用2021版本的 diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 7358fe75..350f5961 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -164,8 +164,9 @@ # 应用实战 --- -- [CookBook](cookbook/intro.md) - +- [Cookbook](cookbook.md) +- [Rust 语言周刊](rust-weekly.md) +- [Rustt 翻译计划](rustt.md) - [Rust 最佳实践](practice/intro.md) - [对抗编译检查](practice/fight-with-compiler/intro.md) - [生命周期](practice/fight-with-compiler/lifetime/intro.md) diff --git a/src/cookbook/intro.md b/src/cookbook.md similarity index 100% rename from src/cookbook/intro.md rename to src/cookbook.md diff --git a/src/rust-weekly.md b/src/rust-weekly.md new file mode 100644 index 00000000..6d617d66 --- /dev/null +++ b/src/rust-weekly.md @@ -0,0 +1,68 @@ +# Rust 语言周刊 + +精选过去一周的文章、新闻、开源项目和 Rust 语言动态( 中文内容用 🇨🇳 进行标识 ),欢迎大家[订阅及查看往期回顾](https://github.com/studyrs/rust-weekly)。 + + +## 「Rust 语言周刊」 第 6 期 · 2022-04-02 + +<img src="https://pica.zhimg.com/80/v2-23889bd3869ac6736256ac51ae4975d3_1440w.jpg"> +<h5 align="center">题图: Rust 嵌入式开发</h5> + +#### 精选文章 + +1、 [Zh] [敢于要求更多 Rust 2024](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-03-28%5D%20Rust%202024:敢于要求更多.md) - 翻译 [YuKun Liu](https://github.com/mrxiaozhuox) + +未来几年的 Rust 和社区应该怎么发展,可以简单总结为:敢于要求更多。 + +2、[Zh] [Rust 嵌入式开发](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-03-26%5D%20Rust%20嵌入式开发.md) - 翻译 [Xiaobin.Liu](https://github.com/lxbwolf) + +本文展示了一些适用于嵌入式 Rust 的特性,总之, Rust 的高性能、高可靠和生产效率都非常适用于嵌入式系统。 + +3、[dyn*: 尝试将 dyn 变成定长类型](https://smallcultfollowing.com/babysteps/blog/2022/03/29/dyn-can-we-make-dyn-sized/) + +三人行必能干翻诸葛亮,这不,作者和两个朋友在一次深入讨论后,突然诞生了这个奇妙的想法,最后还提交给了 Rust Team。作者还认为,一旦成功,那 `dyn Trait` 将更加好用、易用。 + +4、[自修改代码](https://matklad.github.io/2022/03/26/self-modifying-code.html) + +对于 JIT 类似的动态机器码修改技术,大家应该都比较熟悉了,但是 Rust 中并没有。因此,作者想要通过一个简单的方法来替代宏去生成源代码。 + +5、[异步解构器、异步泛型和完成式期约](https://sabrinajewson.org/blog/async-drop) + +本文的主要目标是为 Rust 设计一个系统以支持异步解构器( asynchronous destructors )。长文预警! + +6、[何时不应该使用 Rust](https://kerkour.com/why-not-rust) + +不出所料,文章内给出了快速原型设计的答案。短文预警! + +7、[Rust 交叉编译](https://kerkour.com/rust-cross-compilation) + +黑帽 Rust 作者又出手了,这次为我们带来关于交叉编译的优质内容。 + +8、[小而美的 Rust Docker 镜像](https://azzamsa.com/n/rust-docker/) + +文章用 Rocket 框架写了一个 demo,然后将其打包成 Docker 镜像,最后的大小仅仅是 `8.38MB`,但... 算了,不剧透了,大家还是自己探索吧。 + +9、[Book] [High Assurance Rust](https://highassurance.rs) + +由于我自己是开源书作者,因此对开源书有一种特别的偏爱。这本书主要关于如何开发高可靠、安全的软件服务,当然,书中还有一些计算机原理和架构设计的讲解。 + +10、[Video] [Rust for Linux](https://www.youtube.com/watch?v=fVEeqo40IyQ) + +本视频将讲解目前 Linux 的 kernel 中,Rust 将扮演什么角色以及未来规划。 + +#### 开源项目 + +1、[生成你的 Github Profile](https://github.com/autarch/autarch) + +灵感来自于作者在简历中看到别人的炫酷 Github 个人首页展示,还写了[一篇文章](https://blog.urth.org/2022/03/28/yet-another-github-profile-generator/)。 + + +2、[fp-bindgen: 为全栈 WASM 插件生成相应的 binding](https://fiberplane.dev/blog/announcing-fp-bindgen/) + +全栈 WASM 插件是可以同时用在客户端和服务端的插件,而 `fp-bindgen` 让插件的创作变得更加简单,不仅如此,还提供了工具可以让它们在服务器上运行( hosting )。 + +3、[BonsaiDB v0.4.0](https://bonsaidb.io/blog/bonsaidb-v0-4-0/) + +`BonsaiDB` 的目标是打造一个使用者友好的数据库,拥有大量常用的数据结构。但是之前的版本只支持异步 API,这个缺陷在新版本中得到了解决。 + + diff --git a/src/rustt.md b/src/rustt.md new file mode 100644 index 00000000..a5df3788 --- /dev/null +++ b/src/rustt.md @@ -0,0 +1,13 @@ +# Rustt 翻译计划 + +🥇Rustt 翻译计划,这里有国内最优质、最实时的 Rust 技术文章、学习资料和新闻资讯,欢迎大家[前往阅读和订阅](https://github.com/studyrs/Rustt)。 + +## 最近优秀作品展 + +| 中文名 | 翻译时间 | 作者 | +| ------- | -------- | ----- | +| [series][Rust 六边形架构](https://github.com/studyrs/Rustt/tree/main/Articles/%5B2022-04-03%5D%20Rust%20六边形架构) | 2022-04-04 | [trdthg](https://github.com/trdthg) | +| [用 Rust 写 Devops 工具](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-04-02%5D%20用%20Rust%20写%20DevOps%20工具.md) | 2022-04-03 | [Xiaobin.Liu](https://github.com/lxbwolf) | +| [Rust 大佬给初学者的学习建议](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-04-02%5D%20Rust%20大佬给初学者的学习建议.md) | 2022-04-02 | [Asura](https://github.com/asur4s) | +| [Rust 背后并不是公司](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-04-01%5D%20Rust%20背后并不是公司.md) | 2022-04-01 | [子殊](https://github.com/allenli178) | +| [在 Rust 中使用 epoll 实现非阻塞 IO](https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-03-29%5D%20在%20Rust%20中使用%20epoll%20实现基本的非阻塞%20IO.md) | 2022-03-29 | [BK0717](https://github.com/hyuuko) | From 5fcde5402084eed9ed8a5c387b1ba84199ae924f Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 13:56:55 +0800 Subject: [PATCH 29/34] update toc --- src/SUMMARY.md | 11 +++++++---- theme/style2.css | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 350f5961..2cf408a1 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -2,9 +2,12 @@ [Rust语言圣经](about-book.md) [进入 Rust 编程世界](into-rust.md) -[AWS 为何这么喜欢 Rust?](usecases/aws-rust.md) [快速查询入口](index-list.md) +--- +[Rust Cookbook](cookbook.md) +[Rust 语言周刊](rust-weekly.md) +[Rustt 翻译计划](rustt.md) # 快速开始 --- @@ -164,9 +167,9 @@ # 应用实战 --- -- [Cookbook](cookbook.md) -- [Rust 语言周刊](rust-weekly.md) -- [Rustt 翻译计划](rustt.md) +- [企业落地实践](usecases/intro.md) + - [AWS 为何这么喜欢 Rust?](usecases/aws-rust.md) + - [Rust 最佳实践](practice/intro.md) - [对抗编译检查](practice/fight-with-compiler/intro.md) - [生命周期](practice/fight-with-compiler/lifetime/intro.md) diff --git a/theme/style2.css b/theme/style2.css index e4d5a8cd..eacea892 100644 --- a/theme/style2.css +++ b/theme/style2.css @@ -97,4 +97,12 @@ table { /* Fix on mobile device */ code { word-break: break-word; +} + +/* 修改书侧边目录的区域分隔行样式 */ + +.chapter .spacer { + background-color: #99CCFF; + height: 2px; + margin-top: 8px; } \ No newline at end of file From b51f43c3845091a9a3000afba0b15065720c0fbc Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 13:57:15 +0800 Subject: [PATCH 30/34] update toc --- book.toml | 2 +- theme/{style2.css => style3.css} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename theme/{style2.css => style3.css} (100%) diff --git a/book.toml b/book.toml index 0191baba..00c73b29 100644 --- a/book.toml +++ b/book.toml @@ -5,7 +5,7 @@ title = "Rust语言圣经(Rust Course)" src = "src" [output.html] -additional-css = ["theme/style2.css"] +additional-css = ["theme/style3.css"] additional-js = ["assets/custom.js", "assets/bigPicture.js"] git-repository-url = "https://github.com/sunface/rust-course" edit-url-template = "https://github.com/sunface/rust-course/edit/main/{path}" diff --git a/theme/style2.css b/theme/style3.css similarity index 100% rename from theme/style2.css rename to theme/style3.css From 7f131ae84e2d01e1800d9c83be18a14af31df504 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 14:32:43 +0800 Subject: [PATCH 31/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[c?= =?UTF-8?q?ookbook=20-=20=E5=AD=97=E7=AC=A6=E9=9B=86]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- theme/style3.css | 2 +- 内容变更记录.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/theme/style3.css b/theme/style3.css index eacea892..acf81bc9 100644 --- a/theme/style3.css +++ b/theme/style3.css @@ -105,4 +105,4 @@ code { background-color: #99CCFF; height: 2px; margin-top: 8px; -} \ No newline at end of file +} diff --git a/内容变更记录.md b/内容变更记录.md index 1cd8c8f0..babd272c 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -1,6 +1,10 @@ # ChangeLog 记录一些值得注意的变更。 +## 2022-04-05 + +- 新增章节:[Cookbook - 字符编码](https://cookbook.rs/encoding/strings.html) + ## 2022-04-04 - 新增章节: [Cookbook - 使用 rayon 并行处理数据](https://course.rs/cookbook/cocurrency/parallel.html) From 5afd0d19edcd2bef43c8082d48f44a4750833e3c Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 14:50:05 +0800 Subject: [PATCH 32/34] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=20[c?= =?UTF-8?q?ookbook=20-=20CSV=E5=A4=84=E7=90=86]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 内容变更记录.md | 1 + 1 file changed, 1 insertion(+) diff --git a/内容变更记录.md b/内容变更记录.md index babd272c..bbca8238 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -4,6 +4,7 @@ ## 2022-04-05 - 新增章节:[Cookbook - 字符编码](https://cookbook.rs/encoding/strings.html) +- 新增章节:[Cookbook - CSV处理](https://cookbook.rs/encoding/csv.html) ## 2022-04-04 From b931b8bff01be9eff2d28ea9f10423d73840f8b3 Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 19:49:03 +0800 Subject: [PATCH 33/34] rename cookbook to Cook Rust --- src/SUMMARY.md | 2 +- src/cookbook.md | 30 +++++++++++++++++++++++------- src/practice/third-party-libs.md | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2cf408a1..5cc4abae 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -5,7 +5,7 @@ [快速查询入口](index-list.md) --- -[Rust Cookbook](cookbook.md) +[Cook Rust: Awesome + Cookbook](cookbook.md) [Rust 语言周刊](rust-weekly.md) [Rustt 翻译计划](rustt.md) diff --git a/src/cookbook.md b/src/cookbook.md index 45419e9a..7fc87b10 100644 --- a/src/cookbook.md +++ b/src/cookbook.md @@ -1,25 +1,41 @@ -# 场景化用例 +<h1 align="center">Cook Rust</h1> -对于开发者而言,CookBook 是非常实用的,几乎每一门编程语言都是如此。原因无他:聪明的开发者大部分时间不是在复制粘贴就是在复制粘贴的路上。而 CookBook 恰恰为各种实用场景提供了可供直接复制粘贴的代码,例如文件操作、随机数生成、命令行解析等等, +<div align="center"> + <img height="200px" src="https://github.com/sunface/rust-cookbook/blob/main/assets/banner1.png?raw=true"> +</div> + +<div align="center"> -由于这本书的章节非常多,为了不影响大家的整体阅读体验,请访问以下地址阅读。 + +在线阅读: [https://cook.rs](https://cook.rs) +</div> -- 在线阅读: <a href="https://cookbook.rs" target="_blank">https://cookbook.rs</a> +学习一门语言、做一个项目就像烹饪一顿美食一样,你需要往项目中添加许多调味料,而 Cook Rust 就是教大家如何烹饪一个优秀的 Rust 项目。 -<br /> +| 烹饪美食 | Cook Rust | +| --- | --- | +| 找到合适的厨具、调料、食材 | 为项目挑选 Awesome 依赖库 | +| 按照食谱做好一道道菜,最终呈现一桌大餐 | 在 Cookbook 中查询实用的代码片段,直接复制到项目中,最终快速搭建好一个项目 | -> CookBook 的部分内容翻译自 [Rust CookBook](https://rust-lang-nursery.github.io/rust-cookbook/intro.html),但是内容并不相同,因为我们对部分内容进行了整合,最重要的是增加了大量实用库和代码片段 +可以看出 `Cook Rust` = `Awesome Rust` + `Rust Cookbook`,**在这里你可以找到各种优秀的依赖库和代码片段**,无论是学习还是快速搭建项目,本书都可以助你一臂之力! + +关于 Awesome,相信大家已经非常熟悉。但目前最火的 awesome-rust 项目有一个非常大的问题:里面的项目鱼龙混杂,因为它的目的是列出所有项目,但对于用户而言,更想看到的是可以在生产中使用的、稳定更新的优秀项目。 + +对于开发者而言,Cookbook 非常实用的,几乎每一门编程语言都是如此。原因无他:聪明的开发者大部分时间不是在复制粘贴就是在复制粘贴的路上。而 CookBook 恰恰为各种实用场景提供了可供直接复制粘贴的代码,例如网络协议、数据库和文件操作、随机数生成、命令行解析等。既可以用于学习 Rust ,还能大幅提升你的编码效率。 + +> Cookbook 的部分内容翻译自 [Rust CookBook](https://rust-lang-nursery.github.io/rust-cookbook/intro.html),但由于这本英文书更新不太活跃,导致了内容存在较多的遗漏或过期,因此我们并没有完全照搬翻译这本书的内容,而是在此基础上增加了大量新的实用库和代码片段,希望大家喜欢 ## 这本书的读者 本书适合所有程度的 Rust 开发者使用: -- 新手用来熟悉生态和常用库 +- 新手用来了解 Rust 的常用库和常用代码片段 - 老手在写代码时,可以直接用来复制粘贴,大幅提升工作效率 毕竟咱不是在面试造飞机,谁脑袋中能记住文件操作的各种细节,对不? ## 怎么使用 + Cookbook 中的代码都是完整的,换而言之,这些代码片段包含了 `fn main` 函数,可以直接运行,如果你是拷贝到自己的代码中,请注意拷贝相应的代码部分,而不是全盘复制。 同时,这些代码( 大部分 )支持在线编辑和运行,大家无需复制到 IDE 中即可进行把玩研究。 diff --git a/src/practice/third-party-libs.md b/src/practice/third-party-libs.md index 9b187fae..c137fe6b 100644 --- a/src/practice/third-party-libs.md +++ b/src/practice/third-party-libs.md @@ -4,7 +4,7 @@ 本文就分门别类的精心挑选了一些非常适合日常开发使用的三方库,同时针对优缺点、社区活跃等进行了评价,同一个类别的库,按照**推荐度优先级降序排列**,希望大家能喜欢。 -> 本文节选自[Fancy Rust](https://fancy.rs), 一个Rust酷库推荐项目, 里面精选了各个领域的好项目,无论是学习还是工作使用,都能助你一臂之力。 +> 本文节选自[Cook Rust](https://cook.rs) ## 目录 - 日常开发常用的Rust库: From a63701e542a73f074a531d0f5353bd8d670257ff Mon Sep 17 00:00:00 2001 From: sunface <cto@188.com> Date: Tue, 5 Apr 2022 20:02:07 +0800 Subject: [PATCH 34/34] rename cookbook to Cook Rust --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9f4b0ac..7e8ad63a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ - **规避陷阱和对抗编译器**,只有真的上手写过一长段时间 Rust 项目,才知道该如何规避常见的陷阱以及解决一些难搞的编译器错误,而本书将帮助你大大缩短这个过程,提前规避这些问题 -- **[Cookbook](https://cookbook.rs)**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 +- **[Cookbook](https://cook.rs)**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 - **[配套练习题](https://github.com/sunface/rust-by-practice)**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + Rust语言实战* 双剑合璧,给你最极致的学习体验