Rust 从 0 到 1- 代码组织 -Packages 和 Crates
随着代码规模的增长,代码的组织变得尤为重要,因为我们无法记住我们写过的代码,甚至完全不认识(我想大部分同学都有碰到不认识自己代码的情况)。通过根据约定的规则对代码进行组织,可以帮助我们找到实现了特定功能的代码,以及帮助我们正确的修改它。
在前面的例子中,我们编写的代码都在一个文件的一个模块中。随着代码的增长,我们可以通过将代码分解为多个模块和多个文件来组织代码。一个 package 可以包含多个 binary crates (编译为多个可执行程序)和一个 lib crate(编译为代码库)。随着 package 的增长,我们可以将 package 中的部分代码提取出来,做成独立的 crates,作为外部依赖。并且对于一个由一系列相互关联的 packages 组合而成的超大型项目,Cargo 提供了 “工作空间(Workspaces)” 这一功能,这个将在后续章节讲解。
同时封装实现细节可以在高层实现代码的复用:当实现了某个功能后,其它代码可以通过其公开接口来进行调用,而不需要知道底层的实现。在编写代码时可我们可以自己定义哪些部分是其他代码可以使用的公开部分,这部分相对保持稳定;以及哪些部分是私有部分,我们可以进行修改而不影响其它代码的调用。
另外一个相关的概念是“作用域(scope)”:我们编写的代码中定义的变量、函数、结构体、枚举、模块、常量等所在的上下文。当阅读、编写和编译代码时,程序员和编译器需要知道某个名称是否引用了变量、函数、结构体、枚举、模块、常量等。我们可以创建作用域,并改变哪些在作用域内还是作用域外。同一个作用域内不能拥有两个相同名称的项;有一些工具可以帮助解决名称冲突。
Rust 有许多特性可以帮助我们组织代码,包括哪些可以被公开,哪些作为私有部分,以及在不同作用域中使用不同的名字。这特性统称为 “模块系统(module system)”,包括:
Packages:Cargo 的一个功能,它允许你构建、测试和分享 crates。
Crates:一个模块的树形结构,可以编译为库或二进制程序。
Modules 和 use :对路径的组织、作用域和私有性进行管理的手段。
Paths:包含路径(命名空间)的结构体、函数或模块等的名称
下面我们将先介绍 packages 和 crates。Crate 分为两类,一类是 library crate,一类是 binary crate。Crate root 是 Rust 编译器的入口,如 lib.rs、main.rs,并且是 crate 的根模块。Package 可以包含提供一组功能的一个或者多个 crates。一个 package 包含一个 Cargo.toml 文件,描述如何去构建这些 crates。Package 相关的一些规则如下:
一个 package 中至多只能包含一个 library crate,即 0 个或 1 个
一个 package 中可以包含任意多个 binary crate,即 0 个或多个
一个 package 中至少包含一个 crate,可以是 library crate,也可以是 binary crate
下面让我们看看如何创建一个 package:
cargo 命令会创建一个 Cargo.toml 文件,这个是 package 的描述文件,包括名称、版本、依赖、构建等诸多内容,并遵循以下约定:src/main.rs 就是一个与 package 同名的 binary crate 的 crate root。同样,如果目录中包含 src/lib.rs,则 package 包含与其同名的 library crate,且 src/lib.rs 是其 crate root。Crate root 将由 Cargo 传递给 rustc 来构建 library 或者 binary。
例子中我们创建了一个只包含 src/main.rs 的 package,意味着它只含有一个名为 my-project 的 binary crate。如果一个 package 同时含有 src/main.rs 和 src/lib.rs,则它有两个 crates:一个 binary crate 和一个 library crate,且名字都与 package 相同。通过将文件放在 src/bin 目录下,一个 package 可以拥有多个 binary crate:每个 src/bin 下的文件都会被编译成一个独立的 binary crate。
Crate 主要用于将一个作用域内的相关功能放到一起,使得该功能可以很方便地在多个项目之间共享。举一个例子,假设我们有一个随机数生成器(rand) crate ,它提供了生成随机数的各种功能。通过将这个 crate 引入到我们项目的作用域中,我们就可以通过它的名字:rand 使用其提供的全部功能。并且将一个 crate 的功能保持在其自身的作用域中,还可以防止潜在的冲突。例如,rand crate 提供了一个名为 Rng 的功能。我们还可以在我们自己的 crate 中定义一个名为 Rng 的结构体。这样当我们将 rand 作为一个依赖引入的时候,编译器不会混淆 Rng 这个名字到底指向哪个。在我们的 crate 中,它指向的是我们自己定义的 Rng,此外我们还可以通过 rand::Rng 来访问 rand crate 中的 Rng 功能。
版权声明: 本文为 InfoQ 作者【山】的原创文章。
原文链接:【http://xie.infoq.cn/article/0a3d1b7026f22cf15673157a8】。文章转载请联系作者。
评论