rust中的包
在现代的编程体系中,包管理是非常重要的一环,一门语言如果没有包管理体系,几乎可以说是不可能支持的起复杂的应用的。我们在《从前端到rust:如何在rust中使用函数》已经初步的接触了下rust包引用相关的知识,今天我们接着深入来了解下rust是怎么做包管理的。
学习rust的包管理之前,我们首先要了解三个概念:包(package)、箱(crate)、模块(module)。这三个基本的概念构成了rust的完成的包体系。
包
在js中,你新建一个文件夹并在根目录下新建一个package.json文件,填入相关的信息之后,你就完成了一个js包的创建。
在rust中,包的概念也和js差不多:新建一个文件夹,之后在根目录下新建Cargo.toml文件并输入name、version等信息,就完成了一个rust包的创建。你也可以用cargo new来帮助你创建一个包,就像我们之前创建hello_world那个包一样。
cargo new hello_world
cargo new这条命令本质上就是创建一个文件夹,并在个文件夹下生成一个具备初始化信息的Cargo.toml文件。
我们都知道,js中一个包默认都会有入口文件,rust中也是一样。在rust中,默认以src/main.rs作为包的二进制入口文件,src/lib.rs作为库文件的入口。
箱
上文中我们提到,rust中默认以src/main.rs作为包的二进制入口文件,src/lib.rs作为库文件的入口,那么为什么会需要两个入口呢?这里我们就需要提到箱(crate)的概念了。
"箱"是rust中最小的代码编译单元,当我们执行rustc(抱歉,之前并没有讲到这一点,因为我觉得当时可能用不到,实际上在这里你也不需要过多的去关心rustc相关的知识,只需要知道这是一个编译rust文件的命令,就像tsc)去编译rust文件的时候,rustc就会认为该文件是一个箱。
在rust中箱有两种基本类型:库箱(library crate)和二进制箱(binary crate)。src/lib.rs就是库箱的入口文件。
一个rust包中必须至少包含有一个箱(库或者二进制都可以),也可以同时有很多个二进制箱,但是一个包同时只能有一个库箱。
关于库箱和二进制箱的概念,初学者肯定会有些迷糊,实际上,我自己也是花了点时间才搞懂。在js中,我们写在任意js文件中的任意模块,都可以通过require去引用。但是在rust中不同,rust中只有库箱里面暴露的模块才可以被其他rust项目引用。简单来讲就是,如果你的rust代码需要作为依赖被第三方使用的,那么就需要封装在库箱中;如果只是自己项目使用,那么就只需要封装在二进制箱中。我们之前使用的生成随机数的库rand::Rng,就是一个库箱。
一个二进制箱中,通常包含一个main方法,二进制箱执行的时候会默认执行main方法;库箱中则没有main方法。
模块和模块的访问权限
相比于包和箱这些物理上的概念,模块更多的是一个逻辑上的概念。在实际的项目开发中,我们通常需要把一些有关联的代码组织在一起,以方便管理、复用或者阅读等,这些把代码从逻辑上组织在一起的能力在rust中就是模块的概念。
模块的声明关键字是mod。
mod first_mod {
fn say_hello() {
println!("hello mod")
}
}
在上面的代码中,我们就声明了一个叫first_mod 的模块,模块中包含一个say_hello的方法。
接着我们可以调用封装好的模块:
mod first_mod {
fn say_hello() {
println!("hello mod")
}
}
fn main() {
first_mod::say_hello()
}
这里需要注意的是,rust中我们是通过::来引用模块,这是和js中有着很大不同的一点。
但是执行之后,我们并没有在控制台看到"hello mod",反而收获了一堆异常:
是哪里不对呢?明明我们写了say_hello这个方法,编译器却找不到它。原因就在于rust模块的访问权限机制。
在rust中,模块有两种访问权限:private和public,private权限只允许模块中和它同级的成员访问,而public权限则允许在任何地方被访问。模块成员如果没有被声明权限,则默认是private权限,因此无法在main中被调用。如果想要在main中被调用,则需要显式的声明成为public成员,就像下面这样:
mod first_mod {
pub fn say_hello() {
println!("hello mod")
}
}
fn main() {
first_mod::say_hello()
}
现在,我们就可以在控制台看到"hello mod"了。
rust中也允许我们在模块中嵌套模块:
mod first_mod {
mod hello_mod {
fn say_hello() {
println!("hello mod")
}
}
}
注:上述代码都是在main.rs这个文件内。
rust中包的引用
在讲述完模块之后,我们对rust的包也有了一个比较系统的认识,这时候,你肯定会想问,那么我要怎么引用封装包呢?
在rust中,引用大致分为两种情况(这两种情况其实在js中也一样):1、同项目下引用;2、跨项目引用。我们先来看同项目下引用的情况。
同项目下引用包
我们先在main.rs平级的目录下新建一个first_mod.rs的文件,目录结构如下:
src
├── main.rs
└── my_mod.rs
之后把下面的代码复制进first_mod.rs:
pub mod first_mod {
pub mod hello_mod {
pub fn say_hello() {
println!("hello mod")
}
}
}
删除main.rs里面的内容,并输入以下内容:
mod my_mod;
use crate::my_mod::first_mod;
fn main() {
first_mod::hello_mod::say_hello()
}
我们就完成了对my_mod模块的引用。
注意看上面代码:
我们先是通过mod my_mod来引入my_mod这个文件(类似于js中的require),接着通过use crate::my_mod::first_mod告诉编译器,我们要引用的是my_mod这个文件下的first_mod这模块,这样你就可以在main.rs中自由的使用first_mod模块下的内容了(前提是模块的访问权限得是public)。
注:你也可以在use的时候输入完整的模块路径,例如use crate::my_mod::first_mod::hello_mod,这样你在就直接可以使用hello_mod::say_hello()来调用了。
跨项目引用
在讲解完同项目下引用的模块之后,我们接着来看如何在跨项目引用自己封装的包。 简单起见,我们直接在同一个根目录下用cargo new新建两个rust的项目,新建后的目录结构如下:
root
├── hello_mod
│ ├──────src
│ └──────Cargo.toml
└── hello_rust
├──────src
└──────Cargo.toml
接着我们在hello_mod的src目录下新建lib.rs(为什么是lib.rs还记得吗?),并输入以下内容:
pub mod first_mod {
pub mod hello_mod {
pub fn say_hello() {
println!("hello mod")
}
}
}
现在我们就有了一个hello_mod的包,里面包含一个库箱,库箱包含有first_mod这个模块。
我们有两种方法可以在hello_rust项目中调用hello_mod这个包。
第一种就是通过cargo publish的方法将hello_mod这个包发布到crates.io上,这样你就可以在hello_rust中通过cargo add hello_mod把hello_mod添加到依赖中,之后就可以正常的访问hello_mod了。但是这种方法只适合用来访问功能相对稳定的包,如果我们的包还在开发中,我们肯定不能通过这种方式来引用,不然每改一次代码就cargo publish一下,这酸爽,不敢想象。
所以,为了便于包的调试,我们可以通过第二种方法来添加依赖。
在hello_rust项目下我们直接执行:
cargo add --path ./../hello_mod
cargo add默认是从crates.io上查找依赖,但是通过--path这种方法可以让我们安装本地包到项目下。执行完之后我们再看hello_rust下的Cargo.toml,发现多了下面这行代码:
hello_mod = { version = "0.1.0", path = "../hello_mod" }
说明hello_mod包已经被成功安装了。
接着我们在hello_rust的main.rs中调用它:
use hello_mod::first_mod;
fn main() {
first_mod::hello_mod::say_hello()
}
执行cargo run之后我们就可以在控制台看到“hello mod”了~。
今天我们简单的讲了下rust中的包管理,目前我们应该具备了如何封装、使用模块,如何开发自己的包等基本知识,但是具体包的相关管理、Cargo.toml文件的配置信息我们并没有过多谈及,不过这应该暂时不影响我们接着探索rust,所以关于这些内容的更深入的讨论暂时会被搁置,有兴趣的可以自行查阅相关文档了解~
这篇文章的篇幅比我想象中的长啊,但是内容真的很重要,希望你们还有耐心可以看到最后~~~
最后,撒花~~
咦,为什么要撒花?)