Rust_Learning#
记录我的 Rust 学习
Cargo 使用#
cargo new
cargo build
cargo run
cargo check
cargo doc --open #构建所有依赖项提供的文档并在浏览器中打开
参考链接#
Rust 特点#
- 模式和匹配构造
- 强类型、静态类型系统
- 类型推断
- 影子
- Rust 使用 “恐慌” 一词来描述程序因错误退出的情况
- 在函数定义中要求类型注解
- Rust 的目标是将程序编译为高效的二进制文件,尽可能少地需要运行时检查
- Rust 的一个基础目标是确保你的程序永远不会有未定义行为
第三章 常见编程概念#
- 常量与变量
- 常量不仅默认不可变;它们始终不可变。
- 常量可以在任何作用域中声明
- 常量只能设置为常量表达式,而不能是只能在运行时计算的值的结果。
- 影子结束的条件
- 它本身被影子覆盖
- 作用域结束
- 整数除法向零截断到最近的整数
- tuple 和 array 的区别
- tuple 的各类型可以不同;
- array 的大小固定,在定义时就指定了
- tuple 更灵活,可以存储不同类型,通常用于临时组合数据
- array 大小固定,用于存储大量相同类型的数据
- 表达式和语句
- 表达式不包括结束分号
- 语句不返回值
- 函数的返回值与函数体中最后一个表达式的值同义
- 控制流
- 这意味着每个 if 的每个分支的潜在结果值必须是相同类型
- Rust 没有 “真值” 或 “假值” 的概念。因此if 表达式的条件必须是布尔值
第四章 理解所有权#
- 栈保存与特定函数相关的数据,而堆保存可以超出函数生命周期的数据
- Rust 不允许程序手动释放内存。该政策避免了上述未定义行为的发生。
- Box 释放原则:如果一个变量拥有一个 box,当 Rust 释放该变量的栈帧时,Rust 也会释放该 box 的堆内存。
- 移动堆数据原则:如果变量 x 将堆数据的所有权转移给另一个变量 y,则 x 在移动后不能再使用。
- 引用是一种指针。
- Rust 在某些情况下隐式插入解引用和引用,例如使用点运算符调用方法
- 指针安全原则:数据不应同时被别名和修改。
- 权限是在路径上定义的,而不仅仅是变量。路径是你可以放在赋值左侧的任何东西。
- 创建对数据的引用(“借用” 它)会使该数据在引用不再使用之前暂时变为只读。
- Rust 的借用检查器没有为 a [0]、a [1] 等定义不同的路径。它使用单一路径 a [_] 表示 a 的所有索引。
- 切片是一种特殊类型的引用,因为它们是 “胖” 指针,或带有元数据的指针。在这里,元数据是切片的长度。
总结#
- 优势
- 通过避免垃圾回收来提高运行时性能
- 通过防止意外 “泄漏” 数据来提高可预测性。
- 指针可以通过
- boxes(拥有堆上数据的指针)
- references(非拥有指针)创建。
- 移动与借用
- 移动一个不可复制类型的变量(如 Box或 String)需要 RO 权限,移动会消除该变量的所有权限。该规则防止使用已移动的变量:
- 借用一个变量(创建对它的引用)会暂时移除该变量的一些权限
- 不可变借用创建一个不可变引用,并且禁用借用的数据被修改或移动。
- 可变借用创建一个可变引用,禁用借用的数据被读取、写入或移动
- 使用后释放:不可变借用移除写权限以避免使用后释放,
- 双重释放:对不可复制数据的引用的解引用没有拥有权限以避免双重释放
第五章 结构体#
- Rust没有构造函数的关键字。定义构造函数的惯用方法是创建一个名为 new 的关联函数,但这并不是语言强制的。
- tuple 结构。例如
struct Color (i32,i32,i32);
- Rust 会插入尽可能多的引用和解引用,以使 self 参数的类型匹配
- Rust 不会自动推导 Copy 以在 API 更改中保持稳定。
#[derive(Copy, Clone)]
- 当你看到错误 “无法从 * self 移动”,这通常是因为你试图在像 & self 或 & mut self 这样的引用上调用 self 方法。Rust 在保护你免受双重释放
第六章 枚举#
-
使用枚举而不是结构体的优势:
- 每个变体可以具有不同类型和数量的关联数据
- 我们定义的每个枚举变体的名称也成为构造枚举实例的函数
- 你可以在枚举变体中放入任何类型的数据:字符串、数字类型或结构体。例如,你甚至可以包含另一个枚举
-
Option 枚举
- 编译器可以检查你是否处理了所有应该处理的情况
- null 是当前无效或因某种原因缺失的值。
- Rust 没有 null,但它有一个枚举可以编码值存在或缺失的概念。
- 函数 Option::unwrap 期望自我,这意味着它期望拥有arg。然而 arg 是对一个选项的不可变引用,因此无法提供该选项的所有权。
-
匹配
-
每个匹配从上到下尝试
-
opt 是一个普通枚举 —— 它的类型是 Option,而不是像 & Option那样的引用。因此对 opt 的匹配将移动未被忽略的字段,如 s。
-
如果我们想在不移动其内容的情况下查看 opt,惯用的解决方案是对引用进行匹配:
-
if let
- if let 作为语法糖,用于在值匹配一个模式时运行代码,然后忽略所有其他值。
- 与 else 相关的代码块与与 if let 等效的匹配表达式中的_情况的代码块相同
第七章 使用包、crate 和模块管理增长的项目#
包:Cargo 的一个特性,让你构建、测试和共享 crate#
- 一组提供功能的一个或多个 crate 的捆绑。
- 一个包可以包含任意数量的二进制 crate,但最多只能有一个库 crate。
- 使用外部包
- 标准 std 库也是一个外部 crate。我们不需要更改 Cargo.toml 以包含 std。但我们确实需要通过 use 引用它以将其中的项目引入我们包的作用域
Crates:生成库或可执行文件的模块树#
- 二进制 crate:必须有一个名为
main
的函数 - 库 crate:定义旨在与多个项目共享的功能。
Rustaceans 说 “crate”,他们指的是库 crate,并且他们将 “crate” 与 *“库”* 的通用编程概念交替使用。
模块和使用:让你控制路径的组织、作用域和隐私#
- 用处
- 让我们在 crate 内组织代码以提高可读性和易于重用
- 允许我们控制项目的隐私,因为模块内的代码默认是私有的
- 父模块和子模块
- 所有项目(函数、方法、结构体、枚举、模块和常量)默认对父模块私有。
- 父模块中的项目不能使用子模块内的私有项目,但子模块中的项目可以使用其祖先模块中的项目。
路径:命名项目(如结构体、函数或模块)的一种方式#
- 惯用方式
- 使用 use 将函数的父模块引入作用域
- 当使用 use 引入结构体、枚举和其他项目时,惯用做法是指定完整路径
use std::io::Result as IoResult;
- 我们可以使用嵌套路径在一行中将相同的项目引入作用域。
use std::{cmp::Ordering, io};
,use std::io::{self, Write};
第八章#
向量#
- 对第一个元素的引用关心向量末尾的变化的原因
- 在向量末尾添加新元素可能需要分配新内存并将旧元素复制到新空间
- 对第一个元素的引用将指向已释放的内存
- Vec::push 移动其参数,因此在调用 v.push (s) 后 s 不可用
- 当向量被丢弃时,它的所有内容也会被丢弃,这意味着它所持有的整数将被清理。
字符串#
- 编译器可以将 & String 参数强制转换为 & str
- Rust 字符串不支持索引以避免返回意外值并导致可能无法立即发现的错误
- 从 Rust 的角度看字符串的三种相关方式
- 字节
- 标量值
- 字形簇
- 操作字符串片段的最佳方法是明确你想要字符还是字节
- &str 是一个承诺,指向的字节序列将始终是有效的 UTF-8
哈希图#
- 哈希图在你想要通过键查找数据时非常有用,而不是像使用向量那样使用索引,键可以是任何类型
- 对于实现 Copy 特性的类型,如 i32,值会被复制到哈希图中。对于拥有的值如 String,值将被移动,哈希图将拥有这些值
第十章#
泛型数据类型#
- Rust 要求你提前声明泛型类型的预期能力
- 没有限制,泛型类型 T 没有能力:它不能被打印、克隆或修改(尽管它可以被丢弃)。
- Rust 没有类似于面向对象语言的机制来专门化方法,
- 单态化是将泛型代码转换为特定代码的过程,通过填充编译时使用的具体类型
- const 泛型:
const N : usize
特征#
- 特征定义特定类型具有的功能,并可以与其他类型共享。
- 一项需要注意的限制是,我们只能在类型本地的情况下为类型实现特征。
- 默认实现可以调用同一特征中的其他方法,即使这些其他方法没有默认实现
- 参数中的特征
fn some_function<T:Display + Clone ,U: Clone + Debug>(t:&T,u:&U) -> i32{} // 使用where子句清晰的特征边界 fn some_function<T,U>(t:&T,u:&U) -> i32 where T:Display + Clone , U: Clone + Debug {}
- 只能在返回单一类型时使用 impl Trait
- 使用特征边界有条件地实现方法
use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("最大的成员是 x = {}", self.x); } else { println!("最大的成员是 y = {}", self.y); } } }
- 有条件地为实现另一个特征的任何类型实现特征
impl<T: Display> ToString for T { // --省略-- }
生命周期#
- 生命周期注解不会改变任何引用的生存时间。相反,它们描述多个引用之间的生命周期关系,而不影响生命周期
- 生命周期参数的名称必须以撇号(')开头,通常都是小写且非常简短
- 当我们在这个函数签名中指定生命周期参数时,我们并没有改变传入或返回的任何值的生命周期。相反,我们指定借用检查器应拒绝任何不符合这些约束的值。
- 在 Rust 的早期版本(1.0 之前),每个引用都需要显式的生命周期
- 编译器使用三条规则来确定引用的生命周期,当没有显式注解时。
- 编译器为每个输入类型中的每个生命周期分配不同的生命周期参数。
- 如果恰好有一个输入生命周期参数,则该生命周期分配给所有输出生命周期参数
- 如果有多个输入生命周期参数,但其中一个是
&self
或&mut self
因为这是一个方法,则 self 的生命周期分配给所有输出生命周期参数。
- 静态生命周期
'static
- 字符串字面量直接存储在程序的二进制文件中,始终可用。因此,所有字符串字面量的生命周期是
'static
。 - 'static 意味着 “在整个程序中存活”,因此静态引用下的数据绝不能被释放。
- 字符串字面量直接存储在程序的二进制文件中,始终可用。因此,所有字符串字面量的生命周期是
- 生命周期标注并不会改变任何引用的实际作用域
第十一章#
自动生成测试的输出#
- 0 测量统计数据用于基准测试,测量性能。
Doc-tests
用于任何文档测试的结果
常用命令#
assert!();
assert_eq!();
assert_ne! // 如果我们给它的两个值不相等将通过,如果它们相等则失败。
并行或连续运行测试#
- 当你运行多个测试时,默认情况下它们在使用线程并行运行
cargo test -- --test-threads=1
cargo test -- --show-output
cargo test --help
cargo test -- --help
-
#[test] #[ignore] cargo test -- --ignored cargo test -- --include-ignored
- 我们可以指定测试名称的一部分,任何名称与该值匹配的测试将被运行。
测试组织#
- 单元测试分别测试库的不同部分,并可以测试私有实现细节。
- 单元测试分别测试库的不同部分,并可以测试私有实现细节。
第十二章#
- 在所需函数嵌套在多个模块中的情况下,我们选择将父模块引入作用域而不是函数。
- TDD(测试驱动开发)
- 编写一个失败的测试并运行它,以确保它因你预期的原因而失败。
- 编写或修改足够的代码以使新测试通过。
- 重构你刚刚添加或更改的代码,并确保测试继续通过。
- 从第 1 步重复!
第十三章#
- Rust 将推断闭包的参数 / 返回类型,但不推断顶级函数
- 闭包可以通过三种方式捕获其环境中的值
- 不可变借用
- 可变借用
- 获取所有权。
- Fn 特征
- FnOnce
- FnMut
- Fn
- 迭代器适配器是惰性的,我们需要在这里消耗迭代器。