《Rust权威指南》学习笔记(二)

枚举enum

1.枚举的定义和使用如下图所示:

定义时还可以给枚举的成员指定数据类型,例如:enum IpAddr{V4(u8, u8, u8, u8),V6(String),}。枚举的变体都位于标识符的命名空间下,使用::进行分隔。

2.一个特殊的枚举Option(在预导入模块prelude中定义),它描述了某个值可能存在(某种类型)或者不存在的情况,Rust中没有Null。

3.控制流运算符match:允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码,模式可以是字面值、变量名、通配符等等。match表达式类似于一个函数,每个分支都会有一个返回值,并且整个match表达式的返回值类型必须一致。如下图:

Match匹配必须穷举所有的可能,否则会报错,可以用通配符_(这里可以用任何一个合法标识符来捕获剩下的匹配值)替代其余没有列出的值,_需要写在最后面,也可以使用任何合法的变量名捕获其他所有情况,后续可以在分支内部使用该变量进行进一步的处理。

4.if let的用法:处理只关心一种匹配而忽略其他匹配的情况,下图中v 恰好等于Some(3) 时,条件会成立,第一个分支就会被执行。if let还可以用于简化模式匹配,用于匹配并解构Option,、Result等枚举类型的某个特定变体,并在匹配成功时执行相应代码。

Rust代码组织

1.Package:是一个Rust项目的最外层单位,它由一个或多个crate组成(至少有一个)。它通常包含一个Cargo.toml文件,用于描述包的元数据、依赖项以及构建信息。每个package至少包含一个项目,这个项目可以是一个可执行项目(由main.rs文件定义)或者一个库项目(由lib.rs文件定义)。包和项目的关系是,一个package可以包含多个可执行项目(可放在src/bin目录下),但最多只能包含一个库项目。使用cargo new project创建一个新工程时,生成的就是一个Rust包(package)。想创建一个库包而不是可执行包,可以使用--lib选项。使用cargo new my_library --lib命令时,Cargo会在src/目录下创建一个lib.rs文件,而不是main.rs文件。这个lib.rs文件是库项目的入口文件,定义了库的公共API。

2.Crate:是Rust代码的编译单元,所有的Rust代码都是在crate的上下文中进行编译的。一个crate可以是一个库,也可以是一个可执行文件。每个crate都有一个根模块,根模块对应着crate的入口文件------对于库crate,入口文件是lib.rs,对于二进制crate,默认入口文件是main.rs(这个miain.rs编译生成的可执行文件名称与package名相同)。Crate定义了一个独立的命名空间,并且可以导入其他crate来使用它们的功能。由于main.rs是二进制crate的入口点,它的内容通常不会被库crate直接引用。因此,lib.rs中不能直接使用main.rs中定义的函数或结构体。相反,库crate应该定义功能,并将这些功能公开给其他模块或crate使用,而main.rs可以引用这些功能。

3.Module:是Rust中用于组织代码的机制,它允许将代码划分为多个部分,每个部分可以在其自己的命名空间中定义。模块帮助管理代码的可读性和可维护性,一个crate的根模块可以包含其他模块,而这些模块又可以嵌套定义子模块。Rust中的模块可以通过文件系统组织,例如,一个模块可以定义在与它同名的文件中,或者作为父模块文件中的嵌套模块定义,模块之间的关系通常通过use关键字和路径来引用。

4.Path:是Rust中用于引用项(例如函数、结构体、枚举、模块等)的方式,它定义了如何从一个命名空间访问另一个命名空间的内容。路径可以是绝对路径,从crate的根模块开始(可以使用crate名或者字面值"crate");也可以是相对路径,从当前模块开始(使用self、super或当前模块的标识符,super用来访问父级模块路径中的内容,类似于文件系统中的..),路径的标识符之间用::隔开。路径使得你能够在模块层次结构中导航,访问不同模块和它们的内容。Rust的路径系统允许你清晰、简洁地访问代码片段,即使它们位于不同的模块或crate中。

5.私有边界:模块不仅可以组织代码,还可以定义私有边界,Rust中所有的条目(函数、方法、struct、enum、模块、常量等)默认都是私有的,父模块无法访问子模块中的私有条目,子模块可以使用所有祖先模块中的条目。可以使用pub来将这些条目声明为公有的,如下图,没有声明为公有的私有条目无法被访问:

6.use关键字:可以使用use关键字将路径(可以使用相对路径或者绝对路径)导作用域内(仍然遵循私有性规则),通常习惯将函数的父级模块引入作用域,以此来区分该函数是不是在其他模块引入的,而习惯将除函数外的其他元素如struct、enum等的整个完整路径引入作用域(如果有同名struct可以引用到父级以此来区分)。可以用as关键字来为引入的路径指定本地的别名,如:use std::fmt::Result as Re。

使用use将路径导入到作用域内后,该名称在此作用域中是私有的,可以在前面加上pub,则被导入的条目就可以被其他外部代码引入到他们的作用域中。如:pub use std::fmt::Result。

7.外部包的使用:需要现在Cargo.toml文件中添加依赖,然后用use将需要的特定条目引入到作用域内。标准库(std)也被当作外部包,不用在Cargo.toml文件中添加std,但需要用use将需要使用的std中的条目引入到当前作用域。当在Rust项目中使用cargo build命令编译程序时,Cargo(Rust的包管理器和构建系统)会将所有依赖的外部包下载并解压到target/debug/deps或target/release/deps文件夹中,具体位置取决于你是进行调试编译(debug)还是发布编译(release)。对于同一个包中的不同条目,可以使用嵌套路径的方式导入:路径相同的部分::{路径差异的部分}(路径差异部分用逗号分隔),如:use std::io::{cmp::Ordering,io};,如果两个use路径之一是另一个的子路径,可使用self,如:use std::io::{self,Write};。使用通配符*可以把路径中所有的公共条目都引入到作用域,如:use std::collections::*。

8.模块定义时,如果模块名后边是;,而不是代码块,Rust会从与模块同名的文件中加载内容,模块树的结构不会变化,如下图:

常见集合

1.Vector:由标准库提供,Vec<T>可以存储多个值,但只能存储相同类型的值,值在内存中连续存放。Vector初始化一般有两种方式,用new函数会初始化一个空的vector,还可以用宏定义vec!初始化,如下图所示:

向vector添加元素可以用push方法,例如v.push(1),添加的元素会被放在vector的末尾。当vector离开其作用域时,会像其他struct一样被清理掉,他里面所有的元素也会被清理掉。可以使用索引或者get方法来访问vector中的元素,get方法返回的是Option<T>枚举类型,所以当用get方法获取元素传入的索引越界时,返回的是None,而直接通过索引访问在越界时会引起panic。

所有权和借用规则在vector中同样适用,例如不能在同一作用域中同时拥有可变和不可变引用。可以用for循环来遍历vector中元素的值,如下图(println! 宏可以直接接受引用,所以不需要解引用i,就可以打印出其指向的值):

vector只能存储同一类型的数据,可以和enum配合使用来存储不同类型的值(将enum作为vector的元素),如下图:

2.字符串:字符串本质是Byte的集合,主要包括字符串字面值(&str)和String(由标准库提供,采用UTF-8编码)两种类型,Rust标准库还提供了其他字符串类型:OsString、OsStr、CString、CStr等。创建一个新的字符串可以使用new函数(如let mut s = String::new();),to_string()方法(可用于实现了Displaying trait的类型,包括字符串字面值)或者String::from()函数,如下图:

更新字符串的方式有:push_str()方法可以把一个字符串切片附加到String,如s.push_str("abc");表示将abc附加到s后面;push()方法可以把单个字符附加到String,如s.push('a');;可以用+连接字符串,但只能把&str类型添加到String,即左侧操作数必须是String类型(左侧String的所有权会被消耗),右侧操作数必须是&str;format!可用来连接多个字符串(这种方式不会修改参数的所有权)。

String不支持索引的访问方式,可以将String看作是字节、标量或者字形,对于标量值可以用chars()方法来遍历,对于字节可以用bytes()方法来遍历。

可以使用[ ]和一个范围来创建字符串的切片,但如果切片跨越了字符边界就会报错(字符是以UTF-8编码的,有些字符占2-4个字节,切片的开始或结束位置不能在这样的2-4字节之间)。

3.HashMap<K,V>:以键值对的形式来存储数据,一个键对应一个值,Hash函数用来决定如何在内存中存放K和V。可以用new函数创建一个新的空HashMap(如let mut map = HashMap::new();),用insert方法向其中添加键值对(如map.insert("key1", "value1");)。在同一个HashMap中,所有的K必须是同一个类型,所有的V必须是同一个类型。还可以基于collect方法在元素类型为Tuple(要求Tuple有两个值,一个作为K,另一个作为V)的Vector上创建新的HahsMap,如下图:

在HashMap中,对于实现了Copy trait的类型(如i32),值会被复制到HashMap中,对于拥有所有权的值(如String),所有权会转移给HashMap,如果将值的引用插入到HashMap中,值的所有权不会转移,但在HashMap有效期间,被引用的值必须保持有效。可以用get方法传入参数K访问HashMap中的V,返回值为Option<T>枚举类型。可以用for遍历HashMap中的键值对,如下图:

更新HashMap有以下几种情况:1.K已经存在:可以选择替换现有的V、保留现有的V忽略新的V、合并现有的V和新的V;2.K不存在:添加一对KV。如果向HashMap插入一对KV,然后再插入同样的K不同的V则原来的V会被替换。可以先用entry方法检查指定的K是都存在(该方法返回enum Entry,代表值是否存在),然后使用Entry的or_insert()方法(若K存在,返回对应值的可变引用;若K不存在,该方法将参数作为K的新值插入HashMap,而后返回这个值的可变引用),如下图所示:

错误处理

1.Rust中将错误分为可恢复错误(例如文件未找到等,可再次尝试)和不可恢复错误(bug,例如索引访问越界),可恢复错误可返回Result<T,E>枚举类型,不可恢复错误和使用panic!宏来报错,这个宏的默认处理方式为展开(unwind)、清理调用栈,即从产生错误的地方开始,逆向遍历调用栈,逐层清理每一层函数调用所分配的资源。这个过程确保了所有已经获取的资源(例如内存、文件句柄、锁等)能够被正确地释放,避免资源泄漏。在展开过程中,Rust 会自动调用每个作用域中的析构函数(也称为Drop实现)释放资源或执行其他清理操作。然后退出程序。这样的默认操作比较费时,可以将其重新设置为panic!时直接中止调用栈,而不进行任何清理操作,内存清理交由操作系统去完成,具体设置可修改Cargon.toml文件,如下图:

panic!可能发生在我们自己写的程序中,也可能发生在我们程序所依赖的代码中,可以通过设置RUST_BACKTRACE环境变量回溯错误具体信息,如在运行时设置:cargo run RUST_BACKTRACE=1,但必须保证编译时没有加--realease选项。

2.Result枚举类型原始定义如下:

和Option一样,Result也是由prelude带入作用域,Result枚举类型可以作为函数返回值或match匹配结果,如下图:

3.unwrap()方法可用于从Option或Result类型中提取值。如果调用unwrap()时包含的值是Some或Ok,它将返回内部的值;如果是None或Err,它将触发恐慌(panic),程序会终止执行。expect()方法也用于从Option或Result类型中提取值,但与unwrap() 不同的是,它允许自定义panic时的错误消息。这样,当unwrap() 触发panic时,可以得到更明确的错误信息帮助调试。

4.错误处理的一种更加简洁和快捷的方式是使用?运算符,?运算符可以用于处理返回Result或Option类型的函数或表达式,如果Result是Ok,Ok中的值就是表达式的结果,然后继续执行程序;如果Result是Err,Err就是整个函数的返回值,相当于使用了return。如下图所示,从上到下代码逐渐简洁:

Trait std::convert::From上的from函数可以用于错误类型之间的转换,被?所应用的错误,会隐式地被from函数处理,当?调用from函数时,它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型,只要每个错误类型实现了转换为所返回的错误类型的from函数。

相关推荐
Pandaconda4 分钟前
【Golang 面试题】每日 3 题(二十一)
开发语言·笔记·后端·面试·职场和发展·golang·go
Zhichao_9733 分钟前
【UE5 C++课程系列笔记】21——弱指针的简单使用
笔记·ue5
PyAIGCMaster1 小时前
docker学习记录:本地部署mongodb
学习·mongodb·docker
架构文摘JGWZ2 小时前
一键完成!!网页打包成桌面应用
开发语言·学习·开源软件·工具
m0_748239632 小时前
从零开始的vscode配置及安装rust教程
ide·vscode·rust
快乐非自愿2 小时前
一文解秘Rust如何与Java互操作
java·开发语言·rust
Naiva2 小时前
ESP32-C3 入门笔记08:多帧数据解析
笔记·notepad++
SomeB1oody2 小时前
【Rust自学】10.8. 生命周期 Pt.4:方法定义中的生命周期标注与静态生命周期
开发语言·后端·rust
许野平2 小时前
Rust:运行调用 Lua 脚本
rust·lua·rlua
Source.Liu2 小时前
【学Rust开发CAD】1 环境搭建
开发语言·rust