Rust学习笔记1 包管理系统和版本管理工具

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文件

Rust学习笔记1 包管理系统和版本管理工具_第1张图片
image.png

2.module1.rs中的mod.xxx查找module1目录下的xxx.rs文件。


Rust学习笔记1 包管理系统和版本管理工具_第2张图片
image.png

可以看到,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 配置默认工具链.
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 # 同上,区别是是优化编译的

你可能感兴趣的:(Rust学习笔记1 包管理系统和版本管理工具)