美文网首页工作生活
Rust学习笔记1 包管理系统和版本管理工具

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

作者: bradyjoestar | 来源:发表于2019-07-04 16:09 被阅读0次

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

    image.png

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


    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 <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 # 同上,区别是是优化编译的

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/ipsrhctx.html