About Hello Rust
一个编程高手是怎样练成的呢? 惟手熟尔。重在刻意练习。 这意为着,就是不断重复练习,实践,再实践,熟练掌握各种技能。因为,只有反复练习,才能真正掌握。
Hello,Rust 是如何产生的呢? 这是我在学习Rust过程中,不断地编写样例代码,不断点滴积累经验,最终形成的。
Rust 是一个非常优秀的系统编程语言,它简洁易读,性能高,更安全,功能强大。然而,它也存在学习曲线陡峭的问题,需要花费大量时间和精力去理解借用检查、所有权机制等新的编程概念,确实不太容易上手。
对于新手来说,Hello, Rust 是一个绝佳的起点。通过这个项目,你不仅能快速入门 Rust 编程,还能通过编程、调试、运行示例代码,迅速掌握 Rust 的核心知识点,熟悉基础语法和基本概念。更棒的是,它还涵盖了高级进阶知识和精选的 Rust Crates 库应用示例。
本书的当前版本假设你使用 Rust 1.86.0(2025-04-03 发布)或更高版本并在所有项目的 Cargo.toml 文件中通过 edition = "2024"将其配置为使用 Rust 2024 edition 惯用法。请查看Getting Started的 “安装” 部分了解如何安装和升级 Rust。
Introduction
Rust 是一种现代、高性能的编程语言,专注于安全性、并发性和速度。由 Mozilla 开发,现由 Rust 基金会维护,广泛应用于系统编程、Web 后端和高性能应用。
Rust 是一种通用的编程语言,强调性能、类型安全和并发性。它强制执行内存安全,确保所有引用都指向有效内存。与传统的垃圾回收机制不同,Rust 通过“借用检查器”在编译时跟踪引用的对象生命周期,从而防止内存安全错误和数据竞争。
Rust 支持多种编程范式。它受到函数式编程思想的影响,包括不可变性、高阶函数、代数数据类型和模式匹配。同时,它通过结构体、枚举、特性和方法支持面向对象编程。
为什么选择 Rust?
- 安全性:Rust 的“所有权和借用”,通过“借用检查器“在编译时检测内存安全问题,确保了内存安全。
- 性能:Rust 通过追求零成本抽象(zero-cost abstractions)—— 将高级语言特性编译成底层代码,并且与手写的代码运行速度同样快。Rust 努力确保代码又安全又快速。Rust 的编译器可以生成高效的机器码,接近C的性能。
- 并发性:Rust 提供了强大的并发模型,支持异步编程,多线程编程,async/await 异步原语,tokio/async-std 异步运行时。
- 优秀的包管理:Cargo提供Rust 强大的包管理工具, 包版本,依赖,测试一套工具全套方案解决,尤其体验过C/C++ 包管理痛点的人,使用Cargo 感觉太便捷了。
- 可读性:Rust 的代码风格统一简洁易读。
rustfmt帮你整理代码格式,统一代码风格,便捷阅读。clippy帮你检查代码问题。
Getting Started
安装 Rust
首先,你需要安装 Rust。你可以从 Rust 官方网站 下载并安装。
Windows 上,你可以从 Rust 官方网站 下 载并安装。
Linux 上,你可以使用包管理器来安装 Rust:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ rustup update
# rustup toolchain install stable
$ rustup default stable
或者 Ubuntu 下使用以下命令来安装 Rust:
$ sudo apt-get install rustup
$ rustup update
# rustup toolchain install stable
$ rustup default stable
MacOS 上,你可以使用 Homebrew 来安装 Rust:
$ brew install rustup
$ rustup update
# rustup toolchain install stable
$ rustup default stable
创建项目
安装完成后,你可以使用以下命令来创建一个新的 Rust项目:
$ cargo new hello-rust
这将创建一个名为 hello-rust 的项目目录,并在其中创建一个 Cargo.toml 文件,其中包含了项目的依赖信息。
进入项目目录:
$ cd hello-rust
你应该会看到以下目录结构:
.
├── Cargo.toml
└── src
└── main.rs
现在你可以编辑 src/main.rs 文件来编写你的 Rust 代码。cargo 会默认生成一个 main.rs 文件,并在其中包含以下代码:
fn main() { println!("Hello, world!"); }
main.rs 是一个 Rust 程序的入口点。
或者你可以使用cargo new -lib hello-rust命令,创建的是一个crates库。Cargo 会创建一个lib.rs文件,它包含库的入口点,并且可以被其他 Rust 项目导入和使用。
Cargo.toml 文件内容如下:
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2024"
[dependencies]
Cargo.toml 文件中,
[package]部分定义了项目的名称、版本和 Rust 版本。[dependencies]部分定义了项目的依赖。在这个例子中,我们没有添加任何依赖,所以我们不需要添加任何依赖。
编译和运行
你可以使用以下命令来编译和运行项目
$ cargo build
$ cargo run
这将编译项目并运行 main.rs 文件。运行后,你会看到输出 Hello, world!。
一个完整的 Rust 项目结构
Cargo 推荐的目录结构,如下:
- Cargo.toml 和 Cargo.lock 保存在 package 根目录下
- 源代码放在 src 目录下
- Crate子模块源代码放在 crates 目录下
- 默认的 lib 包根是 src/lib.rs
- 默认的二进制包根是 src/main.rs
- 其它二进制包根放在 src/bin/ 目录下
- 基准测试 benchmark 放在 benches 目录下
- 示例代码放在 examples 目录下
- 集成测试代码放在 tests 目录下
测试你的代码
tip
良好的编程习惯,一定要写单元测试。下面先认识下,如何编写一个简单的单元测试,后面会有单独的章节来详细介绍如何编写单元测试。可以先使用Copy的技能,照着样例去写,然后慢慢深入理解。接下来去测试你的第一个 Rust 程序吧。
单元测试的结构
单元测试通常包含以下部分:
- 导入模块:使用
use语句导入需要的模块。 - 定义测试函数:使用
#[test]注解定义测试函数。测试函数应该以fn开头,并且返回Result或Option类型。 - 编写测试代码:在测试函数中编写实际的测试代码。你可以使用断言来验证函数的行为。
- 运行测试:使用
cargo test命令来运行测试。
示例:单元测试
fn main() { println!("Hello, world!"); } pub fn add(left: u64, right: u64) -> u64 { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } }
cargo test 会运行 tests 目录下的所有测试文件。你可以使用以下命令来编译和运行测试,运行上述命令后,你会看到以下输出:
running 1 test
test tests::it_works ... ok
successes:
successes:
tests::it_works
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
说明你的单元测试通过了。
在这个例子中,add 函数的测试通过了。如果 add 函数返回的值不是 4,测试将会失败。你可以通过修改 add 函数的返回值来验证这一点。
caution
如果你的测试失败了,你可以通过查看 test 目录下的输出文件来找到具体的错误信息。
running 1 test
test tests::it_works ... FAILED
successes:
successes:
failures:
---- tests::it_works stdout ----
thread 'tests::it_works' panicked at src/main.rs:16:9:
assertion `left == right` failed
left: 4
right: 5
stack backtrace:
0: rust_begin_unwind
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/std/src/panicking.rs:695:5
1: core::panicking::panic_fmt
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:75:14
2: core::panicking::assert_failed_inner
3: core::panicking::assert_failed
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:380:5
4: hello_rust::tests::it_works
at ./src/main.rs:16:9
5: hello_rust::tests::it_works::{{closure}}
at ./src/main.rs:14:18
6: core::ops::function::FnOnce::call_once
at /Users/weirenyan/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
7: core::ops::function::FnOnce::call_once
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
failures:
tests::it_works
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
error: test failed, to rerun pass `-p hello-rust --bin hello-rust`
Rust 返回的测试失败结果信息,是很详细的,所以你一定要详细阅读错误信息,看清楚问题所在。最好的方法是通过错误问题,调试代码并解决这些问题,最终可以成功编译和运行项目,整体过程能快速提升代码能力。
note
经过上述简单的旅程,我们已经对 Rust 有了初步的了解。接下来,我们将深入探索 Rust 的核心概念和特性。那么,让我们继续前进吧!开始进入 Rust 的世界旅行吧!
基础入门
变量与表达式
变量
变量绑定是指将一个值赋给一个名称,这样就可以在程序的其他地方使用这个名称来引用该值了。
变量绑定有两部分组成:let关键字和类型注解(type annotation)。
常量
表达式
表达式是计算结果的代码块。它们可以是任何有效的 Rust 代码,包括函数调用、条件语句、循环等。表达式的值会被赋给变量或用于其他目的计算。
基础数据类型
在 Rust 中,每一个值都有一个特定数据类型(data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。
Rust 是静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。
标量类型
标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们。让我们深入了解它们在 Rust 中是如何工作的。
Rust 支持常见的基本类型:
- 整型: i8, i16, i32, i64, i128, isize
- 无符号整型: u8, u16, u32, u64, u128, usize
- 浮点数: f32, f64
- 布尔值: bool
- 字符: char
整型
整型 是一个没有小数部分的数字。我们在Getting Started 部分已经使用过 u64 整数类型。该类型声明表明,它关联的值应该是一个占据 64 比特位的无符号整数(有符号整数类型以 i 开头而不是 u)。表格 1-1 展示了 Rust 内建的整数类型。我们可以使用其中的任一个来声明一个整数值的类型。
Rust 支持多种整数类型: i8, i16, i32, i64, i128, isize 和 u8, u16, u32, u64, u128, usize。 其中 isize 和 usize 是指针大小的整数类型。
表格 1-1 Rust 中的整型:
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| 架构相关 | isize | usize |
每一个整型变量都可以是有符号或无符号的,并有一个明确的大小。有符号 和 无符号 代表数字能否为负值,也就是说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以二进制补码形式(two’s complement representation) 存储。
另外,isize 和 usize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。
浮点数
Rust 也有两个原生的浮点数(floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。所有的浮点型都是有符号的。
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。下面的代码展示了如何在 let 语句中使用各种数值运算:
#![allow(unused)] fn main() { /** * 数字计算方法。 * 加、减、乘、除、余 */ pub(crate) fn number_calc() { // 加 let sum = 5 + 11; let difference = 45.5 - 4.3; // 乘 let product = 4 * 30; // 除 let quotient = 86.7 / 32.2; // 求余 let remainder = 63 % 5; println!( "sum: {}, diff: {}, product: {}, quotient: {}, remainder:{}", sum, difference, product, quotient, remainder ); } // 测试运行 number_calc(); /// /// 单元测试 /// #[cfg(test)] /// #[cfg(test)] mod tests { // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。 use super::*; #[test] fn test_number_calc() { let sum = 5 + 10; // 加法测试。注意这个断言会导致测试失败。 assert_eq!(sum, 15); number_calc(); } } }
布尔类型
Rust 中的布尔类型有两个可能的值:true 和 false。Rust 中的布尔类型使用 bool 表示。例如:
fn main() { let t = true; let f: bool = false; // with explicit type annotation }
使用布尔值的主要场景是条件表达式,例如 if 表达式。
字符类型
Rust 的 char 类型是语言中最原始的字母类型。下面是一些声明 char 值的例子:
fn main() { let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻'; let ok = '好'; }
注意,我们用单引号声明 char 字面值,而与之相反的是,使用双引号声明字符串字面值。Rust 的 char 类型的大小为四个字节 (four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,带变音符号的字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。
什么是数据类型?
在计算机编程语言中,数据类型(Data Type)是用来定义变量或数据的属性,指定其存储方式、取值范围以及可以执行的操作。数据类型为编译器或解释器提供了如何处理数据的指令,包括:
- 内存分配:决定变量占用多少内存(如 32 位整数占 4 字节)。
- 操作支持:定义允许的操作(如整数支持加减,字符串支持拼接)。
- 值范围:限定数据的有效范围(如 i32 的范围是 -2³¹ 到 2³¹-1)。
- 类型安全:确保操作符合数据类型的规则,防止错误(如避免将字符串当作数字相加)。
数据类型是编程语言的基础,影响程序的性能、内存使用和代码的表达性及可读性。在像 Rust 这样的强类型语言中,数据类型在编译时严格检查,确保类型安全。数据类型的简要历史数据类型的发展与编程语言的演进密切相关:
- 早期(1950s-1960s):在汇编语言和早期高级语言(如 Fortran、ALGOL)中,数据类型较为简单,主要包括整数、浮点数和字符。例如,Fortran 引入了 INTEGER 和 REAL 类型,用于数值计算。
- 结构化编程(1970s):随着 Pascal 和 C 的出现,数据类型变得更加丰富,支持结构体(struct)和数组等复合类型,允许更复杂的数据组织。
- 面向对象编程(1980s-1990s):Smalltalk 和 C++ 引入了类和对象作为数据类型,结合数据和行为。类型系统开始支持继承和多态。
- 现代语言(2000s-至今):语言如 Java、Python 和 Rust 引入了更高级的类型系统,包括泛型、动态类型、枚举和模式匹配。Rust 等语言强调类型安全和内存安全,引入所有权和生命周期等概念。
- 类型系统理论:类型理论(如 Hindley-Milner 类型推导)影响了现代语言(如 Haskell、TypeScript),支持更复杂的类型推导和静态检查。
数据类型的演进反映了编程语言对性能、灵活性和安全性的不断追求。数据类型的分类数据类型的分类方式因编程语言而异,但通常可以分为以下几大类:
- 按数据结构分类
- 标量类型(Scalar Types):
- 表示单一值。
- 示例:
- 整数(如 Rust 的
i8,i32,u64):存储整数值。 - 浮点数(如
f32,f64):存储小数。 - 布尔值(bool):表示
true或false。 - 字符(char):存储单个 Unicode 字符(如 Rust 的
char)。
- 整数(如 Rust 的
- 常见于所有语言,用于基本计算。
- 复合类型(Compound Types):
- 由多个值组成,封装更复杂的数据结构。
- 示例:
- 数组(Array):固定长度的同类型元素集合(如 Rust 的
[i32; 5])。 - 元组(Tuple):固定长度的异构类型集合(如 Rust 的
(i32, f64, char))。 - 结构体(Struct):自定义类型,包含命名字段(如 Rust 的
struct)。 - 枚举(Enum):表示一组可能的值(如 Rust 的
enum)。 - 字符串:如 Rust 的
String和&str,存储文本数据。 - 集合类型:如向量(Rust 的
Vec<T>)、哈希表(HashMap)等。
- 数组(Array):固定长度的同类型元素集合(如 Rust 的
- 复合类型用于组织复杂数据,支持更高层次的抽象。
- 按类型检查分类
- 静态类型(Static Typing):
- 类型在编译时确定,变量的类型不可更改。
- 示例:Rust、C、Java。
- 优点:编译时捕获类型错误,性能更高。
- 动态类型(Dynamic Typing):
- 类型在运行时确定,变量类型可动态变化。
- 示例:Python、JavaScript。
- 优点:灵活性高,开发速度快。
- 结构化类型 vs 名称类型:
- 结构化类型(Structural Typing):类型兼容性基于结构(如 TypeScript 的鸭子类型)。
- 名称类型(Nominal Typing):类型基于名称(如 Rust、Java)。
- 按内存分配分类
- 基本/原生类型(Primitive Types):
- 由语言直接支持,存储在栈上,固定大小。
- 示例:Rust 的
i32,f64,bool,char。
- 引用/堆类型(Reference/Heap Types):
- 数据存储在堆上,通过引用访问,动态分配内存。
- 示例:Rust 的
String,Vec<T>,Box<T>。 - 通常涉及指针或引用,需考虑内存管理。
- 按功能分类
- 数值类型:用于计算,如整数、浮点数。
- 文本类型:如字符串(
String,&str)或字符(char)。 - 逻辑类型:如布尔值(
bool)。 - 集合类型:如数组、列表、字典。
- 时间类型:如 Rust 标准库的
std::time::Duration或chrono::DateTime,用于表示时间和日期。 - 函数类型:在支持函数式编程的语言中,函数本身也是一种类型(如 Rust 的闭包类型
Fn、FnMut)。 - 用户自定义类型:通过结构体、枚举或类定义的类型。
- 其他特殊分类
- 泛型类型(Generic Types):允许类型参数化(如 Rust 的
Vec<T>)。 - 空类型/可选类型:如 Rust 的
Option<T>,表示可能为空的值。 - 错误类型:如 Rust 的
Result<T, E>,用于错误处理。 - 动态类型(Any Types):如 Rust 的
Box<dyn Trait>,支持运行时多态。
复合类型
复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
元组(tuple)
#![allow(unused)] fn main() { /** * tupl_sample */ pub(crate) fn tupl_sample() { println!("datatype tupl_sample .....start"); let x: (i32, f64, u8) = (500, 6.4, 1); //使用dot(.) 获取元组数值,offset 从0开始。 //获取元组第1个值 let five_hundred = x.0; //获取元组 第2个值 let six_point_four: f64 = x.1; //获取元组 第3个值 let one: u8 = x.2; println!("tupl:({},{},{})", five_hundred, six_point_four, one); let s1 = String::from("hello"); let (s2, len) = calc_length(s1); println!("The length of '{}' is {}.", s2, len); println!("datatype tupl_sample .....end\n"); } /** * calc length */ fn calc_length(s: String) -> (String, usize) { let length = s.len(); // len() 返回字符串的长度 (s, length) } }
数组(array)
- 固定数组 array:
定义:使用方括号 [] 定义一个数组,每个元素之间用逗号分隔。
完整的array样例代码如下:
#![allow(unused)] fn main() { /// /// Array Sample /// 数组 /// pub(crate) fn array_sample() { println!("datatype::array_sampe ...... start"); //定义 12个月 数组 let months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; //获取数组中元素 println!("Months array first is {:?}", months[0]); println!("Months array second is {:?}", months[1]); println!("Months array all is {:?}", months); // 编译器自动推导出one的类型 let one = [1, 2, 3]; // 显式类型标注 let two: [u8; 3] = [1, 2, 3]; let blank1 = [0; 3]; let blank2: [u8; 3] = [0; 3]; // arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3] let arrays: [[u8; 3]; 4] = [one, two, blank1, blank2]; // 借用arrays的元素用作循环中 for a in &arrays { print!("{:?}: ", a); // 将a变成一个迭代器,用于循环 // 你也可以直接用for n in a {}来进行循环 for n in a.iter() { print!("\t{} + 10 = {}", n, n + 10); } let mut sum = 0; // 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1 for i in 0..a.len() { sum += a[i]; } println!("\t({:?} = {})", a, sum); } println!("array_sampe ...... end \n"); } }
- 变长数组 Vec:
定义一个变量并使用 vec! 宏来初始化它。vec! 宏会自动推断数组的类型。
或者创建一个空的数组:
let mut empty_vec = Vec::new();
完整的Vec样例代码如下:
#![allow(unused)] fn main() { /// /// Vec Samle /// Vector 为动态数组 /// pub(crate) fn vet_sample() { println!("vet_sample ......start"); let v = vec![12, 34, 56, 78]; let first = v.first(); println!("ver fist is {:?}", first); //fist unwrap option is 12 println!("ver fist is {}", first.unwrap()); let mut sum = 0; //iter every item for n in v { println!("vet print item is {}", n); sum += n; } println!("vet all sum is {}", sum); //创建一个空的Vec let mut empty_vec = Vec::new(); //push item to vec empty_vec.push(123); empty_vec.push(456); empty_vec.push(789); println!("empty vec is {:?}", empty_vec); //创建一个包含5个元素的Vec,每个元素都是0 let zero_vec = vec![0; 5]; println!("zero vec is {:?}", zero_vec); //创建一个包含5个元素的Vec,每个元素都是0 let mut zero_vec = Vec::with_capacity(5); //push item to vec zero_vec.push(0); zero_vec.push(0); zero_vec.push(0); zero_vec.push(0); zero_vec.push(0); println!("zero vec is {:?}", zero_vec); println!("vet_sample ......end\n"); } }
字符串
在 Rust 中,字符串类型是 str,它是不可变的字符串字面量切片,类型为 &str, 是在编译时就固定不可变的。 而 String 是一个字符串类型, 编译时动态分配, 可变长度。
日期时间
- Rust 标准库时间
Rust 的标准库,std::time 库提供了基础的时间处理功能。
SystemTime是一个表示时间的结构体,它是一个不可变的时间戳。表示一个时间点。 你可以使用SystemTime来获取当前时间点,并且可以使用UNIX_EPOCH来获取自Unix epoch (1970-01-01 00:00:00 UTC)以来的时间戳(Unixtime)。Instant是用于高精度单调时间测量(不考虑系统时钟调整),适合性能测试或计时。Duration是表示一个时间间隔,以秒和纳秒为单位。时间加减:可与 SystemTime 或 Instant 结合使用
以下是使用 SystemTime 和 Instant 的示例,你可以直接复制到你的 Rust 项目中直接运行,查看输出结果:
#![allow(unused)] fn main() { use std::time::{Duration, Instant, SystemTime}; use std::thread::sleep; /// Sample function to demonstrate time operations. pub fn time_sample() { //Instant let now: Instant = Instant::now(); println!("Instant: {:?}!", now); // 输出当前时间戳 let elapsed = now.elapsed(); println!("Elapsed: {:.2?}", elapsed); // 输出经过的时间 //Duration let timeout = Duration::from_secs(1); let start = Instant::now(); sleep(Duration::from_millis(500)); if start.elapsed() > timeout { println!("Timeout!"); } else { println!("Operation completed within timeout."); } //SystemTime 获取当前时间戳 let now = SystemTime::now(); //fetch unixtime, return current time in seconds since Unix epoch.it since 1970-01-01 00:00:00 UTC was seconds ago let since_the_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap(); //unixtime println!("Seconds since the epoch: {}s", since_the_epoch.as_secs()); //当前线程休眠1000ms sleep(Duration::from_millis(1000)); //返回now与当前时间的差值,单位为秒 let elapsed = now.elapsed().unwrap(); println!("Seconds since the elapsed: {}s", elapsed.as_secs()); //打印输出 1s } ///to call run the time_sample function time_sample(); // to unit test the time_sample function #[cfg(test)] mod tests { use super::*; #[test] fn test_time_sample() { time_sample(); } } }
tip
什么是Unix epoch?
Unix epoch 是一个固定的时间点,即 1970年1月1日 00:00:00 UTC。
什么是Unixtime?
Unix time 是 Unix epoch 的时间戳,(也称为 POSIX time 或 epoch time),它是一个自从 Unix epoch 开始经过的秒数,它不考虑闰秒,以简化和标准化时间计算。通常表示为一个整数。简单来说,Unix epoch 是基准点,Unix time 是从这个基准点开始的秒数计数。Unixtime 是一个非常常用的时间戳格式,在许多编程语言和系统中都广泛使用。
Unix Time 的作用和优点:
- 简化时间存储和计算: Unix time 是一个简单的整数,非常适合在计算机内部存储和进行时间比较、计算等操作。这比处理复杂的日期、月份、年份、时区和闰年规则要简单得多。
- 跨平台兼容性: 几乎所有主流的操作系统、编程语言和数据库都支持 Unix time,使其成为在不同系统之间传递时间信息时的通用标准。
- 精确性和一致性: Unix time 避免了时区、夏令时等问题,确保了时间表示的精确性和一致性。它通常以 32 位或 64 位整数存储,能够表示非常长的时间范围。
- Chrono 库日期时间
Rust 社区提供了 chrono 库来处理日期和时间。 你可以使用 chrono::prelude::NaiveDate 来表示日期和 chrono::prelude::NaiveDateTime 来表示日期时间,并且可以使用 chrono::Duration 来表示时间差。
- 依赖配置
添加 chrono 库到你的项目中,你可以使用以下命令来安装:
cargo add chrono
或者修改 Cargo.toml 文件中的依赖项:
[dependencies]
chrono = { version = "0.4.39", features = ["serde"] }
- 示例代码
如何获取当前日期和时间呢? 你可以使用 chrono::Local.now() 来获取当前的本地日期和时间。 你也可以使用 chrono::Utc.now() 来获取当前的 UTC 日期和时间。
获取本地当前日期和时间:
use chrono::prelude::*;
use chrono::{DateTime, Local};
fn main() {
let now = Local.now();
println!("Current date and time: {}", now);
}
获取 UTC当前日期和时间:
use chrono::{DateTime, Utc};
fn main() {
let now = Utc.now();
println!("Current date and time: {}", now);
}
一个更完成样例代码,展示了如何使用 chrono 库来获取当前日期和时间,并将其格式化为字符串:
fn date_sample() {
// 使用 from_ymd_opt 创建 NaiveDate
let date = NaiveDate::from_ymd_opt(2024, 10, 26).unwrap();
println!("Date: {}", date);
// 使用 from_hms_opt 创建 NaiveTime
let time = NaiveTime::from_hms_opt(12, 30, 0).unwrap();
println!("Time: {}", time);
// 使用 new 创建 NaiveDateTime
let datetime = NaiveDateTime::new(date, time);
println!("DateTime: {}", datetime);
// 使用 with_ymd_and_hms 创建 DateTime<Utc>
let utc_datetime = Utc.with_ymd_and_hms(2024, 10, 26, 12, 30, 0).unwrap();
println!("UTC DateTime: {}", utc_datetime);
// 使用 with_ymd_and_hms 创建 DateTime<Local>
let local_datetime = Local.with_ymd_and_hms(2024, 10, 26, 12, 30, 0).unwrap();
println!("Local DateTime: {}", local_datetime);
// 获取当前 UTC 时间
let now_utc = Utc::now();
println!("Now (UTC): {}", now_utc);
// 获取当前本地时间
let now_local = Local::now();
println!("Now (Local): {}", now_local);
//日期格式化
let now = Utc::now();
// 常用格式
println!("ISO 8601 / RFC 3339: {}", now.to_rfc3339()); // 推荐的格式
println!(
"Year-Month-Day Hour:Minute:Second: {}",
now.format("%Y-%m-%d %H:%M:%S")
);
println!(
"Day/Month/Year Hour:Minute:Second: {}",
now.format("%d/%m/%Y %H:%M:%S")
);
println!("Month Day, Year: {}", now.format("%B %d, %Y"));
println!("Weekday, Day Month Year: {}", now.format("%A, %d %B %Y"));
// 自定义格式
println!("Custom format: {}", now.format("%a %b %e %T %Y"));
// 时间戳 (Unix timestamp)
println!("Timestamp (seconds): {}", now.timestamp());
println!("Timestamp (milliseconds): {}", now.timestamp_millis());
//日期解析
let datetime_str = "2024-10-26 12:30:00";
let datetime = NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S").unwrap();
println!("Parsed DateTime: {}", datetime);
let date_str = "2024-10-26";
let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap();
println!("Parsed Date: {}", date);
let rfc3339_str = "2024-10-26T12:30:00Z";
let rfc3339_datetime = DateTime::parse_from_rfc3339(rfc3339_str).unwrap();
println!("Parsed RFC3339 DateTime: {}", rfc3339_datetime);
//错误处理
let invalid_date_str = "2024-13-26";
let invalid_date = NaiveDate::parse_from_str(invalid_date_str, "%Y-%m-%d");
match invalid_date {
Ok(_) => println!("Parsed Date: {:?}", invalid_date),
Err(e) => println!("Error parsing date: {}", e),
}
}
了解所有权
什么是所有权?
所有权系统的基本概念
所有权遇到问题
1. 如何解决函数内部定义变量如何返回出来的问题?
这是 Rust 所有权系统的核心问题。你不能返回一个指向函数内部栈上定义的局部变量的引用。 函数执行完毕后,栈上的局部变量会被清理掉,返回的引用将指向无效内存(悬垂指针
// 这是错误的示例!悬垂指针!
fn create_value_and_return_ref<'a>() -> &'a i32 {
let value = 42; // value 在函数栈上
// &value // 错误!不能返回指向栈上局部变量的引用,因为它在函数返回后就无效了
}
// 这是正确的,但需要一个外部的生命周期 'a
fn get_ref_from_external_source<'a>(data: &'a i32) -> &'a i32 {
data // data 是从外部借用进来的,它的生命周期 >= 函数返回的引用生命周期
}
解决方法:
要将函数内部创建的数据“返回”出来,你必须转移该数据的所有权。Rust 的移动语义(Move Semantics)使得这变得简单且安全:
- 直接返回数据 (按值返回): 函数返回类型是 T,你直接返回函数内部创建的变量。数据的所有权从函数内部转移到调用者。
#![allow(unused)] fn main() { fn create_value_and_return_owned() -> i32 { let value = 42; // value 在函数栈上 value // value 的所有权被移出函数 } // value 在这里不会被 drop,因为它已经被移出 fn create_string_and_return_owned() -> String { let text = String::from("hello"); // text 在函数栈上,但其数据在堆上 text // text 的所有权被移出函数。堆上的数据不会被清理。 } // text 在这里不会被 drop fn create_box_and_return_owned() -> Box<i32> { let boxed_value = Box::new(100); // boxed_value 在函数栈上,它指向堆上的数据 boxed_value // boxed_value 的所有权被移出函数。堆上的数据不会被清理。 } // boxed_value 在这里不会被 drop }
- 返回智能指针: 如果你需要共享数据,可以将内部创建的数据包装在 Rc 或 Arc 等智能指针中,并返回智能指针的副本。数据的实际所有权由智能指针管理,而你返回的是智能指针的共享引用或智能指针本身(所有权转移)。
#![allow(unused)] fn main() { use std::sync::Arc; use std::cell::RefCell; fn create_shared_data() -> Arc<RefCell<i32>> { let data = RefCell::new(0); // data 在函数栈上,它包装了堆上的数据 Arc::new(data) // Arc::new 会将 RefCell 移动到堆上,并返回 Arc 的所有权 } // data 在这里不会被 drop,因为它内部的 RefCell 已经被移到堆上并被 Arc 拥有 fn ownership_shared_sample() { let shared = create_shared_data(); // shared 现在拥有 Arc 的所有权 println!("{}", shared.borrow()); } /// /// 单元测试 /// #[cfg(test)] /// #[cfg(test)] mod tests { // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。 use super::*; #[test] fn test_ownership_shared_sample() { ownership_shared_sample(); println!("print test in mdbook") } } }
结构体
结构体是 Rust 中的一种数据结构,它允许你将多个值组合在一起,并且可以定义方法来操作这些值。你可以使用结构体来定义数据类型,这些数据类型可以包含其他值。
结构体样例代码如下:
#![allow(unused)] fn main() { /** * 结构体 sample */ pub(crate) fn struct_sample() { println!("datatype sample struct_sample .....start"); let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; println!("Struct update filed value by other struct result."); let user2 = User { email: String::from("another@example.com"), ..user1 }; //可以尝试注释以下语句,会报错误,因为user1 所有权已经被借用了, //^ print!("user is {:?}", user1); //所以user2 拥有所有权 ,可以正常打印 println!( "根据已有的结构体实例,创建新的结构体实例.user2: {:?}", user2 ); let user3 = build_user( String::from("another@example.com"), String::from("someusername456"), ); let user4: User = User { active: user3.active, username: user3.username, email: String::from("another@example.com"), sign_in_count: user1.sign_in_count, }; println!("user3 user.emal:{}", user3.email); //以下语句报: //borrow of moved value: `user3.username` //move occurs because `user3.username` has type `String`, which does not implement the `Copy` //可以尝试删除注释 //^ println!("user3 user.username:{}",user3.username); println!("user build result user4 is {:?}", user4); println!("datatype sample struct_sample .....end\n"); } /// /// 用户信息 结构体 /// #[derive(Debug)] struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { email: email, username: username, active: true, sign_in_count: 1, } } }
结构体字段
结构体方法
枚举
模块
模块命名限制:
- 以字母开头,可以包含数字和下划线。
- 不可使用特殊字符。
命名约定
自定义模块不能与标准库中的模块冲突。例如: core, std。
若模块命名为core,则会出现以下错误。
#![allow(unused)] fn main() { error[E0433]: failed to resolve: could not find marker in core --> crates/domain/src/repository/monitor_repository.rs:8:1 | 8 | #[async_trait] | ^^^^^^^^^^^^^^ could not find marker in core }
特征
特征,Rust 定义一组行为方法,类似Java 语言中的接口。
#![allow(unused)] fn main() { /// more Trait inherit sample // Define Trait A trait A { fn method_a(&self); } // Define Trait B that inherits from Trait A trait B: A { fn method_b(&self); } // Our struct struct MyStruct; // Implement Trait B for MyStruct // This requires MyStruct to also implement Trait A impl B for MyStruct { fn method_b(&self) { println!("MyStruct implements method_b"); } } // ERROR! Rust tells us: "the trait `A` is not implemented for `MyStruct`" // Even though we only explicitly wrote `impl B for MyStruct`, // because `B: A`, MyStruct *must* also satisfy `A`. // We need to add the `impl A for MyStruct` block: // when `A` is empty method ,must to implement "the trait `a`" impl A for MyStruct { fn method_a(&self) { println!("MyStruct implements method_a"); } } /// more trait inherit sample fn multi_inherit_sample() { let s = MyStruct; s.method_a(); // Can call method from A s.method_b(); // Can call method from B // We can also treat it as an A trait object because B implies A let a_trait_obj: &dyn A = &s; a_trait_obj.method_a(); let b_trait_obj: &dyn B = &s; b_trait_obj.method_a(); // Can call method_a through B trait object b_trait_obj.method_b(); } /// /// 单元测试 /// #[cfg(test)] /// #[cfg(test)] mod tests { // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。 use super::*; #[test] fn test_multi_inherit_sample() { multi_inherit_sample(); } } }
高级进阶
测试
Mock 测试
Rust 中有类似Java Mockito 的测试Mock框架,mockall 框架。
下面是一个完成Mock 测试样例代码:
#![allow(unused)] fn main() { use mockall::automock; use std::sync::Arc; /// trait mock sample #[automock] trait HmsMonitorService { fn monitor(&self) -> bool; } #[derive(Clone)] pub struct MonitorMessageConsumerListener { monitor_service: Arc<dyn HmsMonitorService>, } /// async trait sample #[automock] #[async_trait::async_trait] trait HmsMonitorAsyncService { async fn monitor(&self) -> bool; } #[derive(Clone)] pub struct MonitorMessageController { monitor_service: Arc<dyn HmsMonitorAsyncService>, } #[cfg(test)] mod tests { use super::*; #[test] fn test_monitor() { let mut mock = MockHmsMonitorService::new(); mock.expect_monitor().returning(|| true); let listener = MonitorMessageConsumerListener { monitor_service: Arc::new(mock), }; assert!(listener.monitor_service.monitor()); } #[tokio::test] async fn test_monitor_async() { let mut mock = MockHmsMonitorAsyncService::new(); mock.expect_monitor().returning(|| true); let listener = MonitorMessageController { monitor_service: Arc::new(mock), }; assert!(listener.monitor_service.monitor().await); } } }
Rspec 测试
文件目录操作
获取 HOME 目录
使用std::env 模块来获取 HOME 目录。也可以使用dotenvy 从 .env 文件中加载环境变量。
安装:
cargo add home
cargo add dotenvy
或者配置Cargo.toml
dotenvy = "0.15.7"
home = "0.5.11"
以下示例代码,主要实现通过dotenv 加载环境变量,获取当前环境变量全部列表配置,并打印出来。
- 获取 HOME 环境变量。
- 获取 CARGO_MANIFEST_DIR 环境变量。
- 获取 Cargo.toml 所在的目录。
样例代码:
#![allow(unused)] fn main() { /// # dotenv_sample /// use dotenvy crate to load environment variables from .env file. /// Fails if .env file not found, not readable or invalid. /// dotenvy::dotenv()?; /// use dotenvy; use home; use std::env::{self, home_dir}; use std::error::Error; /// dotenv_sample /// use dotenvy crate to load environment variables from .env file. fn dotenv_sample() -> Result<(), Box<dyn Error>> { // Load environment variables from .env file. // Fails if .env file not found, not readable or invalid. dotenvy::dotenv()?; // Iterate over all environment variables for (key, value) in env::vars() { println!("{key}: {value}"); } // 获取 HOME 环境变量 let home = dotenvy::var("HOME")?; println!("HOME: {}", home); // 获取 CARGO_MANIFEST_DIR 环境变量 let cargo_home = env::var("CARGO_MANIFEST_DIR")?; println!("CARGO_MANIFEST_DIR: {}", cargo_home); // 获取 Cargo.toml 所在的目录 let manifest_dir = env!("CARGO_MANIFEST_DIR"); println!("Cargo manifest directory (Project root): {}", manifest_dir); // 构建相对于 Cargo.toml 的路径 let data_path = format!("{}/data/data.txt", manifest_dir); println!("Data file path: {}", data_path); // 使用 PathBuf 构建路径(更推荐) use std::path::PathBuf; let data_path_buf = PathBuf::from(manifest_dir).join("data").join("data.txt"); println!("Data file path (PathBuf): {}", data_path_buf.display()); Ok(()) } fn current_dir_sample() -> Result<(), Box<dyn std::error::Error>> { // 获取当前工作目录 let current_dir = env::current_dir()?; println!("Current directory: {}", current_dir.display()); // 获取可执行文件的完整路径 let exe_path = env::current_exe()?; // 从路径中提取目录部分 let exe_dir = exe_path .parent() .ok_or("Could not get executable directory")?; println!("Executable directory: {}", exe_dir.display()); // 获取 HOME 目录 env::home_dir().map(|home| println!("Home directory: {}", home.display())); // 获取 HOME 目录 home::home_dir().map(|home| println!("Home directory: {}", home.display())); // 获取 HOME 目录 // 优先使用 home crate home::cargo_home().map(|home| println!("Cargo home directory: {}", home.display())); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_dotenv_sample() { dotenv_sample().unwrap(); } #[test] fn test_current_dir_sample() { current_dir_sample().unwrap(); } } }
临时文件
在 Rust 中,你可以使用 std::fs 模块来创建和管理临时文件。
安装:
cargo add tempfile
或者配置Cargo.toml
tempfile = "3.10.0"
样例代码:
#![allow(unused)] fn main() { use std::env; use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; use tempfile::{tempdir, NamedTempFile}; /** * 临时文件样例, * 此创建一个系统临时文件,并进行写入内容,读取临时文件内容。 */ pub(crate) fn tempfile_sample() { // 系统临时目录 let tmpdir = std::env::temp_dir(); println!("temp dir location: {:?}", tmpdir); let currdir = std::env::current_dir().unwrap(); println!("current dir: {:?}", currdir); // Write let mut tmpfile: File = tempfile::tempfile().unwrap(); println!("tempfile : {:?}", tmpfile); write!(tmpfile, "Hello World!").unwrap(); // Seek to start tmpfile.seek(SeekFrom::Start(0)).unwrap(); // Read let mut buf = String::new(); tmpfile.read_to_string(&mut buf).unwrap(); assert_eq!("Hello World!", buf); } /** * */ pub(crate) fn temp_namedfile_sample() { let text = "Brian was here. Briefly."; let home_dir: std::path::PathBuf = env::home_dir().expect("Failed to get home directory"); // Create a file inside of path by `NamedTempFile::new_in(paht)`. let mut file1 = NamedTempFile::new_in(home_dir).unwrap(); println!("tempfile : {:?}", { &file1 }); // Re-open it. let mut file2 = file1.reopen().unwrap(); // Write some test data to the first handle. file1.write_all(text.as_bytes()).unwrap(); // Read the test data using the second handle. let mut buf = String::new(); file2.read_to_string(&mut buf).unwrap(); assert_eq!(buf, text); } /** * 临时目录创建,临时文件 */ pub(crate) fn tempdir_addfile() { // Create a directory inside of `std::env::temp_dir()`. let dir = tempdir().unwrap(); let file_path = dir.path().join("my-temporary-note.txt"); let mut file = File::create(file_path).unwrap(); writeln!(file, "Brian was here. Briefly.").unwrap(); // By closing the `TempDir` explicitly, we can check that it has // been deleted successfully. If we don't close it explicitly, // the directory will still be deleted when `dir` goes out // of scope, but we won't know whether deleting the directory // succeeded. drop(file); dir.close().unwrap(); } /// /// 单元测试 /// #[cfg(test)] /// #[cfg(test)] mod tests { // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。 use super::*; #[test] fn test_tempfile() { tempfile_sample(); tempdir_addfile(); temp_namedfile_sample(); } } }
CSV 文件操作
安装csv 文件cartes包
cargo add csv
或者修改Cargo.toml配置:
csv = "1.3.1"
样例代码:
#![allow(unused)] fn main() { use std::{error::Error, io, process}; use anyhow::Result; use csv::Reader; use csv::{ReaderBuilder, WriterBuilder}; use serde::{Deserialize, Serialize}; /// 定义 CSV 结构体 并使用 serde 进行序列化和反序列化 /// 使用 serde 的属性来指定字段名和映射 #[derive(Debug, Deserialize, Serialize)] struct Employee { #[serde(rename = "ID")] id: u32, #[serde(rename = "Name")] name: String, #[serde(rename = "Age")] age: u8, #[serde(rename = "Department")] department: String, #[serde(rename = "Salary")] salary: f64, } /// 使用 serde 的属性来指定字段名和映射 #[derive(Debug, serde::Deserialize)] struct Record { city: String, region: String, country: String, population: Option<u64>, } /// 处理 CSV 文件 fn process_employees(input_path: &str, output_path: &str) -> Result<()> { // 读取 CSV 文件 (自动推断分隔符) let mut reader = ReaderBuilder::new() .has_headers(true) .from_path(input_path)?; // 反序列化为 Employee 结构体 let employees: Vec<Employee> = reader.deserialize().collect::<Result<_, _>>()?; // 收集时处理错误 // 过滤数据: 保留薪资 > 5000 且年龄 < 50 的员工 let filtered: Vec<_> = employees .into_iter() .filter(|e| e.salary > 5000.0 && e.age < 50) .collect(); // 写入新 CSV let mut writer = WriterBuilder::new() .delimiter(b',') .has_headers(false) .from_path(output_path)?; // 写入表头 writer.write_record(&["ID", "Name", "Age", "Department", "Salary"])?; // 序列化并写入数据 for employee in filtered { //序列化并写入数据 writer.serialize(employee)?; } writer.flush()?; // 确保数据写入磁盘 Ok(()) } /// 处理大型 CSV 文件 fn process_large_csv() -> Result<()> { // 读取 CSV 文件 let mut reader = Reader::from_path("data/large_data.csv")?; // 逐行读取并处理 // 使用迭代器逐行读取 for result in reader.deserialize::<Employee>() { let employee: Employee = result?; // 逐条处理避免内存溢出 println!("{}", employee.name) } Ok(()) } /// 读取 CSV 文件并处理数据 fn csv_sample() -> Result<()> { // 输入输出文件路径 let input = "data/employees.csv"; let output = "data/filtered_employees.csv"; // 处理数据 process_employees(input, output)?; println!("CSV 处理完成!结果已保存至 {}", output); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_csv_sample() { assert!(csv_sample().is_ok()); } #[test] fn test_process_large_csv() { assert!(process_large_csv().is_ok()); } } }
数据库
并发异步
宏
声明宏
一个简单的声明宏(Macro), Say Hello
一个简单的声明宏(Macro),say_hello,用于打印一条消息。
#![allow(unused)] fn main() { macro_rules! say_hello { () => { println!("Hello from macro!"); }; } fn declare_macros_hello_sample() { say_hello!(); // 调用宏 } }
带参数的声明宏
带参数的声明宏(Macro),make_vec,用于创建一个动态数组,并初始化值,与标准库的vec! 很像吧。
#![allow(unused)] fn main() { macro_rules! make_vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } fn declare_macros_make_sample() { let v = make_vec!(1, 2, 3, 4); println!("{:?}", v); // [1, 2, 3, 4] } }
服务
序列化
JSON 序列化
使用 serde_json 库进行序列化和反序列化
安装serde 文件cartes包
cargo add serde
cargo add serde-transcode
cargo add serde_json
或者修改Cargo.toml配置:
serde = { version = "1.0.196", features = ["derive"] }
serde-transcode = "1.1.1"
serde_json = "1.0.113"
样例代码:
#![allow(unused)] fn main() { //! //! JSON Sample //! use std::io; use serde::{Deserialize, Serialize}; use serde_json::{Result, Value}; /** * parse untype JSON data * return Value HashMap */ pub(crate) fn untyped_sample() -> Result<()> { // Some JSON input data as a &str. Maybe this comes from the user. let data = r#" { "name": "John Doe", "age": 43, "phones": [ "+44 1234567", "+44 2345678" ] }"#; // Parse the string of data into serde_json::Value. let v: Value = serde_json::from_str(data)?; // Access parts of the data by indexing with square brackets. println!("Please call {} at the number {}", v["name"], v["phones"][0]); Ok(()) } /// Struct Person #[derive(Serialize, Deserialize)] struct Person { name: String, age: u8, phones: Vec<String>, } /** * Struct serialize to Json */ pub(crate) fn typed_sample() -> Result<()> { // Some JSON input data as a &str. Maybe this comes from the user. let data = r#" { "name": "John Doe", "age": 43, "phones": [ "+44 1234567", "+44 2345678" ] }"#; // Parse the string of data into a Person object. This is exactly the // same function as the one that produced serde_json::Value above, but // now we are asking it for a Person as output. let p: Person = serde_json::from_str(data)?; // Do things just like with any other Rust data structure. println!("Please call {} at the number {}", p.name, p.phones[0]); Ok(()) } /** * JSON transcode */ pub(crate) fn json_transcode_sample() { // A JSON input with plenty of whitespace. let input = r#" { "a boolean": true, "an array": [3, 2, 1] } "#; // A JSON deserializer. You can use any Serde Deserializer here. let mut deserializer = serde_json::Deserializer::from_str(input); // A compacted JSON serializer. You can use any Serde Serializer here. let mut serializer = serde_json::Serializer::new(io::stdout()); // Prints `{"a boolean":true,"an array":[3,2,1]}` to stdout. // This line works with any self-describing Deserializer and any Serializer. serde_transcode::transcode(&mut deserializer, &mut serializer).unwrap(); } /// Point Struct #[derive(Serialize, Deserialize, Debug)] struct Point { x: i32, y: i32, } /** * JSON process */ pub(crate) fn json_process_sample() { let point = Point { x: 1, y: 2 }; // Convert the Point to a JSON string. let serialized = serde_json::to_string(&point).unwrap(); // Prints serialized = {"x":1,"y":2} println!("serialized = {}", serialized); // Convert the JSON string back to a Point. let deserialized: Point = serde_json::from_str(&serialized).unwrap(); // Prints deserialized = Point { x: 1, y: 2 } println!("deserialized = {:?}", deserialized); } /// /// 单元测试 /// #[cfg(test)] /// #[cfg(test)] mod tests { // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。 use super::*; #[test] fn test_json() { untyped_sample().unwrap(); typed_sample().unwrap(); json_transcode_sample(); json_process_sample(); } } }
精选实战
Database
- SurrealDB
Services
Sequences
Message Queue
MQ Broker 和 Client 样例。
- MQTT
- Rumqtt: Rumqtt 是由 Rust 开发的支持 MQTT 3.1/5 协议规范的全功能的MQTT 的Broker 和 Client 。
数据库
服务
服务依赖注入(Dependency Injection)
简单的静态依赖注入
#![allow(unused)] fn main() { use std::{ any::{Any, TypeId}, collections::HashMap, sync::{Arc, RwLock}, }; pub trait AnyService: Any + Send + Sync + 'static {} impl<T: Any + Send + Sync + 'static> AnyService for T {} // 定义一个 Repository Trait trait UserRepository: Any + Send + Sync { fn get_user(&self, id: u32) -> String; } // 实现一个具体的 Repository struct InMemoryUserRepository; impl UserRepository for InMemoryUserRepository { fn get_user(&self, id: u32) -> String { format!("User {} from InMemoryRepo", id) } } // 定义一个 Service,它依赖 UserRepository struct UserService<R: UserRepository> { repo: R, } impl<R: UserRepository> UserService<R> { fn new(repo: R) -> Self { Self { repo } } fn greet_user(&self, id: u32) -> String { let user = self.repo.get_user(id); format!("Hello, {}!", user) } } // di 通常与异步运行时配合,但这里可以不用 async fn dependency_injection_manul_sample() -> anyhow::Result<()> { // --- 创建 DI 容器 --- let mut services = HashMap::<TypeId, Arc<dyn Any + Send + Sync>>::new(); services.insert( TypeId::of::<InMemoryUserRepository>(), Arc::new(InMemoryUserRepository), ); let repo = services .get(&TypeId::of::<InMemoryUserRepository>()) .unwrap(); let repo = (repo.clone()).downcast::<InMemoryUserRepository>().unwrap(); services.insert( TypeId::of::<UserService<InMemoryUserRepository>>(), Arc::new(UserService::new(InMemoryUserRepository)), ); // --- 从容器中解析出 UserService --- let user_serivce = services .get(&TypeId::of::<UserService<InMemoryUserRepository>>()) .unwrap(); let user_service = user_serivce .clone() .downcast::<UserService<InMemoryUserRepository>>() .unwrap(); // // 调用业务方法 let greeting = user_service.greet_user(42); println!("{}", greeting); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_dependency_injection_sample() { dependency_injection_manul_sample(); } } }
执行输出结果:
running 1 test
test services::dependency_injection_concrete_sample::tests::test_dependency_injection_sample ... ok
successes:
---- services::dependency_injection_concrete_sample::tests::test_dependency_injection_sample stdout ----
Hello, User 42 from InMemoryRepo!
successes:
services::dependency_injection_concrete_sample::tests::test_dependency_injection_sample
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 18 filtered out; finished in 0.00s
服务容器的动态依赖注入
#![allow(unused)] fn main() { use std::any::{Any, TypeId}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; // Base trait for all services trait Service: Send + Sync + 'static { fn name(&self) -> &'static str; } // Example service interfaces trait LoggerService: Send + Sync + 'static { fn log(&self, message: &str); } trait DatabaseService: Send + Sync + 'static { fn query(&self, query: &str) -> String; } // Concrete service implementations struct ConsoleLogger; impl Service for ConsoleLogger { fn name(&self) -> &'static str { "ConsoleLogger" } } impl LoggerService for ConsoleLogger { fn log(&self, message: &str) { println!("Log: {}", message); } } struct InMemoryDatabase; impl Service for InMemoryDatabase { fn name(&self) -> &'static str { "InMemoryDatabase" } } impl DatabaseService for InMemoryDatabase { fn query(&self, query: &str) -> String { format!("Query result for: {}", query) } } // Service with dependencies struct BusinessService { logger: Arc<dyn LoggerService>, database: Arc<dyn DatabaseService>, } impl Service for BusinessService { fn name(&self) -> &'static str { "BusinessService" } } impl BusinessService { fn new(logger: Arc<dyn LoggerService>, database: Arc<dyn DatabaseService>) -> Self { BusinessService { logger, database } } fn perform_task(&self, task: &str) { self.logger.log(&format!("Performing task: {}", task)); let result = self.database.query(task); self.logger.log(&format!("Task result: {}", result)); } } // Service Container struct ServiceContainer { services: Mutex<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>, factories: Mutex<HashMap<TypeId, Box<dyn Fn(&ServiceContainer) -> Arc<dyn Any + Send + Sync>>>>, } impl ServiceContainer { fn new() -> Self { ServiceContainer { services: Mutex::new(HashMap::new()), factories: Mutex::new(HashMap::new()), } } // Register a concrete service fn register<T: Service + 'static>(&self, service: T) { let type_id = TypeId::of::<T>(); let service: Arc<dyn Any + Send + Sync> = Arc::new(service); self.services.lock().unwrap().insert(type_id, service); } // Register a trait object fn register_trait<T: ?Sized + Any + Send + Sync + 'static>( &self, service: Arc<dyn Any + Send + Sync>, type_id: TypeId, ) { // Ensure the service is a trait object // Insert the service into the container let service: Arc<dyn Any + Send + Sync> = service; // let type_id = TypeId::of::<T>(); println!("Registering trait object: {:?}", type_id); // Insert the service into the services map self.services.lock().unwrap().insert(type_id, service); } // Register a factory for lazy initialization fn register_factory< T: Service + 'static, F: Fn(&ServiceContainer) -> Arc<T> + Send + Sync + 'static, >( &self, factory: F, ) { let type_id = TypeId::of::<T>(); let wrapped_factory: Box<dyn Fn(&ServiceContainer) -> Arc<dyn Any + Send + Sync>> = Box::new(move |container| { let service = factory(container); service as Arc<dyn Any + Send + Sync> }); self.factories .lock() .unwrap() .insert(type_id, wrapped_factory); } // Resolve a concrete service fn resolve<T: Service + 'static>(&self) -> Option<Arc<T>> { let type_id = TypeId::of::<T>(); if let Some(service) = self.services.lock().unwrap().get(&type_id) { return service.clone().downcast::<T>().ok(); } if let Some(factory) = self.factories.lock().unwrap().get(&type_id) { let service = factory(self); self.services .lock() .unwrap() .insert(type_id, service.clone()); return service.downcast::<T>().ok(); } None } // Resolve a trait object (accepts Arc<dyn Trait> types); trait objects themselves (dyn Trait) are unsized, // so call this with Arc<dyn Trait> as the type parameter (e.g. resolve_trait::<Arc<dyn LoggerService>>()). fn resolve_trait<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> { let type_id = TypeId::of::<T>(); println!("resolve trait trait object: {:?}", type_id); if let Some(service) = self.services.lock().unwrap().get(&type_id) { let downcast = service.clone().downcast::<T>().ok(); println!("resolve trait trait object: {:?}", downcast.is_some()); return downcast; } None } } fn container_injection_main() { let container = Arc::new(ServiceContainer::new()); // Register concrete services container.register(ConsoleLogger); container.register(InMemoryDatabase); // Register trait objects // using a box or reference package to dyn trait objects container.register_trait::<Arc<dyn LoggerService>>( Arc::new(Arc::new(ConsoleLogger) as Arc<dyn LoggerService>), TypeId::of::<Arc<dyn LoggerService>>(), ); container.register_trait::<Arc<dyn DatabaseService>>( Arc::new(Arc::new(InMemoryDatabase) as Arc<dyn DatabaseService>), TypeId::of::<Arc<dyn DatabaseService>>(), ); // Register a factory for BusinessService (resolve concrete implementations and coerce to trait objects) container.register_factory::<BusinessService, _>(|container| { let logger_concrete = container .resolve::<ConsoleLogger>() .expect("LoggerService not found"); let logger: Arc<dyn LoggerService> = logger_concrete; let database_concrete = container .resolve::<InMemoryDatabase>() .expect("DatabaseService not found"); let database: Arc<dyn DatabaseService> = database_concrete; Arc::new(BusinessService::new(logger, database)) }); // Resolve and use BusinessService let business_service = container .resolve::<BusinessService>() .expect("BusinessService not found"); business_service.perform_task("Process data"); // Resolve and use a trait object (request Arc<dyn LoggerService> as the type parameter) let logger = container .resolve_trait::<Arc<dyn LoggerService>>() .expect("LoggerService not found"); logger.log("Direct logger access"); // Resolve and use a trait object (request Arc<dyn DatabaseService> as the type parameter) let database_service = container .resolve_trait::<Arc<dyn DatabaseService>>() .expect("DatabaseService not found"); let result = database_service.query("Direct database access"); println!("query: {}", result); } #[cfg(test)] mod tests { use super::*; #[test] fn test_injection_main() { container_injection_main(); } } }
执行输出结果:
running 1 test
test services::dependency_injection_dynmaic_sample::tests::test_injection_main ... ok
successes:
---- services::dependency_injection_dynmaic_sample::tests::test_injection_main stdout ----
Registering trait object: TypeId(0x56cbfa3cabf188feb9d956576e38424a)
Registering trait object: TypeId(0x8ffe278beee70f55493c3bb2f23af8b3)
Log: Performing task: Process data
Log: Task result: Query result for: Process data
resolve trait trait object: TypeId(0x56cbfa3cabf188feb9d956576e38424a)
resolve trait trait object: true
Log: Direct logger access
resolve trait trait object: TypeId(0x8ffe278beee70f55493c3bb2f23af8b3)
resolve trait trait object: true
query: Query result for: Direct database access
successes:
services::dependency_injection_dynmaic_sample::tests::test_injection_main
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 18 filtered out; finished in 0.00s
插件(Plugin)
如何实现一个可扩展的插件系统,在 rust中,可以使用 inventory crates 实现类似的目标。
样例代码:
#![allow(unused)] fn main() { use inventory::submit; use std::collections::HashMap; use std::sync::{Arc, Mutex}; // Define a trait for inventory operations trait InventoryOp: Send + Sync { fn name(&self) -> &'static str; fn execute(&self, inventory: &Mutex<HashMap<String, u32>>, item: &str, quantity: u32); } // Structure for registry #[derive(Clone, Copy)] struct InventoryPlugin { name: &'static str, handler: &'static dyn InventoryOp, } inventory::collect!(InventoryPlugin); // Plugin: Empty Operation struct EmptyOp; impl InventoryOp for EmptyOp { fn name(&self) -> &'static str { "" } fn execute(&self, _inventory: &Mutex<HashMap<String, u32>>, _item: &str, _quantity: u32) {} } inventory::submit! { InventoryPlugin { name: "", handler: &EmptyOp, } } // Plugin 1: Add Item struct AddItem; impl InventoryOp for AddItem { fn name(&self) -> &'static str { "add" } fn execute(&self, inventory: &Mutex<HashMap<String, u32>>, item: &str, quantity: u32) { let mut inv = inventory.lock().unwrap(); let current = inv.get(item).copied().unwrap_or(0); inv.insert(item.to_string(), current + quantity); println!("Added {} {} to inventory", quantity, item); } } inventory::submit! { InventoryPlugin { name: "add", handler: &AddItem, } } // Plugin 2: Remove Item struct RemoveItem; impl InventoryOp for RemoveItem { fn name(&self) -> &'static str { "remove" } fn execute(&self, inventory: &Mutex<HashMap<String, u32>>, item: &str, quantity: u32) { let mut inv = inventory.lock().unwrap(); if let Some(current) = inv.get_mut(item) { if *current >= quantity { *current -= quantity; println!("Removed {} {} from inventory", quantity, item); } else { println!("Error: Not enough {} in inventory", item); } } else { println!("Error: {} not found in inventory", item); } } } inventory::submit! { InventoryPlugin { name: "remove", handler: &RemoveItem, } } fn inventory_main() { // Thread-safe inventory let inventory = Arc::new(Mutex::new(HashMap::<String, u32>::new())); inventory.lock().unwrap().insert("apple".to_string(), 10); // Collect plugins let mut plugins: HashMap<&'static str, &'static dyn InventoryOp> = HashMap::new(); for plugin in inventory::iter::<InventoryPlugin> { if !plugin.name.is_empty() { plugins.insert(plugin.name, plugin.handler); } } // Simulate operations let ops = vec![ ("add", "apple", 5), ("remove", "apple", 3), ("add", "sword", 2), ("remove", "apple", 20), ]; for (op_name, item, quantity) in ops { if let Some(op) = plugins.get(op_name) { op.execute(&inventory, item, quantity); } else { println!("Unknown operation: {}", op_name); } } // Print final inventory println!("Final inventory:"); let inv = inventory.lock().unwrap(); for (key, quantity) in inv.iter() { println!("{}: {}", key, quantity); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_inventory_main() { inventory_main(); } } }