From aa96369aede204788d8382a800dffb97d916f6a1 Mon Sep 17 00:00:00 2001 From: "nap.liu" Date: Fri, 15 Dec 2023 09:20:45 +0800 Subject: [PATCH] =?UTF-8?q?15=E3=80=8116=E3=80=8117=20=E7=AB=A0=E5=AE=8C?= =?UTF-8?q?=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 +- .../14.9 Phantom type parameters/src/main.rs | 51 ++ 15.Scoping/15.1 RAII/Cargo.lock | 7 + 15.Scoping/15.1 RAII/Cargo.toml | 8 + 15.Scoping/15.1 RAII/src/main.rs | 64 ++ .../15.2 Ownership and moves/Cargo.lock | 7 + .../15.2 Ownership and moves/Cargo.toml | 8 + .../15.2 Ownership and moves/src/main.rs | 110 +++ 15.Scoping/15.3 Borrowing/Cargo.lock | 7 + 15.Scoping/15.3 Borrowing/Cargo.toml | 8 + 15.Scoping/15.3 Borrowing/src/main.rs | 238 +++++++ 15.Scoping/15.4 Lifetimes/Cargo.lock | 75 ++ 15.Scoping/15.4 Lifetimes/Cargo.toml | 9 + 15.Scoping/15.4 Lifetimes/src/main.rs | 384 ++++++++++ 16.Traits/Cargo.lock | 7 + 16.Traits/Cargo.toml | 8 + 16.Traits/src/main.rs | 655 ++++++++++++++++++ 17.macro_rules/Cargo.lock | 7 + 17.macro_rules/Cargo.toml | 8 + 17.macro_rules/src/main.rs | 279 ++++++++ 20 files changed, 1941 insertions(+), 1 deletion(-) create mode 100644 15.Scoping/15.1 RAII/Cargo.lock create mode 100644 15.Scoping/15.1 RAII/Cargo.toml create mode 100644 15.Scoping/15.1 RAII/src/main.rs create mode 100644 15.Scoping/15.2 Ownership and moves/Cargo.lock create mode 100644 15.Scoping/15.2 Ownership and moves/Cargo.toml create mode 100644 15.Scoping/15.2 Ownership and moves/src/main.rs create mode 100644 15.Scoping/15.3 Borrowing/Cargo.lock create mode 100644 15.Scoping/15.3 Borrowing/Cargo.toml create mode 100644 15.Scoping/15.3 Borrowing/src/main.rs create mode 100644 15.Scoping/15.4 Lifetimes/Cargo.lock create mode 100644 15.Scoping/15.4 Lifetimes/Cargo.toml create mode 100644 15.Scoping/15.4 Lifetimes/src/main.rs create mode 100644 16.Traits/Cargo.lock create mode 100644 16.Traits/Cargo.toml create mode 100644 16.Traits/src/main.rs create mode 100644 17.macro_rules/Cargo.lock create mode 100644 17.macro_rules/Cargo.toml create mode 100644 17.macro_rules/src/main.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 421d036..638c8e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "rust-analyzer.linkedProjects": [ - "./14.Generics/14.9 Phantom type parameters/Cargo.toml" + "./17.macro_rules/Cargo.toml" ] } diff --git a/14.Generics/14.9 Phantom type parameters/src/main.rs b/14.Generics/14.9 Phantom type parameters/src/main.rs index a335698..aafbd4d 100644 --- a/14.Generics/14.9 Phantom type parameters/src/main.rs +++ b/14.Generics/14.9 Phantom type parameters/src/main.rs @@ -30,6 +30,54 @@ struct PhantomStruct { // 注意:代码编译的时候只会为正常的泛型 `A` 申请空间,而幽灵类型 `B` 没有空间, // 所以使用了幽灵类型 `B` 的字段或者位置都不能当做实际存在的类型使用。 +// 幽灵小测试 +fn testcase_unit_clarification() { + // use std::marker::PhantomData; + use std::ops::Add; + + /// 创建两个枚举类型 + #[derive(Debug, Clone, Copy)] + enum Inch {} + #[derive(Debug, Clone, Copy)] + enum Mm {} + + /// `Length` 是一个拥有幽灵泛型 `Unit` 的一个元组结构, + /// 我们明确指定了元组拥有一个 `f64` 的实际类型, + /// f64 天然实现了 `Clone` 和 `Copy` 特性。 + #[derive(Debug, Clone, Copy)] + struct Length(f64, PhantomData); + + /// `Add` 特性会重定义 `Length` 这个类型的 `+` 操作符。 + impl Add for Length { + type Output = Length; + + // add() 返回一个新的 `Length` 结构保存着加法的结果。 + fn add(self, rhs: Length) -> Length { + // 实现 `+` 号的操作。 + Length(self.0 + rhs.0, PhantomData) + } + } + + // 明确指明幽灵类型的实际类型是 `Inch`。 + let one_foot: Length = Length(12.0, PhantomData); + // 明确指明幽灵类型的实际类型是 `Mm`。 + let one_meter: Length = Length(1000.0, PhantomData); + + // 因为我们重定义了 `Length` 类型的 `+` 号操作符,所以这里可以直接使用 `+` 号进行计算, + // 而 `Length` 类型实现了 `Copy` 特性,所以 `add()` 会优先使用 `Copy` 方法来复制一个副本进行所有权的转移, + // 所以这里同一个变量使用两次才不会报错。 + let two_feet = one_foot + one_foot; + let two_meters = one_meter + one_meter; + + // 加法操作正常。 + println!("one foot + one_foot = {:?} in", two_feet.0); + println!("one meter + one_meter = {:?} mm", two_meters.0); + + // 没有意义的对比。 + // 编译错误: 类型不匹配 + // let one_feter = one_foot + one_meter; +} + fn main() { // 这里的 `f32` 和 `f64` 类型是给幽灵类型使用的。 // PhantomTuple type specified as ``. @@ -55,4 +103,7 @@ fn main() { // 编译错误!类型匹配不能对比 // println!("_struct1 == _struct2 yields: {}", _struct1 == _struct2); + + // 幽灵类型小测试 + testcase_unit_clarification(); } diff --git a/15.Scoping/15.1 RAII/Cargo.lock b/15.Scoping/15.1 RAII/Cargo.lock new file mode 100644 index 0000000..9359ba1 --- /dev/null +++ b/15.Scoping/15.1 RAII/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rall" +version = "0.1.0" diff --git a/15.Scoping/15.1 RAII/Cargo.toml b/15.Scoping/15.1 RAII/Cargo.toml new file mode 100644 index 0000000..270a464 --- /dev/null +++ b/15.Scoping/15.1 RAII/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rall" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/15.Scoping/15.1 RAII/src/main.rs b/15.Scoping/15.1 RAII/src/main.rs new file mode 100644 index 0000000..3e61002 --- /dev/null +++ b/15.Scoping/15.1 RAII/src/main.rs @@ -0,0 +1,64 @@ +//! +//! 作用域规则 +//! +//! 作用域规则是变量的所有权、借用规则、生命周期 中最重要的一部分, +//! 它给编译器提供了明确的指示,告诉编译器变量什么时候是有效的, +//! 什么时候资源应该被释放,或者哪些变量应该被销毁。 +//! + +/// 在 `Rust` 中变量并不仅仅在栈中保存数据,还会在堆中申请内存并保存堆指针 +/// 比如说 `Box`,`Rust` 中引用并加强了 `RAII` (Resource Acquisition Is Initialization 这个概念起源于 C++) 的使用, +/// 所以每当一个对象的作用域消失了,则这个对象就会被销毁并且回收对应的资源。 +/// +/// 这种行为可以有效的防止内存泄露的问题,而且你永远不需要手动的释放内存, +/// 并且也不需要担心内存泄露的问题。 +fn raii() { + fn create_box() { + // 在堆中申请一块内存来存放数字 + let _box1 = Box::new(3i32); + + // 因为作用域已经结束,所以 `_box1` 在这里被销毁了并且堆上的内存也被回收了。 + } + + // 在堆上申请一块内存来存放数字 + let _box2 = Box::new(5i32); + + // 嵌套的作用域 + { + // 在堆上申请一块内存来存放数字 + let _box3 = Box::new(4i32); + + // 因为作用域已经结束,所以 `_box3` 在这里被销毁了并且堆上的内存也被回收了。 + } + + // 在这里在堆上申请了很多内存,但是又因为作用域结束,马上就被回收了。 + for _ in 0u32..1_000 { + create_box(); + } + + // 因为作用域已经结束,所以 `_box2` 在这里被销毁了并且堆上的内存也被回收了。 +} + +/// Rust 中通过 `Drop` 特性提供了析构函数的概念, +/// 析构函数在资源被回收的时候自动调用,用于清理执行一些清理的动作, +/// 这个特性并不要求所有的类型都要实现它,只有你自己定义的特殊类型需要一些特殊的逻辑的时候 +/// 才会需要手动实现这个特性来实现一些特殊的清理操作。 +/// +/// 下面这个例子就是一个析构函数的实例 +fn descructor() { + struct ToDrop; + + impl Drop for ToDrop { + fn drop(&mut self) { + println!("ToDrop is being dropped"); + } + } + + let x = ToDrop; + println!("Made a ToDrop!"); +} + +fn main() { + raii(); + descructor(); +} diff --git a/15.Scoping/15.2 Ownership and moves/Cargo.lock b/15.Scoping/15.2 Ownership and moves/Cargo.lock new file mode 100644 index 0000000..e4d9c53 --- /dev/null +++ b/15.Scoping/15.2 Ownership and moves/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ownership_and_moves" +version = "0.1.0" diff --git a/15.Scoping/15.2 Ownership and moves/Cargo.toml b/15.Scoping/15.2 Ownership and moves/Cargo.toml new file mode 100644 index 0000000..4b9b5a4 --- /dev/null +++ b/15.Scoping/15.2 Ownership and moves/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ownership_and_moves" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/15.Scoping/15.2 Ownership and moves/src/main.rs b/15.Scoping/15.2 Ownership and moves/src/main.rs new file mode 100644 index 0000000..bbe87c8 --- /dev/null +++ b/15.Scoping/15.2 Ownership and moves/src/main.rs @@ -0,0 +1,110 @@ +//! +//! 拥有权和拥有权转移 +//! +//! 因为变量负责释放自己的资源,所以资源的所有权只能由一个变量拥有, +//! 这个限制可以避免出现资源的多重释放,(对于资源的引用并不会拥有所有权) +//! +//! 当进行变量赋值 `(let x = y)` 或者传递参数 `foo(x)` 的时候,对于 +//! 资源的所有权会被转移,使用 `Rust` 语言表达就是变量发生了转移。 +//! +//! 当资源被转移以后,上一个拥有所有权的变量就不能再次被使用了,这个限制 +//! 也避免了 `悬空指针(dangling pointers)` 的问题 +//! +//! + +fn ownership_and_moves() { + // 这个函数会转移所有权,并且拥有堆上分配的内存 + fn destroy_box(c: Box) { + println!("Destroying a box that contains {}", c); + + // 作用域销毁 `c` 被回收,堆上的内存也被回收。 + } + + // 保存在栈上的数据 + let x = 5u32; + + // 复制了栈上的数据,这里没有用到堆数据,所以在这行代码之后 `x` 还可以继续使用 + let y = x; + + // 因为数据是保存在栈上的,所以这两个变量都是独立并且可用的。 + println!("x is {}, and y is {}", x, y); + + // 通过 `Box` 在堆上分配一块内存并保存数字 `5`,并把 `Box` 绑定给 `a` 变量 + let a = Box::new(5i32); + + println!("a contains: {}", a); + + // 转移 `a` 资源的所有权转移给 `b` + let b = a; + // 把 `a` 中保存的资源地址(这里只复制了指针,并没有复制堆上的数据)复制给 `b`, + // 这时候 `a` 和 `b` 同时都指向堆上面的同一个的资源, + // 但是因为所有权的限制当地址复制给 `b` 的时候我们称 `a` 指向的资源的所有权转移给了 `b`, + // 这时候 `a` 就不再有效,只有 `b` 可以访问这个资源。 + + // 错误!`a` 不能再使用了,因为所有权已经被转移了。 + // println!("a contains: {}", a); + // TODO ^ 移除注释看错误 + + // 这个函数会转移 `b` 的所有权,所以后面也不再能访问 `b` 了 + destroy_box(b); + + // 因为 `b` 的所有权已经转移给了函数 `destroy_box`,所以这里不能再次使用 `b` 了, + // 错误!`b` 不能再使用了,因为所有权已经被转移了。 + //println!("b contains: {}", b); + // TODO ^ 移除注释看错误 +} + +/// 可以在变量的所有权转移的时候让数据可以修改 +fn mutability() { + let immutable_box = Box::new(5u32); + + println!("immutable_box contains {}", immutable_box); + + // 错误! 不可变数据不能修改 + // *immutable_box = 4; + + // 转移变量所有权的时候可以重新修改变量为可变的 + // 也就是通过 `let mut x = x` 这样等于通过所有权转移 + 变量遮蔽 让变量变成了可变的。 + let mut mutable_box = immutable_box; + + println!("mutable_box contains {}", mutable_box); + + // 通过解引用操作符获取可变引用修改数值 + *mutable_box = 4; + + println!("mutable_box now contains {}", mutable_box); +} + +/// 使用解构操作符的时候可以在一个解构语句中同时使用 `转移(所有权转移)` 和 `引用(使用 ref 关键字)`, +/// 这样的话解构操作完成以后,被转移出来的属性在原始对象上就不能再次使用了,解构出来的引用是可以继续再原始对象上用的。 +fn partial_moves() { + #[derive(Debug)] + struct Person { + name: String, + age: Box, + } + + let person = Person { + name: String::from("Alice"), + age: Box::new(20), + }; + + // `name` 字段被转移出来了,`age` 因为使用了 `ref` 关键字只是借用了一个不变的引用。 + let Person { name, ref age } = person; + + println!("The person's age is {}", age); + + println!("The person's name is {}", name); + + // 错误!因为 `name` 字段已经被转移出来了,所以这里不能再把 `person` 当做完整的对象使用。 + // println!("The person struct is {:?}", person); + + // `person` 不能完整的使用,但是可以使用 `person.age` 因为这个属性并没有被转移。 + println!("The person's age from person struct is {}", person.age); +} + +fn main() { + ownership_and_moves(); + mutability(); + partial_moves(); +} diff --git a/15.Scoping/15.3 Borrowing/Cargo.lock b/15.Scoping/15.3 Borrowing/Cargo.lock new file mode 100644 index 0000000..066eac4 --- /dev/null +++ b/15.Scoping/15.3 Borrowing/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "borrowing" +version = "0.1.0" diff --git a/15.Scoping/15.3 Borrowing/Cargo.toml b/15.Scoping/15.3 Borrowing/Cargo.toml new file mode 100644 index 0000000..567c891 --- /dev/null +++ b/15.Scoping/15.3 Borrowing/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "borrowing" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/15.Scoping/15.3 Borrowing/src/main.rs b/15.Scoping/15.3 Borrowing/src/main.rs new file mode 100644 index 0000000..febc691 --- /dev/null +++ b/15.Scoping/15.3 Borrowing/src/main.rs @@ -0,0 +1,238 @@ +//! +//! 变量借用(Borrowing) +//! +//! 大多数情况下我们只想使用变量的数据并不是想转移变量的所有权, +//! `Rust` 提供了 `&` 关键字来创建一个引用,引用只是对数据的借用,而不会转移所有权, +//! 通过在变量前加上 `&` 关键字来创建一个引用。 +//! + +fn example01() { + // 这个函数会转移传递进来的参数的所有权,函数执行完成以后就会销毁参数。 + fn eat_box_i32(boxed_i32: Box) { + println!("Destroying box that contains {}", boxed_i32); + } + + // 这个函数只是对参数的借用,不会销毁参数。 + fn borrow_i32(borrowed_i32: &i32) { + println!("This int is: {}", borrowed_i32); + } + + // `Box` 是在堆中储存的,所以会应用所有权规则。 + let boxed_i32 = Box::new(5_i32); + // `6_i32` 是在栈中储存的,栈中保存的数据会在所有权转移的时候自动复制一份。 + let stacked_i32 = 6_i32; + + // 这里只是对两个参数的借用,所以不会转移所有权,而且在之后的逻辑中可以继续使用这两个变量。 + borrow_i32(&boxed_i32); + borrow_i32(&stacked_i32); + + { + // 手动创建一个引用。 + let _ref_to_i32: &i32 = &boxed_i32; + + // 错误!因为函数要求的是一个可以转移所有权的变量,而这里只提供了一个引用。 + // eat_box_i32(boxed_i32); + // TODO ^ 移除注释查看错误 + + // 这里符合函数要求所以可以使用 + borrow_i32(_ref_to_i32); + + // 当前的作用域结束了,所以 `_ref_to_i32` 会被销毁。 + } + + // 这里可以把 `boxed_i32` 传递进去,因为 `boxed_i32` 可以被转移,当函数执行完成以后,`boxed_i32` 就不能再次使用了。 + eat_box_i32(boxed_i32); +} + +/// 可变变量的所有权转移和借用 +fn mutability() { + #[allow(dead_code)] + #[derive(Clone, Copy)] + struct Book { + // `&'static str` 是一个只读的字符串引用,并且在整个程序的证明周期内都有效。 + author: &'static str, + title: &'static str, + year: u32, + } + + // 函数接收一个不可变引用 + fn borrow_book(book: &Book) { + println!( + "I immutably borrowed {} - {} edition", + book.title, book.year + ); + } + + // 函数接受一个可变的引用。 + fn new_edition(book: &mut Book) { + book.year = 2014; + println!("I mutably borrowed {} - {} edition", book.title, book.year); + } + + // 创建一个不可变的结构体实例 + let immutabook = Book { + // 字符串的字面量是 `&'static str` 类型 + author: "Douglas Hofstadter", + title: "Gödel, Escher, Bach", + year: 1979, + }; + + // 通过所有权转移和重定义让 `immutabook` 变成一个可变的 `mutabook` + // 因为 `Book` 实现了 `Copy` 和 `Clone` 特性,所以这里实际上是复制了一份数据,并没有所有权的转移。 + let mut mutabook = immutabook; + + // 从不可变对象借用一个不可变引用。 + borrow_book(&immutabook); + + // 从可变对象上借用一个不可变引用。 + borrow_book(&mutabook); + + // 从可变对象上借用一个可变引用。 + new_edition(&mut mutabook); + + // 错误!不能从不可变引用上创建可变引用。 + // new_edition(&mut immutabook); + // TODO ^ 移除注释查看错误 +} + +/// 变量的借用规则 +/// 一个数据可以同时被借用无数个不可变引用,但是一个数据同时只允许借用一个可变引用, +/// 当数据存在可变引用,则其他的任何引用都不允许使用。 +/// 或者是数据存在不可变引用,这个不可变引用的使用周期内都不允许再次借用可变引用, +/// 只有不可变引用不再使用了以后才允许借用一个可变引用。 +/// +fn aliasing() { + struct Point { + x: i32, + y: i32, + z: i32, + } + let mut point = Point { x: 0, y: 0, z: 0 }; + + let borrowed_point = &point; + let another_borrow = &point; + + // 使用不可变引用读取数据,这三个都指向同一份数据。 + println!( + "Point has coordinates: ({}, {}, {})", + borrowed_point.x, another_borrow.y, point.z + ); + + // 错误!`point` 在这里不允许借用一个可变的引用,因为下面的 `println!` 中使用了上面的不可变引用。 + // let mutable_borrow = &mut point; + // TODO ^ 移除注释查看错误 + + // The borrowed values are used again here + // 因为这里使用了不可变引用导致了上面的可变引用的借用失败了。 + println!( + "Point has coordinates: ({}, {}, {})", + borrowed_point.x, another_borrow.y, point.z + ); + + // 上面的所有不可变引用在这里都已经不再使用了,所以这里可以借用一个可变的引用。 + let mutable_borrow = &mut point; + + // 通过可变引用修改数据 + mutable_borrow.x = 5; + mutable_borrow.y = 2; + mutable_borrow.z = 1; + + // 错误!因为当前 `point` 已经被借用了一个可变引用,所以不允许再次借用了。 + // let y = &point.y; + // TODO ^ 移除注释查看错误 + + // 错误!因为 `point` 已经存在了一个可变引用,所以这里不允许再借用一个不可变引用。 + // println!("Point Z coordinate is {}", point.z); + // TODO ^ 移除注释查看错误 + + // Ok! Mutable references can be passed as immutable to `println!` + // 这里可以使用对可变引用再次借用不可变引用。 + println!( + "Point has coordinates: ({}, {}, {})", + mutable_borrow.x, mutable_borrow.y, mutable_borrow.z + ); + + // 上面的可变引用已经不再使用了,所以这里可以重新借用一个不可变引用。 + let new_borrowed_point = &point; + println!( + "Point now has coordinates: ({}, {}, {})", + new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z + ); +} + +/// 很多场景下不能通过 `&` 关键字来定义引用,所以需要使用 `ref` 关键字 +fn the_ref_pattern() { + #[derive(Clone, Copy)] + struct Point { + x: i32, + y: i32, + } + + let c = 'Q'; + + // `ref` 关键字可以使用在等号左侧代表借用右边的值。 + // `&` 关键字使用在等号右侧也是代表借用右边的值。 + // 两个关键字的效果是一样的,只不过使用的位置不同。 + let ref ref_c1 = c; + let ref_c2 = &c; + + println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2); + + let point = Point { x: 0, y: 0 }; + + // `ref` 关键字在解构解构的时候非常有用。 + let _copy_of_x = { + // 强制让 `ref_to_x` 是一个不可变引用。 + let Point { + x: ref ref_to_x, + y: _, + } = point; + + // 返回具体的数据,因为 `x` 字段是一个基础值所以会自动复制数据 + *ref_to_x + }; + + // 对 `point` 进行所有权转移,这里实际上是 `Copy` 因为 `Point` 实现了 `Copy` 特性, + // 然后重新绑定为可变对象。 + let mut mutable_point = point; + + { + // `ref` 可以加 `mut` 修饰来表明是一个可变引用 + let Point { + x: _, + y: ref mut mut_ref_to_y, + } = mutable_point; + + // 因为是可变引用,所以这里可以直接修改值 + *mut_ref_to_y = 1; + } + + println!("point is ({}, {})", point.x, point.y); + println!( + "mutable_point is ({}, {})", + mutable_point.x, mutable_point.y + ); + + // 定义一个可变元组 + let mut mutable_tuple = (Box::new(5u32), 3u32); + + { + // 这里对元组进行解构,拿到一个可变的引用。 + let (_, ref mut last) = mutable_tuple; + // 修改可变引用中的数据 + *last = 2u32; + } + + println!("tuple is {:?}", mutable_tuple); +} + +fn main() { + // 基础示例 + example01(); + // 可变引用和不可变引用 + mutability(); + // 可变引用和不可变引用的借用关系 + aliasing(); + // `ref` 的使用场景 + the_ref_pattern(); +} diff --git a/15.Scoping/15.4 Lifetimes/Cargo.lock b/15.Scoping/15.4 Lifetimes/Cargo.lock new file mode 100644 index 0000000..7e5436c --- /dev/null +++ b/15.Scoping/15.4 Lifetimes/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "lifetimes" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/15.Scoping/15.4 Lifetimes/Cargo.toml b/15.Scoping/15.4 Lifetimes/Cargo.toml new file mode 100644 index 0000000..00a4079 --- /dev/null +++ b/15.Scoping/15.4 Lifetimes/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lifetimes" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/15.Scoping/15.4 Lifetimes/src/main.rs b/15.Scoping/15.4 Lifetimes/src/main.rs new file mode 100644 index 0000000..c302ab0 --- /dev/null +++ b/15.Scoping/15.4 Lifetimes/src/main.rs @@ -0,0 +1,384 @@ +//! +//! 声明周期 (Lifetimes) +//! +//! 生命周期的作用是为了让编译器可以明确的知道每一个变量从什么时候开始到什么时候结束。 +//! 声明周期和作用域通常都是一起出现的,但是这两个是不同的概念。 +//! +//! 声明周期取决于当前变量声明的作用域,当变量发生转移则作用域也会转移。 +//! +extern crate rand; + +/// 下面每个变量的线条指示了该变量的生命周期 +fn example01() { + let i = 3; // 变量 `i` 的生命周期开始 ────────────────┐ + // │ + { + // │ + let borrow1 = &i; // `borrow1` 生命周期开始 ───────┐│ + // ││ + println!("borrow1: {}", borrow1); // ││ + } // 变量 `borrow1` 的生命周期终止 ───────────────────────────────┘│ + // │ + // │ + { + // │ + let borrow2 = &i; // `borrow2` 生命周期开始 ───────┐│ + // ││ + println!("borrow2: {}", borrow2); // ││ + } // 变量 `borrow2` 的生命周期终止 ─────────────────────────────────┘│ + // │ +} // 生命周期结束 ──────────────────────────────────────────────────┘ + +/// 明确声明生命周期 +/// 可以使用 `'a` 关键字其中 `a` 可以是任意的字母组合,通常来说都是小写的字母 +/// 比如 `foo<'a>` 表示 `foo` 有一个生命周期参数是 `'a` +fn explicit() { + // `print_refs` 有两个生命周期参数,这两个参数 `x` 和 `y` 都有各自独立的生命周期 `'a` 和 `'b`。 + // 这两个参数的生命周期必须大于等于当前函数的生命周期。 + fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) { + println!("x is {} and y is {}", x, y); + } + + // 这个函数有一个生命周期参数 `'a` 但是该声明周期并没有被参数使用, + // 所以这里的 `'a` 等于 `'static` 也就是整个应用的生命周期 + fn failed_borrow<'a>() { + let _x = 12; + + // 错误: `_x` 的生命周期短于参数的生命周期 `'a` + // let y: &'a i32 = &_x; + // 因为 `'a` 没有被参数使用,所以会被推断为 `'static`,又因为 + // `_x` 的生命周期仅仅存在于函数中,根据生命周期规则,一个小的生命周期不能赋值给大的生命周期。 + } + + // 创建两个变量用于下面的函数。 + let (four, nine) = (4, 9); + + // 传递两个引用到函数中。 + print_refs(&four, &nine); + // 出借方一定要比借用方存活的时间更长。 + // 也就是说这里 `&four` 和 `&nine` 要比函数 `print_refs` 的生命周期更长, + // 因为函数调用完以后函数就不再需要了,但是变量还可以继续使用。 + + failed_borrow(); + // 由于函数 `failed_borrow` 没有参数,所以没有任何方式来约束 `'a` 和 `failed_borrow` 的生命周期关系, + // 所以编译器会把 `'a` 推断为 `'static`,这就代表 函数中所有使用 `'a` 来声明的变量都可以在函数外的任意位置使用, + // 也就是整个应用只要存活就可以使用。 +} + +/// 这里不谈[省略语法](https://doc.rust-lang.org/rust-by-example/scope/lifetime/elision.html),函数的签名有两个强制约束, +/// - 任何的引用值必须有生命周期标注。 +/// - 任何的返回值引用必须引用 输入的参数生命周期或者明确指定为 `'static` +/// +/// 如果返回的引用值和输入没有关联的话则会导致对数据的无效引用(特殊 `'static` 标注不受此规则限制),所以必须禁止!! +/// +fn functions() { + // 函数定义了一个 `'a` 生命周期,这个周期被参数 `x: &'a i32` 引用,也就是 `x` 的生命周期必须大于函数。 + fn print_one<'a>(x: &'a i32) { + println!("`print_one`: x is {}", x); + } + + // 同样的也可以使用可变引用 `mut` 关键字 + fn add_one<'a>(x: &'a mut i32) { + *x += 1; + } + + // 多个参数声明不同的生命周期,也可以多个参数使用同一个生命周期。 + // 不同生命周期的话代表两个参数可以使用不同生命周期的变量。 + fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) { + println!("`print_multi`: x is {}, y is {}", x, y); + } + + // 声明两个生命周期,返回值和第一个声明周期关联,所以这个函数返回值只能是声明周期为 `'a` 的 `x` 参数。 + fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { + x + } + + // 这里因为函数没有参数,所以生命周期会自动推断成 `'static`,因为字符串("foo")和 `String` 的生命周期为当前函数作用域, + // 当函数结束后作用域销毁,导致字符串("foo") 和 `String` 被销毁,返回的引用就变成了无效的引用,所以报错。 + // fn invalid_output<'a>() -> &'a String { + // &String::from("foo") + // } + + let x = 7; + let y = 9; + + print_one(&x); + print_multi(&x, &y); + + let z = pass_x(&x, &y); + print_one(z); + + let mut t = 3; + add_one(&mut t); + print_one(&t); +} + +/// 方法和函数的生命周期规则是一样的参考函数即可 +fn methods() { + struct Owner(i32); + + impl Owner { + // 声明声明周期为实例的生命周期 + fn add_one<'a>(&'a mut self) { + self.0 += 1; + } + fn print<'a>(&'a self) { + println!("`print`: {}", self.0); + } + } + + let mut owner = Owner(18); + + owner.add_one(); + owner.print(); +} + +fn structs() { + // 结构体声明一个生命周期和构建的实例参数声明周期相同 + #[derive(Debug)] + struct Borrowed<'a>(&'a i32); + + // 结构体中的生命周期和构建的生命周期相同,而且每个字段的生命周期也相同。 + #[derive(Debug)] + struct NamedBorrowed<'a> { + x: &'a i32, + y: &'a i32, + } + + // 枚举中也可以指定生命周期,还可以指定部分的特定值的生命周期。 + #[derive(Debug)] + enum Either<'a> { + Num(i32), + Ref(&'a i32), + } + + let x = 18; + let y = 15; + + let single = Borrowed(&x); + let double = NamedBorrowed { x: &x, y: &y }; + let reference = Either::Ref(&x); + let number = Either::Num(y); + + println!("x is borrowed in {:?}", single); + println!("x and y are borrowed in {:?}", double); + println!("x is borrowed in {:?}", reference); + println!("y is *not* borrowed in {:?}", number); +} + +/// 特性也支持定义生命周期,规则和函数非常相似 +/// 只不过 `impl` 关键字可能拥有很多个声明周期声明。 +fn traits() { + // 声明一个有生命周期的结构体 + #[derive(Debug)] + struct Borrowed<'a> { + x: &'a i32, + } + + // 实现标准库的 `Default` 特性并指明声明周期 + impl<'a> Default for Borrowed<'a> { + fn default() -> Self { + // 这里直接使用字面量,让编译器把字面量的生命周期声明成 `'a` + Self { x: &10 } + } + } + + let b: Borrowed = Default::default(); + println!("b is {:?}", b); +} + +/// 泛型约束中的生命周期 +fn bounds() { + use std::fmt::Debug; // 引用标准库 + + #[derive(Debug)] + struct Ref<'a, T: 'a>(&'a T); + // `Ref` 结构体拥有一个泛型参数和一个未知的生命周期 `'a` ,并且约束了 `T` 类型的声明周期为 `'a`, + // 也就是说泛型 `T` 必须要能存活大于等于 `'a` 才可以, + // 换句话说就是 构 `Ref` 的实例的的参数必须要大于 `Ref` 实例的生命周期。 + + // 泛型参数 `T` 使用 `where` 关键字约束为必须实现 `Debug` 特性。 + fn print(t: T) + where + T: Debug, + { + println!("`print`: t is {:?}", t); + } + + // 函数 `print_ref` 有两个泛型参数 泛型 `T` 和一个未知的生命周期 `'a`, + // 参数 `t` 被类型被定义为泛型 `T` 并且类型 `T` 的生命周期和定义的 `'a` 绑定, + // `where` 语句约束了泛型 `T` 必须实现 `Debug` 特性并且要满足声明周期 `'a`, + // 也就是说参数 `t` 可以是任意实现了 `Debug` 特性的类型,但是参数的生命周期必须大于函数运行的生命周期。 + fn print_ref<'a, T>(t: &'a T) + where + T: Debug + 'a, + { + println!("`print_ref`: t is {:?}", t); + } + + let x = 7; + let ref_x = Ref(&x); + + print_ref(&ref_x); + print(ref_x); +} + +/// 强制缩短声明周期 +/// 当生命周期不同的时候需要手动指定声明周期,编译器会尝试自动强制让生命周期相同, +/// 或者手动强制约束多个生命周期之间的关系。 +fn coercion() { + // 编译器会自动推断并对齐两个参数之间的生命周期 + fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 { + first * second + } + + // 这里 `<'a: 'b, 'b>` 强制让 'a 生命周期必须大于等于 'b 的声明周期。 + fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 { + first + } + + let first = 2; // 长一点的生命周期 + + { + let second = 3; // 短一点的生命周期 + + println!("The product is {}", multiply(&first, &second)); + println!("{} is the first", choose_first(&first, &second)); + }; +} + +/// 静态声明周期 +/// +/// Rust 中有几个特殊的生命周期,其中有一种是 `'static`, +/// 有两种使用场景, +/// +/// ``` +/// // 声明静态字符串 +/// let s: &'static str = "hello world"; +/// // 泛型的静态约束 +/// fn generic(x: T) where T: 'static {} +/// ``` +/// +fn statics() { + { + // 声明 `NUM` 是静态生命周期的变量。 + static NUM: i32 = 18; + + // 固定返回静态的变量 `NUM`,这里发生了强制转换,因为 `'static` 的存活时间最久,所以允许转换。 + fn coerce_static<'a>(_: &'a i32) -> &'a i32 { + &NUM + } + + { + // 一个字面量的字符串 `static_string`,字面量天生用于 `'static` 生命周期, + // 尽管当前的作用域被销毁了,引用也被回收了,但是这个字符串依旧存在于二进制文件中不会被销毁。 + let static_string = "I'm in read-only memory"; + println!("static_string: {}", static_string); + } + + { + // 定义一个字面量 `lifetime_num` + let lifetime_num = 9; + + // 使用这个字面量变量调用 `coerce_static` 返回固定的 `NUM` 引用。 + let coerced_static = coerce_static(&lifetime_num); + + println!("coerced_static: {}", coerced_static); + } + + println!("NUM: {} stays accessible!", NUM); + } + + // 使用 `Box::leak` 动态创建 `'static` 变量。 + // `'static` 声明周期实际上只是要求在该变量存在以后可以一直使用,所以可以在程序运行中 + // 动态的创建 `'static` 的引用值,这里可以通过标准库提供的 `Box::leak` 方式来动态创建一个 `'static` 的引用。 + { + use rand::Fill; + + fn random_vec() -> &'static [usize; 100] { + let mut rng = rand::thread_rng(); + let mut boxed = Box::new([0; 100]); + boxed.try_fill(&mut rng).unwrap(); + Box::leak(boxed) + } + + let first: &'static [usize; 100] = random_vec(); + let second: &'static [usize; 100] = random_vec(); + assert_ne!(first, second) + } + + // 使用 特性 + 'static 来约束参数 + { + use std::fmt::Debug; + + fn print_it(input: impl Debug + 'static) { + println!("'static value passed in is: {:?}", input); + } + + // 字面量天生拥有 `'static` 所以可以正常调用 + let i = 5; + print_it(i); + + // 这里对 `i` 借用了一个不可变引用,但是该引用只存在于当前作用域,所以不满足 `'static` 约束 + // print_it(&i); + // TODO 移除注释查看错误 + } +} + +/// 声明周期的省略写法 +/// +/// 编译器可以允许省略一些常见的生命周期写法,这样可以提高代码的可读性, +/// 详细的可省略的写法可以[参考这里](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-elision) +/// +fn elision() { + // 当只有一个参数的时候可以省略生命周期 `elided_input` 和 `annotated_input` 是一样的。 + fn elided_input(x: &i32) { + println!("`elided_input`: {}", x); + } + + fn annotated_input<'a>(x: &'a i32) { + println!("`annotated_input`: {}", x); + } + + // 如果只有一个参数和返回值的话也可以省略生命周期,`elided_pass` 和 `annotated_pass` 也是一样的。 + // 如果只有一个参数和返回值 那么参数和返回值会自动的分配声明周期为 `'a` 也就是 `elided_pass` 会 + // 自动补全成 `annotated_pass` 的写法。 + fn elided_pass(x: &i32) -> &i32 { + x + } + + fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { + x + } + + let x = 3; + + elided_input(&x); + annotated_input(&x); + + println!("`elided_pass`: {}", elided_pass(&x)); + println!("`annotated_pass`: {}", annotated_pass(&x)); +} + +fn main() { + // 生命周期的基础展示 + example01(); + // 明确指定声明周期 + explicit(); + // 函数中的生命周期关系 + functions(); + // 方法中的生命周期关系 + methods(); + // 结构体中的生命周期关系 + structs(); + // 特性中的生命周期 + traits(); + // 泛型约束中的生命周期 + bounds(); + // 声明周期的强制转换 + coercion(); + // 字面量和 `'static` + statics(); + + println!("Hello, world!"); +} diff --git a/16.Traits/Cargo.lock b/16.Traits/Cargo.lock new file mode 100644 index 0000000..0b7d36b --- /dev/null +++ b/16.Traits/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "traits" +version = "0.1.0" diff --git a/16.Traits/Cargo.toml b/16.Traits/Cargo.toml new file mode 100644 index 0000000..460f45f --- /dev/null +++ b/16.Traits/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "traits" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/16.Traits/src/main.rs b/16.Traits/src/main.rs new file mode 100644 index 0000000..4122331 --- /dev/null +++ b/16.Traits/src/main.rs @@ -0,0 +1,655 @@ +//! +//! 特性(Traits) +//! +//! 特性是一些未知类型的(Self)方法的集合,实现了指定特性就可以访问该特性中定义的其他方法。 +//! +//! 特性一个被任何类型实现,下面的代码展示了基础的特性定义和实现。 +//! +//! + +use std::{ + f64::consts::E, + fmt::{Debug, Display}, + path::Iter, +}; + +fn example01() { + // 定义一个类型 + struct Sheep { + naked: bool, + name: &'static str, + } + + // 定义基础的特性函数 + trait Animal { + // 定义关联函数,通过 `Self` 关键字获取实现该类型的类型声明。 + fn new(name: &'static str) -> Self; + + // 定义关联方法 + fn name(&self) -> &'static str; + fn noise(&self) -> &'static str; + + // 定义并实现关联方法,在该方法中使用当前特性中定义的其他方法。 + // 这个这个方法有函数体,也就代表这个方法是一个默认方法,实现当前特性的类型可以不实现该方法 + fn talk(&self) { + println!("{} says {}", self.name(), self.noise()); + } + } + + // 对定义的类型实现一些私有方法。 + impl Sheep { + // 定义关联方法 + fn is_naked(&self) -> bool { + self.naked + } + + // 定义关联方法 + fn shear(&mut self) { + if self.is_naked() { + // Implementor methods can use the implementor's trait methods. + println!("{} is already naked...", self.name()); + } else { + println!("{} gets a haircut!", self.name); + + self.naked = true; + } + } + } + + // 为自定义类型 `Sheep` 实现 自定义特性 `Animal` + impl Animal for Sheep { + // 实现特性上的 `new` 方法,这里的 `Self` 指的就是 `Sheep` + fn new(name: &'static str) -> Sheep { + Sheep { + name: name, + naked: false, + } + } + + fn name(&self) -> &'static str { + self.name + } + + fn noise(&self) -> &'static str { + if self.is_naked() { + "baaaaah?" + } else { + "baaaaah!" + } + } + + // 特性上的默认实现方法可以重新定义覆盖掉。 + fn talk(&self) { + // 这里只是作为演示,实际使用的时候可能会做和默认实现完全不同的操作。 + println!("{} pauses briefly... {}", self.name, self.noise()); + } + } + + // 这里必须类型因为 `Animal` 只是一个特性,需要给出明确类型编译器才能确定应该调用哪个实现来返回类型。 + let mut dolly: Sheep = Animal::new("Dolly"); + // TODO ^ 尝试移除类型声明 `Sheep` + + dolly.talk(); + dolly.shear(); + dolly.talk(); +} + +/// 编译器通过 `derive` 属性宏提供了很多基础的特性的快捷实现 +/// +/// - 对比运算符 `Eq`,`PartialEq`,`Ord`,`PartialOrd` +/// - 克隆运算符(同时复制栈和堆的数据) `Clone`,在把 `&T` 类型转换为 `T` 的时候自动调用 +/// - 复制运算符 `Copy` 类型做等号赋值的时候自动调用 +/// - 哈希运算符 `Hash` 类型取引用的时候 `&T` 的时候自动调用 +/// - 默认值运算符 `Default` 类型的默认值,在数据转移但是转以后原始位置需要防止一个默认值。 +/// - 调试运算符 `Debug` 该运算符主要用于格式化语法 `{:?}` 来使用 +/// +fn derive() { + // 该类型可以使用对比操作符作对比 + #[derive(PartialEq, PartialOrd)] + struct Centimeters(f64); + + // 该类型可以通过 `{:?}` 格式化 + #[derive(Debug)] + struct Inches(i32); + + impl Inches { + fn to_centimeters(&self) -> Centimeters { + let &Inches(inches) = self; + + Centimeters(inches as f64 * 2.54) + } + } + + // 该类型没有任何额外的实现 + struct Seconds(i32); + + let _one_second = Seconds(1); + + // 错误:`Seconds` 没有实现 `Debug` 特性不能被打印 + // println!("One second looks like: {:?}", _one_second); + // TODO ^ 移除注释查看错误 + + // 错误:`Seconds` 因为没有实现 `PartialEq` 特性,所以不能被对比 + //let _this_is_true = (_one_second == _one_second); + // TODO ^ 移除注释查看错误 + + let foot = Inches(12); + + println!("One foot equals {:?}", foot); + + let meter = Centimeters(100.0); + + let cmp = if foot.to_centimeters() < meter { + "smaller" + } else { + "bigger" + }; + + println!("One foot is {} than one meter.", cmp); +} + +/// 动态类型 +/// 编译器需要明确的知道函数的返回值是什么类型,以及类型需要的内存空间大小是多少, +/// 这样的话所有的函数都必须返回一个固定的实际类型,比如说我们有一个特性是 `Animal`, +/// 但是并不知道实现了该特性的类型是多大,所以没有办法直接使用 `Animal` 来约束函数的返回值。 +/// +/// 基于上面的问题有简单的解决方案吗?答案是有! +/// +/// 我们使用 `Box` 来替换特性的约束,因为 `Box` 是一个实际的类型有固定大小,然后使用 `Box` 来 +/// 存放实现了 `Animal` 特性的动态对象。 +/// +fn returning_traits_with_dyn() { + // 定义两个类型 + #[derive(Debug)] + struct Sheep {} + #[derive(Debug)] + struct Cow {} + + // 定义特性 + trait Animal { + fn noise(&self) -> &'static str; + } + + // 实现特性 + impl Animal for Sheep { + fn noise(&self) -> &'static str { + "baaaaah!" + } + } + + // 实现特性 + impl Animal for Cow { + fn noise(&self) -> &'static str { + "moooooo!" + } + } + + // 使用 `Box` 固定返回值需要的尺寸,然后使用 `Box` 来存放一个动态的类型 + fn random_animal(random_number: f64) -> Box { + if random_number < 0.5 { + Box::new(Sheep {}) + } else { + Box::new(Cow {}) + } + } + + let random_number = 0.234; + let animal = random_animal(random_number); + println!( + "You've randomly chosen an animal, and it says {}", + animal.noise() + ); +} + +/// 重写操作符 +/// `Rust` 提供了很多的特性可以用来重写操作符,很多操作符都可以通过参数来实现不同的逻辑, +/// 可以实现的原因其实就是每一个操作符实际上都是特定函数的语法糖, +/// 比如说 `+` 号操作符实际上就是调用了 `add` 函数,我们可以通过在指定类型上实现 `Add` 特性 +/// 实现该特性上定义的 `add` 方法来重新定义类型的加法操作。 +fn operator_overloading() { + // 标准库的操作符导出,这里包含了所有可以重写的操作符。 + use std::ops; + + struct Foo; + struct Bar; + + #[derive(Debug)] + struct FooBar; + + #[derive(Debug)] + struct BarFoo; + + // 为 `Foo` 类型实现 `+` 法操作符 + // Add接收一个泛型对象 这个泛型对象会被当做 `add` 函数的第二个参数使用 + // `type Output` 是加法操作的返回值类型 + // 这里实现的效果就是 `Foo + Bar = FooBar` + impl ops::Add for Foo { + type Output = FooBar; + + fn add(self, _rhs: Bar) -> FooBar { + println!("> Foo.add(Bar) was called"); + + FooBar + } + } + + // 上面的反向实现 `Bar + Foo = BarFoo` + impl ops::Add for Bar { + type Output = BarFoo; + + fn add(self, _rhs: Foo) -> BarFoo { + println!("> Bar.add(Foo) was called"); + + BarFoo + } + } + + println!("Foo + Bar = {:?}", Foo + Bar); + println!("Bar + Foo = {:?}", Bar + Foo); +} + +/// 声明周期结束的时候自动调用 `Drop` 特性的函数。 +/// +/// 实现了该特性的类型,在实例走出作用域被销毁之前会自动调用该特性的函数,用于做一些自定义的清理操作 +/// +fn drop_() { + struct Droppable { + name: &'static str, + } + + // `Droppable` 实例被销毁的时候会自动调用该特性的函数 + impl Drop for Droppable { + fn drop(&mut self) { + println!("> Dropping {}", self.name); + } + } + + let _a = Droppable { name: "a" }; + + // 局部块 A + { + let _b = Droppable { name: "b" }; + + // 局部块 B + { + let _c = Droppable { name: "c" }; + let _d = Droppable { name: "d" }; + + println!("Exiting block B"); + } + println!("Just exited block B"); + + println!("Exiting block A"); + } + println!("Just exited block A"); + + // 手动调用销毁函数 + drop(_a); + // TODO ^ 尝试注释这行代码 + + println!("end of the main function"); + + // `_a` 不会因为作用域销毁再次被销毁一次,因为上面已经手动的销毁了一次。 +} + +/// 迭代器(Iterator) +/// +/// 迭代器是一个非常重要的特性,该特性的函数式编程可以让代码更简单易懂, +/// 并且因为不需要额外的安全检查性能会更好! +/// +/// 迭代器一般都是使用在类似数组或者集合类的数据上,通过实现迭代器方法可以 +/// 让调用方非常轻松的对数据集合进行操作。 +/// +/// 迭代器特性只需要实现一个方法 `next` 就可以轻松的把集合数据转换成迭代器。 +/// +/// +fn iterators() { + // 定义一个 `斐波那些数列` 类 + struct Fibonacci { + curr: u32, + next: u32, + } + + // 为这个类实现迭代器特性 + impl Iterator for Fibonacci { + // 迭代器需要提供一个具体的返回值类型,这里的类型为 `u32`。 + type Item = u32; + + // 迭代器要求的返回值必须是 Option 包裹的,当返回为 None 的时候代表迭代器终止, + // 但是我们这里没有这个情况,因为斐波那些数列是一个无穷大的递增序列。 + fn next(&mut self) -> Option { + let current = self.curr; + + // 基于前一个值计算出下一个序列的值 + self.curr = self.next; + self.next = current + self.next; + + // 因为没有终止的可能所以只需要一直返回结果就可以了 + Some(current) + } + } + + // 构造一个实例 + fn fibonacci() -> Fibonacci { + Fibonacci { curr: 0, next: 1 } + } + + // 创建一个 `0..3` 区间的迭代器,生成的值为 0,1,2 + let mut sequence = 0..3; + + println!("Four consecutive `next` calls on 0..3"); + println!("> {:?}", sequence.next()); + println!("> {:?}", sequence.next()); + println!("> {:?}", sequence.next()); + println!("> {:?}", sequence.next()); + + // `for` 可以消费迭代器直到迭代器返回 `None` 循环就会终止,循环的变量 `i` 会自动从 `Some` 中解构出数据 + println!("Iterate through 0..3 using `for`"); + for i in 0..3 { + println!("> {}", i); + } + + // 迭代器上提供了 `take(n)` 方法,代表从迭代器中取多少个数据,也就是调用多少次 `next` 方法 + println!("The first four terms of the Fibonacci sequence are: "); + for i in fibonacci().take(4) { + println!("> {}", i); + } + + // `skip(n)` 方法还可以跳过前面 `n` 个数据,这里就是跳过了前四个数据,然后再获取四个数据,然后终止迭代器。 + println!("The next four terms of the Fibonacci sequence are: "); + for i in fibonacci().skip(4).take(4) { + println!("> {}", i); + } + + let array = [1u32, 3, 3, 7]; + + // 数组或者切片可以调用 `iter` 方法来创建迭代器。 + println!("Iterate the following array {:?}", &array); + for i in array.iter() { + println!("> {}", i); + } +} + +/// +/// 函数参数和返回值可以通过 `impl trait` 来约束参数或返回值必须实现某些特性,来实现类似泛型的效果。 +/// +fn impl_trait() { + // 通过泛型约束 + { + fn parse_csv_document(src: R) -> std::io::Result>> { + src.lines() + .map(|line| { + // 每一行数据 + line.map(|line| { + // 如果没有发生错误则继续处理 + line.split(',') // 分割字符串 + .map(|entry| String::from(entry.trim())) // 移除掉收尾的空字符 + .collect() // 转换成 Vec + }) + }) + .collect() // 转换成 Vec> + } + } + + // 通过特性约束 + { + fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result>> { + src.lines() + .map(|line| { + // 每一行数据 + line.map(|line| { + // 如果没有发生错误则继续处理 + line.split(',') // 分割字符串 + .map(|entry| String::from(entry.trim())) // 移除掉收尾的空字符 + .collect() // 转换成 Vec + }) + }) + .collect() // 转换成 Vec> + } + } + + // 可以看到两种方法都可以实现相同的功能,但是通过特性约束的话会失去手动指定泛型类型的特性, + // 比如第二个实现就不能通过 `parse_csv_document::(std::io::empty())` 使用。 + + { + use std::iter; + use std::vec::IntoIter; + + // 把两个 Vec 类型链接到一起,然后组成一个无限循环的迭代器。 + // 可以看到这个函数的返回值生命非常复杂,但实际上就是一个无限循环的迭代器。 + fn combine_vecs_explicit_return_type( + v: Vec, + u: Vec, + ) -> iter::Cycle, IntoIter>> { + v.into_iter().chain(u.into_iter()).cycle() + } + + // 通过 `impl Trait` 来约束可以极大的简化返回值类型的声明!!!! + fn combine_vecs(v: Vec, u: Vec) -> impl Iterator { + v.into_iter().chain(u.into_iter()).cycle() + } + + let v1 = vec![1, 2, 3]; + let v2 = vec![4, 5]; + let mut v3 = combine_vecs(v1, v2); + assert_eq!(Some(1), v3.next()); + assert_eq!(Some(2), v3.next()); + assert_eq!(Some(3), v3.next()); + assert_eq!(Some(4), v3.next()); + assert_eq!(Some(5), v3.next()); + println!("all done"); + } + + // 还有很重要的一点,有些类型不能直接写出来,比如闭包类型,所有的闭包都会生成一个匿名结构, + // 但是我们无法知道这个匿名类型是什么,所以对于闭包类型我们可以使用 `impl Fn` 来约束闭包的类型。 + { + // 这里返回了一个闭包,闭包捕获了参数 `y` + fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 { + let closure = move |x: i32| x + y; + closure + } + let plus_one = make_adder_function(1); + assert_eq!(plus_one(2), 3); + } + + // 还可以使用 `impl` 来返回一个迭代器的闭包,这样就可以把 `filter` `map` 等操作封装成一个函数。 + { + fn double_positives<'a>(numbers: &'a Vec) -> impl Iterator + 'a { + numbers.iter().filter(|x| x > &&0).map(|x| x * 2) + } + + let singles = vec![-3, -2, 2, 3]; + let doubles = double_positives(&singles); + assert_eq!(doubles.collect::>(), vec![4, 6]); + } +} + +/// 当把资源赋值给一个变量或者当做参数调用函数的时候都会进行资源的转移, +/// 或者我们需要对资源进行复制,这时候我们就需要使用 `Clone` 特性来支持这个操作了, +/// 大多数情况下我们都可以使用 `.clone()` 方法(该方法是 `Clone` 特性提供的)进行复制数据。 +/// +fn clone_() { + // 一个没有数据的结构类型 + #[derive(Debug, Clone, Copy)] + struct Unit; + + // 元组结构类型 + #[derive(Clone, Debug)] + struct Pair(Box, Box); + + // 实例化 `Unit` + let unit = Unit; + // 复制 `Unit` 因为没有数据所以没什么需要复制的 + let copied_unit = unit; + + // 两个 `Unit` 都是独立的 + println!("original: {:?}", unit); + println!("copy: {:?}", copied_unit); + + // 实例化 `Pair` + let pair = Pair(Box::new(1), Box::new(2)); + println!("original: {:?}", pair); + + // 移动 `pair` 到 `moved_pair`,这里因为 `pair` 有实际的数据,所以进行了所有权的转移。 + let moved_pair = pair; + println!("moved: {:?}", moved_pair); + + // 错误! `pair` 对数据的所有权已经转移给了 `moved_pair` + // println!("original: {:?}", pair); + // TODO ^ 移除注释查看错误 + + // 手动调用 `clone` 方法深度复制数据 + let cloned_pair = moved_pair.clone(); + // 销毁原始数据 + drop(moved_pair); + + // 错误! `moved_pair` 已经被销毁了 + //println!("copy: {:?}", moved_pair); + // TODO ^ 移除注释查看错误 + + // `cloned_pair` 还可以使用,因为复制了所有的数据 + println!("clone: {:?}", cloned_pair); +} + +fn supertraits() { + trait Person { + fn name(&self) -> String; + } + + // Person 是 Student 的前提条件 + // 当实现 Student 特性的时候也必须同时实现 Person + trait Student: Person { + fn university(&self) -> String; + } + + trait Programmer { + fn fav_language(&self) -> String; + } + + // 同上 + trait CompSciStudent: Programmer + Student { + fn git_username(&self) -> String; + } + + fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { + format!( + "My name is {} and I attend {}. My favorite language is {}. My Git username is {}", + student.name(), + student.university(), + student.fav_language(), + student.git_username() + ) + } + + struct Engineer { + name: String, + university: String, + fav_language: String, + git_username: String, + } + + impl Person for Engineer { + fn name(&self) -> String { + self.name.clone() + } + } + + impl Student for Engineer { + fn university(&self) -> String { + self.university.clone() + } + } + impl Programmer for Engineer { + fn fav_language(&self) -> String { + self.fav_language.clone() + } + } + impl CompSciStudent for Engineer { + fn git_username(&self) -> String { + self.git_username.clone() + } + } + + let engineer = Engineer { + name: String::from("name"), + university: String::from("university"), + fav_language: String::from("fav_language"), + git_username: String::from("git_username"), + }; + + comp_sci_student_greeting(&engineer); +} + +/// 因为不管什么类型都可以实现任意数量的特性,这些特性上可能存在相同的名称 +/// 这样的话会产生名称冲突,`Rust` 提供了完全限定语法来解决这个问题。 +fn disambiguating_overlapping_traits() { + trait UsernameWidget { + // 定义方法 + fn get(&self) -> String; + } + + trait AgeWidget { + // 定义同名方法 + fn get(&self) -> u8; + } + + // 基础类型 + struct Form { + username: String, + age: u8, + } + + // 实现特性 + impl UsernameWidget for Form { + fn get(&self) -> String { + self.username.clone() + } + } + + // 实现特性 + impl AgeWidget for Form { + fn get(&self) -> u8 { + self.age + } + } + + let form = Form { + username: "rustacean".to_owned(), + age: 28, + }; + + // 错误!:找到了多个同名的方法,因为 `Form` 同时实现了 `AgeWidget` 和 `UsernameWidget` 特性 + // 这两个特性都有一个方法名 `get` 这样的话编译器就没办法知道你想要调用的是哪个方法。 + // println!("{}", form.get()); + + // 可以使用完全限定语法来调用指定特性上的方法 + let username =
::get(&form); + assert_eq!("rustacean".to_owned(), username); + let age = ::get(&form); + assert_eq!(28, age); + + // 还可以使用这个语法 + UsernameWidget::get(&form); + AgeWidget::get(&form); +} + +fn main() { + // 特性基础展示 + example01(); + // 属性宏快捷实现通用特性 + derive(); + // 使用特性重载操作符 + operator_overloading(); + // 析构函数 + drop_(); + // 迭代器 + iterators(); + // 函数参数和返回值的特性约束 + impl_trait(); + // 数据的复制 + clone_(); + // 特性的继承约束 + supertraits(); + // 特性方法名冲突的解决方案 + disambiguating_overlapping_traits(); +} diff --git a/17.macro_rules/Cargo.lock b/17.macro_rules/Cargo.lock new file mode 100644 index 0000000..e7011de --- /dev/null +++ b/17.macro_rules/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "mar" +version = "0.1.0" diff --git a/17.macro_rules/Cargo.toml b/17.macro_rules/Cargo.toml new file mode 100644 index 0000000..c184d2b --- /dev/null +++ b/17.macro_rules/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/17.macro_rules/src/main.rs b/17.macro_rules/src/main.rs new file mode 100644 index 0000000..91bd031 --- /dev/null +++ b/17.macro_rules/src/main.rs @@ -0,0 +1,279 @@ +//! 宏 (macro_rules!) +//! +//! 宏是一个非常强大的元编程系统,前面已经见过了类似于函数的宏,宏使用 `!` 结尾的函数, +//! 宏不像是代码能生成实际的内容,宏更像是一个预处理器,在代码被编译之前先对代码进行加工修改, +//! 然后再进行编译,在 Rust 中宏并不是对源码的处理,而是对源码的抽象语法树(AST)进行处理, +//! 这样能避免因为优先级出现的问题。 +//! + +/// +/// 这是一个最基础的宏示例 +/// +/// 为什么宏非常有用? +/// +/// - 不需要写重复代码,可能很多情况下都会需要有大块相同但是又有细微不同的重复代码,这时候就可以用宏来优化代码结构。 +/// - DSL,自定义特定的语法场景。 +/// - 可变参数,有很多场景需要参数能根据数量动态变化,这样的好处是函数非常通用,可以让 `API` 使用起来更方便。 +/// +fn example01() { + // 使用 `macro_rules!` 关键字定义一个 `say_hello` 宏 + macro_rules! say_hello { + // `()` 表示没有参数 + () => { + // 这里的代码都会展开到调用处 + println!("Hello!") + }; + } + + // 调用自定义宏 + say_hello!(); +} + +/// 宏参数以及基础使用 +/// +/// 宏的参数提供了多种格式 +/// +/// - block +/// - expr 表达式 +/// - ident 标识符 +/// - item +/// - literal 字面量 +/// - pat match 的子语句 +/// - path +/// - stmt 语句块 +/// - tt (token tree) 操作符 +/// - ty (类型) +/// - vis (可见性限定符) +/// +/// 完整列表可以[查看这里](https://doc.rust-lang.org/reference/macros-by-example.html) +/// +fn designators() { + macro_rules! create_function { + // 宏接收一个参数,这个参数是一个标识符(ident), + // 宏展在使用处展开后会使用参数的标识符生成一个函数。 + ($func_name: ident) => { + fn $func_name() { + // 这里使用 `stringify!` 宏把一个标识符转换成字符串形式; + println!("You called {:?}", stringify!($func_name)); + } + }; + } + + macro_rules! print_result { + // 这个宏接收一个表达式为参数,当宏被展开的时候,表达式会被替换成下面的代码。 + // 这里依旧使用 `stringify!` 宏把收到的表达式转换为了字符串的字面量放到了输出的代码中。 + ($expression: expr) => { + println!("{:?} == {:?}", stringify!($expression), $expression); + }; + } + + // 通过宏定义了两个函数 + create_function!(foo); + create_function!(bar); + + // 调用宏生成的函数 + foo(); + bar(); + + // 调用宏,把表达式转换成宏生成的代码。 + print_result!(1u32 + 1); + + print_result!({ + let x = 1u32; + + x * x + 2 * x - 1 + }); +} + +/// 宏的重载 +fn overload() { + // 这个宏可以接收两种不同的参数格式 + macro_rules! test { + // 第一种宏格式 `test!(a; and b)` + ($left: expr; and $right: expr) => { + println!( + "{:?} and {:?} is {:?}", + stringify!($left), + stringify!($right), + $left && $right + ) + }; + // 第二种宏格式 `test!(a; or b)` + ($left: expr; or $right: expr) => { + println!( + "{:?} or {:?} is {:?}", + stringify!($left), + stringify!($right), + $left || $right + ) + }; + } + + test!(1i32 + 1== 2i32; and 2i32 * 2 == 4i32); + test!(true; or false); +} + +fn repeat() { + // 这个宏会递归展开参数直到全部展开 + macro_rules! find_min { + // 当参数只有一个的时候那就是最小值 + ($x: expr) => { + $x + }; + // 当参数大于一个的时候需要递归展开 + // 这里相当于 `$x` 是第一个参数,`$($y: expr),+` 是后面的所有参数。 + // 实际上这里的 `$(...),+` 类似于正则表达式 `$(...),` 是要尝试重复匹配的模式 `+` 号代表 1到无数次。 + ($x: expr, $($y: expr),+) => { + // 然后这里使用的时候类似于 `$($y),+` 是剩余的列表不断地递归展开剩余的参数。 + std::cmp::min($x, find_min!($($y),+)) + }; + } + + // 这里 `find_min!(1)` = `1` + println!("{}", find_min!(1)); + // 这里 `find_min!(1 + 2, 2)` = `std::cmp::min(1 + 2, 2)` + println!("{}", find_min!(1 + 2, 2)); + // 这里 `find_min!(5, 2 * 3, 4)` = `std::cmp::min(5, std::cmp::min(2 * 3, 4))` + println!("{}", find_min!(5, 2 * 3, 4)); +} + +/// 使用自定义宏来生成重复的代码, +/// 还可以使用宏定义还生成重复的测试用例 +fn dont_repeat_yourself() { + pub mod macro_test { + use std::ops::{Add, Mul, Sub}; + + macro_rules! assert_equal_len { + // 使用 `$op:tt` 类型来捕获操作符 + ($a:expr, $b:expr, $func:ident, $op:tt) => { + assert!( + $a.len() == $b.len(), + "{:?}: dimension mismatch: {:?} {:?} {:?}", + stringify!($func), + ($a.len(),), + stringify!($op), + ($b.len(),) + ); + }; + } + + macro_rules! op { + ($func:ident, $bound:ident, $op:tt, $method:ident) => { + // 定义宏展开的函数模版 + fn $func + Copy>(xs: &mut Vec, ys: &Vec) { + assert_equal_len!(xs, ys, $func, $op); + + for (x, y) in xs.iter_mut().zip(ys.iter()) { + *x = $bound::$method(*x, *y); + } + } + }; + } + + // 使用宏快速定义三个方法 + op!(add_assign, Add, +=, add); + op!(mul_assign, Mul, *=, mul); + op!(sub_assign, Sub, -=, sub); + + pub mod tests { + use std::iter; + macro_rules! test { + ($func:ident, $x:expr, $y:expr, $z:expr) => { + fn $func() { + for size in 0usize..10 { + let mut x: Vec<_> = iter::repeat($x).take(size).collect(); + let y: Vec<_> = iter::repeat($y).take(size).collect(); + let z: Vec<_> = iter::repeat($z).take(size).collect(); + + super::$func(&mut x, &y); + + assert_eq!(x, z); + } + } + }; + } + + // 使用宏来生成三种测试用例函数。 + test!(add_assign, 1u32, 2u32, 3u32); + test!(mul_assign, 2u32, 3u32, 6u32); + test!(sub_assign, 3u32, 2u32, 1u32); + + pub fn start_tests() { + // 这里调用的是当前的模块内宏展开后的函数 + add_assign(); + mul_assign(); + sub_assign(); + } + } + } + + // 调用测试用例 + macro_test::tests::start_tests(); +} + +/// 自定义语法 (DSL) +/// +/// `Rust` 中可以使用宏来实现自定义语法的扩展, +/// 因为宏会先进行展开变成合法的 `Rust` 代码 +/// +fn domain_specific_languages() { + macro_rules! calculate { + // 这里使用双花括号来定义宏展开后的内容,因为宏是使用 `calculate! { eval 1 + 2 }` 来使用的。 + // 自定义了一个关键字叫 `eval` 这个关键字后面接收一个语句。 + (eval $e:expr) => { + { + let val: usize = $e; // 强制转换捕获到的表达式结果为数字 + println!("{} = {}", stringify!{$e}, val); + } + }; + } + + calculate! { + eval 1 + 2 // `eval` 是自己定义的关键字 + } + + calculate! { + eval (1 + 2) * (3 / 4) + } +} + +/// 多态,可变参数宏 +fn variadic_interfaces() { + macro_rules! calculate { + // 一个参数的情况直接进行展开。 + (eval $e: expr) => {{ + let val: usize = $e; + println!("{} = {}", stringify!($e), val); + }}; + // 不定参数情况下动态展开 + (eval $e: expr, $(eval $r: expr),+) => {{ + // 独立的一个参数进行直接展开 + calculate! { eval $e } + // 剩余的参数继续递归展开 + calculate! { $(eval $r),+ } + }} + } + + calculate! { // 可变多态宏 `calculate!`! + eval 1 + 2, + eval 3 + 4, + eval (2 * 3) + 1 + } +} + +fn main() { + // 宏的基本使用 + example01(); + // 宏的参数 + designators(); + // 宏的多态 + overload(); + // 可变参数宏 + repeat(); + // 定义宏函数模版 + dont_repeat_yourself(); + // 自定义语法 DSL + domain_specific_languages(); + // 可变多态宏 + variadic_interfaces(); +}