之前的6篇文字,大体上介绍了Rust的基本特征,并带大家领略了一下该语言的类型系统。以此为基础,相信读者能自己写一些基础的程序了,但这还不够,我们需要持续升级,逐渐进入实操实际项目的层次。
在工作中,程序往往是多由文件组成的,而其间存在依赖关系,编译,链接等都不是很简单事情,而这其中所涉及的复杂度是必须管理的。尤其对于大型项目,人工管理已变得不太可能。幸而在Rust世界里,有强大项目管理工具存在。
那么,在这本章这几篇中会介绍Rust如何管理大型项目,以及相关项目管理工具的特色,具体而言,覆盖以下主题:
Package管理器
模块
Cargo项目管理器和crate作为编译单元
创建和构建项目
运行简易测试
Cargo子命令以及第三方包crate的安装
项目实操
Package管理器
模块
现实中的代码,通常是多文件,多依赖的,因而需要好的管理工具,以保证条理始终清晰。对于常用Python或是R的朋友,应该对各自Package的管理工具所涉及的各种更新,检查有所认识。而对于像C或C++这种相对底层的语言,没有默认的Package管理器,主要用GNU make tool,而该工具语言晦涩,语焉不详,而且经常需要手动添加头文件,还没有内建的下载第三方package的工具,因此不便于长期性的大型项目的管理。
那么,好在Rust不是这样,其拥有切实的Package管理工具,Cargo,可以应对足够复杂的情况,易于创建和维护项目代码,而这就是本篇文字的核心。
作为了解Package管理器的基础,我们先要稍微详细再介绍一下Rust中的模块(Modules),看一下代码是如何组织的。
每个Rust程序都是从root模块开始的:
创建一个库,root模块是lib.rs文件。
创建一个可执行文件,root模块是任何带有主函数的文件,通常是main.rs。
当代码膨胀时,为了在组织项目时提供灵活性,可以分成模块,这里面有多种创建方法。
创建模块最简单的方法是在现有模块中使用mod{}块,看下以下代码
// mod_within.rs
mod food {
struct Cake;
struct Smoothie;
struct Pizza;
}
//use food::Cake;
fn main() {
let eatable = Cake;
}
这里我们构建了一个内部模块,名为food,在大括号内部,声明了三个结构体,Cake, Smoothie, 和Pizza;
在main中,我们创建一个make的实例。代码结果如下:
显然,编译器不知道Cake的定义,此时加上use food::Cake;语句,再编译一下,看下结果,如下所示
原来的错误没有了,但这里得到另外一个错误,提及Cake是私有的,这里引出模块另一个重要特征,默认私有设置。如果需要访问的话,需要两步走:
改成如下
// mod_within.rs
mod food {
pub struct Cake; //pub
struct Smoothie;
struct Pizza;
}
use food::Cake;
fn main() {
let eatable = Cake;
}
虽然得到了3个warning,但还是通过编译了。可能有的读者问了,为什么要定义这样一个嵌套的模块,有关其原因和用法,会在详解代码测试(第3章)时候给大家介绍。
如题,模块可以创建为文件。直接上个实例,这里我建个目录,包含以下结构。
在foo.rs中包含一个结构体Bar,以及其实现impl 内容,代码如下:
// modules_demo/foo.rs
pub struct Bar;
impl Bar {
pub fn init() {
println!("Bar type initialized");
}
}
这时候,我们希望在main.rs中使用这个模块,那么代码如下:
// modules_demo/main.rs
mod foo;
use crate::foo::Bar;
fn main() {
let _bar = Bar::init();
}
看下main.rs代码,我们先声明了模块foo。然后导入了foo,这里涉及的格式为use crate::foo::Bar, 需要谈一点的是,这里用的前缀是crate,不知道读者是否还有印象,crate在rust指文件包或库文件,我们看看在这里指什么。
一般而言,在rust中,导入模块所需要用到的前缀主要有三种:
Absolute imports:绝对路径
crate,从根节点起,导入与当前文件同级的文件。
Relative imports:相对路径
self:从当前模块开始
Super:从上一级模块开始
以目录作为模块,意味着结构具备了层次性,我们可以在其中创建子模块文件或更多目录层级。设想这样一个情景:我们有一个目录my_program,其中有一个名为foo的模块作为文件foo.rs,包含了一个名为Bar的类型以及foo的功能。随着时间的推移,Bar APIs的数量越来越多,我们希望将其分离为一个子模块。这时就涉及以目录为模块的内容了。
那么我们以代码实现一下,先建立一个目录,在main.rs有一个入口点,再加一个名为foo. rs的目录,而该目录现在包含一个名为bar.rs的子模块,如下所示:
为了让Rust知道bar,我们还需要创建一个名为foo的文件,与在foo目录同级。foo.rs文件将包含在foo目录下创建的任何子模块(这里是bar.rs)的mod声明。
我们看下bar.rs文件的内容:
//bar.rs
pub struct Bar;
impl Bar {
pub fn hello() {
println!("Hello from Bar !");
}
}
这里有一个单元结构体Bar有一个相关的方法hello,我们会在main.rs中使用这个API。接着,看下foo.rs文件:
//foo.rs
mod bar;
pub use self::bar::Bar;
pub fn do_foo() {
println!("Hi from foo!");
}
第3行我们添加了一个模块的声明。
导入模块bar,导入关键词用的self,表明是从模块自身导入,这里将Bar定义为pub,以保证子模块内容可以为父模块使用。这是一种很方便的写法,原因还要到后续篇章中才能揭示。
最后,我们看下main.rs文件:
// my_program/main.rs
mod foo;
use foo::Bar;
fn main() {
foo::do_foo();
Bar::hello();
}
可见,程序跑通了。
本篇在介绍了Package管理器之后,着重讨论了rust中模块的内容,而相关实例是为了让读者对如何使用模块能够有些基本的认识,以作为后续学习内容的基础。
下一篇会介绍一下Cargo和Crates。