RUST
Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)
Packages
Cargo的一个功能,可以构建、测试和分享crate。包是提供一系列功能的一个或多个crate。一个包会包含一个Cargo.toml文件。Cargo本身也是一个包含构建代码的二进制项目的包。包中可以包含至多一个库crate,和任意多个二进制crate,但是必须至少有一个crate。
一个包目录中
src/main.rs是与包名相同的二进制crate的根cratesrc/lib.rs是与包名相同的库crate的根cratesrc/bin目录下是这个包中的其他的二进制crate
crate根文件由Cargo传递给rustc来实际构建库或二进制项目。
rust版本
edition
为了处理rust大版本更新后兼容,在[package]中会说明类似edition = "2021"版本信息,告诉编译器这个包是对哪个版本兼容的。因此如果项目要使用rust的新特性,需要使用特性对应的版本。基本上3年一个版本,目前最新的为2024.
详细的版本信息和指南在这里The Rust Edition Guide
使用cargo fix可以辅助版本升级。
例如:
- 2015版本兼容rust1.0版本
- 2018版本把async和await作为关键字,所以程序中不能在使用这两个作为变量名
rust version
可以为工程指名使用的rust的最低版本,例如在[package]下添加rust-version = "1.91.0"。如果当前本机安装的rust版本小于指定的版本号,会提示无法编译
1 | error: rustc 1.88.0 is not supported by the following package: |
如果要求版本号小于本地安装的rust版本,cargo会用当前安装的版本编译,不会精确匹配编译器的版本。
- 使用指定的rust版本编译
cargo +1.91.0 build就会用rustup去自动下载1.91.0版本,不过配置的aliyun镜像目录不正确,会下载失败。 - 使用配置文件指定编译版本,在项目根目录下新建
rust-toolchain.toml,文件中指定rust的版本,下次在cargo build时,就会用指定的版本编译1
2
3[toolchain]
channel = "1.91.0" # 也可以写 channel = "stable"
components = [ "clippy" ]
Crates
crate是rust在编译时的最小代码单位,可以是一个文件。Crate有两类:库或二进制项目。一般crate都是指的库。
依赖
Cargo.toml中[dependencies]段是当前项目的依赖,cargo在编译时会依次下载依赖库的源代码,并进行编译。如果一个库又依赖其他库,也会先下载被依赖的库,进行编译,从而把整个依赖树下载编译。例如randcrate依赖rand_core v0.9.3就会下载rand_core v0.9.3并进行编译,而不只是下载当前项目直接依赖的crate。
cargo会传递--extern选项,告诉rustc在编译时使用的crate,所以当rustc看到代码中的use rand::Rng;就直到rand是一个crate,并且也知道去哪里找到这个库文件。
通过cargo build --verbose可以查看详细的编译信息--extern 'rand=E:\dev\rust\memorywork\target\debug\deps\librand-f6713db433808e1e.rmeta'
项目编译
lib项目
cargo使用--crate-type lib选项,这样rustc不会去代码中找main函数,同时会生成.rlib文件,这时rust的库文件,可以被其他rust程序静态链接使用。
.rlib文件中存储了库的类型信息,因此rustc就可以知道程序中使用的crate的features是否在这个crate中。
可执行程序
cargo使用--crate-type bin选项,生成一个二进制程序。
cargo build --release选项会优化代码,程序执行的更快,但是编译所需的时间更长,不会检查整数溢出,并会跳过debug_asser!()断言,生成的调用栈追溯也更不可靠。
Modules
多个模块构成了一个crate,module用来对一个crate中的代码进行分组,提高可读性和重复使用。模块使用mod声明,和python的module类似,也可以看作和c++中的namespace类似。
模块以树结构进行组织,一个模块中的代码默认是私有的,子模块可以访问父模块的成员,但父模块默认不能访问子模块的成员,除非在子模块中将成员声明为pub的。同一级的模块之间是可以访问的。
使用super可以访问父一级的内容(方法,结构体,枚举等)。
如果一个模块声明了pub,他的内容对外部来说,还是私有不能访问的,要访问一个模块的内容,必须给具体的内容,例如函数,结构体加上pub。
结构体内的字段默认都是私有,而枚举中的字段都是公开的,不需要给枚举的每个值都增加pub。
src/lib.rs文件中
1 | fn deliver_order() {} |
use
可以使用use简化模块使用时很长的前缀,和c++的using或python的import类似的作用。use的短路径只能在use所在的特定作用域内使用,如果和use的作用域不同,就不能使用。
1 | use crate::front_of_house::hosting; |
use其实也可以直接指定到最后的接口,但是那样以来,使用的地方直接调用接口名字,可能存在不同模块内用相同接口名的情况。所以,一般只是把use指定到模块,类,结构体或枚举。类似python的import,use也有as的语法别名,这样也可以避免冲突。
1 | use std::fmt::Result; |
使用pub use可以把一个名称重导出,相同于这个名字就定义在当前作用域一样。
1 | pub use crate::front_of_house::hosting; |
use语句可以把多个语句合并简化
1 | use std::{cmp::Ordering, mem}; |
模块文件管理
模块文件可以有三种组织方式:
- 模块使用单独的文件存放,文件名就是模块的名称
不同的模块可以按文件放在其父模块的目录中,编译器根据mod语句定位模块的代码文件的位置。
1 | └── src |
编译器看到了根文件中的square模块声明,就会在根目录中找这个src/square.rs文件。
- 当需要把多个子模块放在一起时,可以使用目录名来创建一个模块,目录中使用
mod.rs来声明这个模块的子模块
例如有一个模块名称为shape标识形状,它有2个子模块circle和square
1 | └── src |
- 使用文件名和目录名相同来创建一个模块,rust的官方指南推荐使用这种方法,如果用方法2,每个目录中都有mod.rs在编辑器中打开多个不容易区分。
例如在src/front_of_house.rs中声明了一个子模块hosting,
1 | └── src |
square是shape的子模块,所以它的模块文件square.rs放在他父模块shape同名的目录下src/shape/square.rs
IO控制台项目
- 将程序拆成main.rs和lib.rs,程序的逻辑放入lib.rs中
- main中调用lib的run函数
main.rs中
1 | use std::env; |
lib.rs中
1 | use std::error::Error; |
cargo test执行其中的单元测试用例
windows中设置环境变量,并运行程序
PS E:\code\rust\minigrep> $Env:IGNORE_CASE=1; cargo run Body poem.txt
- 使用
eprintln!将错误信息输出到标准错误流,将正常输出到文件中。
cargo run BOdy poem.txt > output.txt