github地址:https://github.com/bradyjoestar/rustnotes(欢迎star!)
pdf下载链接:https://github.com/bradyjoestar/rustnotes/blob/master/Rust%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
参考:
https://rustcc.gitbooks.io/rustprimer/content/ 《RustPrimer》
https://kaisery.github.io/trpl-zh-cn/ 《Rust程序设计语言-简体中文版》
包管理系统与版本管理工具
包管理系统是所有语言向工程化方向走必须考虑的事情。
rust的包管理系统和go的包管理系统以及java的包管理系统大大不同,很容易给人造成困惑。
最主要原因是:
1.Rust 的模块支持层级结构,但这种层级结构本身与文件系统目录的层级结构是解耦的。
因为 Rust 本身可用于操作系统的开发。
开发者需要自己去定义路径,定义mod的层级关系,配合rust的默认约定。这点和java,go开发完全不同,在面向vm的语言中这些都不需要考虑。
2.Rust的包管理系统中使用了大量的默认约定,很容易使人头昏脑乱。
而在开发中我们又必须建立模块层级系统,rust给出了如下方案,在给出了一些模块的最基本规则外,由开发者更大范围地自定义模块的存在。
首先在一个rust项目中,首先定义了crate和module。
1.1 Crate
1.crate编译后会形成一个库(例如.so)或二进制可执行文件。crate分为两种:lib crate和bin crate。
2. 一个包可以带有零个或一个lib crate 和任意多个bin crate。一个包中必须有crate,至少一个,(lib crate或bin crate都可以)
3.通常写rust项目时非常依赖crate,很多重要的信息都是配置在cargo.toml文件中,不仅仅包括lib和crate的入口文件,后面还有很多的attribute。
4.rust对于crate的layout有一些默认的约定:
i.Cargo 约定如果在代表包的 Cargo.toml 的同级目录下包含src目录且其中包含main.rs文件的话,Cargo 就知道这个包带有一个与包同名的bin crate,且src/main.rs就是 crate 根。不用在写cargo.toml的时候精确到文件。
ii.另一个约定如果包目录中包含src/lib.rs,则包带有与其同名的lib crate,且src/lib.rs是 crate 根。同样不需要精确到文件。
iii. 包可以带有多个二进制 crate,默认将文件置于 src/bin 目录,但是也可以自由配置。
举例:
[[bin]]
name = “base_language_demo”
会自动去寻找src/bin/base_language_demo.rs作为bin crate的编译入口。
[[bin]]
name = “src/bin_build_demo/bin_test.rs”
非常清晰地指明了文件名,直接以src/bin_build_demo/bin_test.rs作为编译入口。
1.1.1 Cargo
rust官方参考了现有语言管理工具的优点,于是就产生了cargo。主要是为了减少复杂的项目管理配置参数。cargo工具是官方正统出身。
在cargo.toml中不配置唯一的lib crate和bin crate name的话,会自动去根据package进行命名。
约定的补充:
cargo.toml和cargo.lock文件总是位于项目根目录下。
源代码位于src目录下。
默认的库入口文件是src/lib.rs。
默认的可执行程序入口文件是src/main.rs。
其他可选的可执行文件位于src/bin/*.rs(这里每一个rs文件均对应一个可执行文件)。
外部测试源代码文件位于tests目录下。
示例程序源代码文件位于examples。
基准测试源代码文件位于benches目录下。
cargo.toml是cargo特有的项目数据描述文件,对于猿们而言,cargo.toml文件存储了项目的所有信息,它直接面向rustacean,如果想让自己的rust项目能够按照期望的方式进行构建、测试和运行,那么,必须按照合理的方式构建'cargo.toml'。
而cargo.lock文件则不直接面向开发者,也不需要直接去修改这个文件。lock文件是cargo工具根据同一项目的toml文件生成的项目依赖详细清单文件。
Cargo字段:
1.[package]段落描述了软件开发者对本项目的各种元数据描述信息。
2.[dependency]
3.单元测试主要通过在项目代码的测试代码部分前用#[test]属性来描述,而集成测试,则一般都会通过toml文件中的[[test]]段落进行描述
4.example用例的描述以及bin用例的描述。其描述方法和test用例描述方法类似。不过,这时候段落名称'[[test]]'分别替换为:'[[example]]'或者'[[bin]]'
1.2 module
Rust 提供了一个关键字 mod,它主要起到两个用途,在一个文件中定义一个模块,或者引用另外一个文件中的模块。
模块也有一些默认的约定:
1.每个 crate 中,默认实现了一个隐式的根模块(root module);
2.模块的命名风格也是 lower_snake_case,跟其它的 Rust 的标识符一样;
3.模块可以嵌套;
4.模块中可以写任何合法的 Rust 代码;
为了让外部能使用模块中item,需要使用pub关键字。外部引用的时候,使用use关键字。
1.2.1 module的可见性
为了让外部能使用模块中 item,需要使用 pub 关键字。外部引用的时候,使用 use 关键字。
规则很简单,一个 item(函数,绑定,Trait 等),前面加了pub,那么就它变成对外可见(访问,调用)的了。
1.2.2引用外部文件模块
通常,我们会在单独的文件中写模块内容,然后使用 mod 关键字来加载那个文件作为我们的模块。
比如,我们在src下新建了文件 aaa.rs。现在目录结构是下面这样子:
foo
├── Cargo.toml
└── src
└── aaa.rs
└── main.rs
我们在 aaa.rs 中,写上:
pub fn print_aaa() {
println!("{}", 25);
}
在 main.rs 中,写上:
mod aaa;
use self::aaa::print_aaa;
fn main () {
print_aaa();
}
编译后,生成一个可执行文件。
细心的朋友会发现,aaa.rs 中,没有使用 mod xxx {} 这样包裹起来,是因为 mod xxx; 相当于把 xxx.rs 文件用 mod xxx {} 包裹起来了。(又一个约定)初学者往往会多加一层,请注意。
1.2.3 多文件模块的层级关系
Rust 的模块支持层级结构,但这种层级结构本身与文件系统目录的层级结构是解耦的。
mod xxx; 这个xxx不能包含::号。也即在这个表达形式中,是没法引用多层结构下的模块的。也即,你不可能直接使用mod a::b::c::d;的形式来引用a/b/c/d.rs这个模块。
换句话说,必须依靠rust的默认约定去由开发去建立层级关系。rust的层级关系是我们自己依靠默认规则自己定义出来的!
那么,Rust 的多层模块的定义查询遵循如下两条规则:
1.优先查找xxx.rs文件
2.main.rs、lib.rs、mod.rs中的mod xxx;默认优先查找同级目录下的xxx.rs文件;
其他文件yyy.rs中的mod xxx;默认优先查找同级目录的yyy目录下的xxx.rs文件;
如果xxx.rs不存在,则查找xxx/mod.rs文件,即xxx目录下的mod.rs文件。
先不要去考虑默认不默认的问题,优先考虑尽可能不要让定义的mod xxx有两种解释。另外每个xxx只定义一次。
例子:
1.默认优先查找module1.rs文件
2.module1.rs中的mod.xxx查找module1目录下的xxx.rs文件。
可以看到,module1下没有mod.rs文件,避免歧义。
1.2.4 module 路径
前面我们提到,一个 crate 是一个独立的可编译单元。它有一个入口文件,这个入口文件是这个 crate(里面可能包含若干个 module)的模块根路径。整个模块的引用,形成一个链,每个模块,都可以用一个精确的路径(比如:a::b::c::d)来表示;
与文件系统概念类似,模块路径也有相对路径和绝对路径的概念。为此,Rust 提供了self和super两个关键字。
路径是自定义出来的!
super表示,当前模块路径的上一级路径,可以理解成父模块。
另外,还有一种特殊的路径形式:
::xxx::yyy
它表示,引用根路径下的 xxx::yyy,这个根路径,指的是当前 crate 的根路径。
1.2.5 Re-exporting
我们可以结合使用 pub use 来实现 Re-exporting。Re-exporting 的字面意思就是 重新导出。它的意思是这样的,把深层的 item 导出到上层目录中,使调用的时候,更方便。接口设计中会大量用到这个技术。
还是举上面那个 a::b::c::d 的例子。我们在 main.rs 中,要调用 d,得使用 use a::b::c::d; 来调用。而如果我们修改 a/mod.rs 文件为: a/mod.rs 文件内容:
pub mod b;
pub use b::c::d;
那么,我们在 main.rs 中,就可以使用 use a::d; 来调用了。
baidu/rust-sgx-sdk中的SgxMutex就使用了re-exporting.
1.2.6 加载外部库
外部库是通过
extern crate xxx;
这样来引入的。
至于为何 Rust 要这样设计,有以下几个原因:
1.Rust 本身模块的设计是与操作系统文件系统目录解耦的,因为Rust本身可用于操作系统的开发;
2.Rust中的一个文件内,可包含多个模块,直接将a::b::c::d映射到a/b/c/d.rs会引起一些歧义;
3.Rust一切从安全性、显式化立场出发,要求引用路径中的每一个节点,都是一个有效的模块,比如上例,d是一个有效的模块的话,那么,要求c, b, a分别都是有效的模块,可单独引用。
1.2.7 prelude
Rust 的标准库,有一个 prelude 子模块,这里面包含了默认导入(std 库是默认导入的,然后 std 库中的 prelude 下面的东西也是默认导入的)的所有符号。
大体上有下面一些内容:
std::marker::{Copy, Send, Sized, Sync}
std::ops::{Drop, Fn, FnMut, FnOnce}
std::mem::drop
std::boxed::Box
std::borrow::ToOwned
std::clone::Clone
std::cmp::{PartialEq, PartialOrd, Eq, Ord}
std::convert::{AsRef, AsMut, Into, From}
std::default::Default
std::iter::{Iterator, Extend, IntoIterator, DoubleEndedIterator, ExactSizeIterator}
std::option::Option::{self, Some, None}
std::result::Result::{self, Ok, Err}
std::slice::SliceConcatExt
std::string::{String, ToString}
std::vec::Vec
在baidu/rust-sgx-sdk 这些都需要重新引入。
1.2.8 pub restricted
在rust中后来引入了支持使item仅仅在其能够指定想要的作用域(可见范围)可见。这块的内容可以查看https://rustcc.gitbooks.io/rustprimer/content/module/pub-restricted.html相关内容。
理性看待rust语言的升级。只是升级频度高一些,这样的升级在java和go中也普遍存在。go中的感知稍微小一些。
每次升级都要更新相应的工具链。保证最新的编译器和链接器可以将新生成的程序生成出来。
Rust的包管理系统非常明显地体现了它的与众不同。
1.3 版本管理工具
作为一门更新快速的语言,rust开发了专用的版本管理工具rustup。
对于go而言,不需要对这些东西进行了解,只需要下载包安装到环境变量中即可。
而rust的开发中经常会遇到配置不同的toolchain等需求,因此官方开发了rustup。rustup功能如下:
1.管理安装多个官方版本的 Rust 二进制程序。
2.配置基于目录的 Rust 工具链。
3.安装和更新来自 Rust 的发布通道: nightly, beta 和 stable。
4.接收来自发布通道更新的通知。
5.从官方安装历史版本的 nightly 工具链。
6.通过指定 stable 版本来安装。
7.安装额外的 std 用于交叉编译。
8.安装自定义的工具链。
rustup常用命令:
1.rustup default <toolchain> 配置默认工具链.
2.rustup show 显示当前安装的工具链信息。
3.rustup update 检查安装更新。
4.rustup toolchain [SUBCOMMAND] 配置工具链
更多细节查看rustprimer。
1.4 rust编译运行
ps: cargo build 普通编译
ps: cargo build --release # 这个属于优化编译
ps: ./target/debug/hellorust.exe
ps: ./target/release/hellorust.exe # 如果前面是优化编译,则这样运行
ps: cargo run # 编译和运行合在一起
ps: cargo run --release # 同上,区别是是优化编译的