懂你,更懂Rust系列之不断增长的Rust项目管理

lgli

共 7613字,需浏览 16分钟

 · 2021-06-12



简单爱,你心所爱

世界也变得大了起来

所有花都为你开

所有景物也为了你安排

我们是如此的不同

肯定前世就已经深爱过

讲好了这一辈子

再度重相逢



前面所分享到的所有的Rust相关的代码,都是在一个文件中的,那么当在实际的开发过程中,肯定是涉及到很多很复杂的代码量的,这个时候,就涉及到公共函数的封装,接口调用等等关联的东西,那么今天就给大家带来关于Rust项目的实际工作中的,包、模块管理。


首先,看下新建一个名为first-large-project的Rust项目


其结构如下:


eff2538742fc6e657eef90d1dc21d426.webp


新建的一个Rust项目,包含一个src文件夹,下面有一个main.rs文件,此外还有Cargo.lock和Cargo.toml文件


首先先来了解下Cargo.lock和Cargo.toml


下面是2个文件内容的对比:


b941c71f35cc4d45b1339cfd0c5ed96a.webp


Cargo.lock:注释描述中说到,该文件是Cargo自动生成的,同时不是手动手动编辑的,言下之意就是,我们不需要去手动修改这个文件。那么这个文件究竟是干啥用的呢?


  • Cargo.lock contains exact information about your dependencies. It is maintained by Cargo and should not be manually edited.


这里是官方对该文件的解释:包含项目有关依赖项的确切信息


即我们可以这么理解,这个文件用于记载项目的所有依赖的详细信息



Cargo.toml:这里可以看到,描述了本项目的基本信息,同时下面有一个

[dependencies],即这里可以给项目添加依赖。



那么我们尝试添加一个依赖:


afe522988cb8e01a2f3949c8351de735.webp



这里添加了一个rand库,等待项目构建完成,这时候可以看看Cargo.lock文件:


db9a75b28cc379852ceee7be85bac523.webp



上图可以看见,Cargo.lock文件记录了更多的依赖信息,而不是像Cargo.toml文件中记载的那一个依赖了。


然后可以通过依赖的git地址,发现rand项目所有依赖的库和rand包含的子项目也都在我们的Cargo.lock文件中有所记载。


这里显然,Cargo.lock就是对Cargo.toml依赖的一个详细说明。



接下来,先明确几个概念:



crate


crate:a library or executable program is called a crate .



上面是Rust官方给出的一个解释,一个库或者可执行的程序,被称为crate


包<package>


包:是提供一系列功能的一个或者多个crate,即包=crate+crate+...+crate

一个包会包含一个Cargo.toml文件,描述如何构建包所包含的所有crate


那么其实上面例子中的这个first-large-project就是一个包



然后回头看下上面的例子,还剩下一个src/main.rs文件。Cargo遵循了一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根,那么这里的src/main.rs就是一个二进制的crate


下面有3点重要的约束:


1、一个包中最多只能包含一个库crate


2、一个包中可以有多个二进制crate


3、一个包中至少包含一个crate


以上三点约束是很重要很重要的,在之后构建大型的Rust项目,这是一个必须掌握的,否则,我们连如果建Rust文件都搞不定,总不能把所有代码都写在一个文件吧!



结合上面的例子,接着看这个first-large-project,可以看到目前这个项目只有一个src/main.rs文件,上面也讲过了,这个main.rs文件其实就是和我们项目同名的二进制crate根。


那么这就满足了3点约束中的第3条:一个包中至少包含一个crate,因为如果在把这个main.rs去掉,这也就不是一个程序了,Rust也无法编译构建了


那么看3点约束中的第2条:一个包中可以有多个二进制crate,那么这个怎么做呢?


这时候,在src目录下,新建一个main_other.rs


8a4b48f417b6dd026a101340a400c658.webp


然后定义main函数,这时候,可以发现,虽然这里也定义了main方法,但是可以看到,这个main函数是不可被Rust编译后可以运行的主函数mian,


对比这2个main函数:


f0a2c73900e6f938acc79e510a9ae7a4.webp


后者没有run标识,这就说明,这个main_other.rs不是一个二进制的crate,仅仅是一个普通的Rust文件,在此时,先透露一下,这个main_other.rs被称为module。


我们在这个main_other.rs的main函数前面加一个pub关键字,然后就可以在main.rs中调用了。


0f4c2e234703f075268ab87b4eef606d.webp


修改main.rs


4157cd3499276b610cf35fcf2920ce73.webp


编译运行:


6e4b3cb21212b2eee4863d6cbcbaa225.webp


这里使用mod关键字,将与main.rs同级目录下的main_other模块儿<module>引入进来,然后调用main函数。


main_other.rs的main函数为啥需要加一个pub呢?


在Rust中,默认的所有函数或者模块或者结构体、枚举等,都是private的,即只能自己或者同级调用,不可被其他地方使用,如果需要被其他地方调用,需要将其设置为公开的,即:pub


比如,假设去掉pub


编译报错


12a2a654e9635c06130161fd410483d0.webp


那么有人可能会问,在main.rs中,是如何使用mod,将main_other引入到main.rs中的呢?


这里,是因为,在Rust中,src/main.rs为与包同名的二进制crate根,这里的src/main_other.rs,为名为main_other<注,模块名与文件同名>的crate根模块,即两者是平级的:


a587559a18b2e4c9877034aae5ce38a5.webp


所以,在main.rs中,可以直接使用mod,引入,但是由于main_other.rs中的main函数是私有的,所以在main.rs中,不可使用,所以报错。


所以必须要改为pub



此外,这里有一个比较重要的,容易出错的地方,那就是二进制的crate库,只能在当前目录下,通过mod方式引入module。至少当前版本是这样子的。


这个地方跑得有些远了,这里需要实现拥有多个二进制crate库,


那么现在一个目的就是如何将这个main_other.rs编程二进制crate库,因为现在它是个module


在src下新建一个bin目录,然后将这个main_other.rs移动进去看看会发生什么?


4541ca2a65887e84991e9c6e18ed0ad5.webp


这时候,可以看见这个main_other.rs也变成了一个二进制的crate库


即:一个包中可以有多个二进制crate


接下来分析第一个:一个包中最多只能包含一个库crate


回到项目的初始状态:


5026e975984143d49d34f001e65794f9.webp


这时候,在src下新建一个lib.rs


d255a55919d29ad186d538d9d3576144.webp


Rust约定:这个lib.rs就是一个和项目同名的crate库


在lib.rs中,定义两个模块儿:


1530b1541d7b70c4807229136a23e3bb.webp



这里描述下,在Rust中,模块儿定义方式:


mod 关键字然后一对大括号,将模块儿内容写入其中,模块儿中,可以定义方法、结构体、枚举等。因为下面我们需要调用这个lib.rs中的内容,所以全部定义为pub的。



然后在二进制文件中调用这个lib.rs


bb507742d6a230e2a4f81a7947997030.webp


这里可以看见,在二进制的Rust文件中,调用crate库,需要用use + crate库名,但是我这里用的是:use first_large_project;


但是我们前面说到,Rust约定crate应该是与包同名,这里的crate虽然不是二进制的,难道就不是与包同名了么?


显然不是这样的,不管是不是二进制的crate,都是与包同名,但是因为这里写上use first-large-project;Rust解析会报错:


81afcb345dcfd8e845f09d0f0b719004.webp



因为和关键字冲突了,所以不能用-,这时候就提了一个醒,Rust包命名的时候,尽量不要用-,但是如果已经用了怎么办?比如上面我们这个项目。可以通过修改Cargo.toml文件的项目构建信息来改变:


比如这里:


1d7af980dbcb81348ffe5d6662e45b12.webp


然后包的crate库就变成了,first_large_project


所以就有了前面使用use first_large_project;将crate库引入crate二进制文件中。


在lib.rs文件中,再定义一个函数,函数调用

lib_module_one.print_one和lib_module_two.print_two

函数


cc90522a5f8e9236aa61d4d99baa48ec.webp


这里,可以看到直接通过

lib_module_one::print_one()

或者lib_module_two::print_two()

完成了函数调用


这是Rust语言中,通过相对路径对模块的引入,因为这里lib_module_one和print_one_and_two函数同属一个层级,即两者是兄弟关系,所以可以直接通过相对路径调用,当然也可以通过绝对路径来调用:


比如:


ca3c1d2b8cdf1c62885fce7595add159.webp



绝对路径,即通过关键字crate : : +module路径来表示


 在二进制crate库中调用下:


d78e85c4da3e0ea69080dd594a48bdfe.webp


下面尝试在lib_module_two中定义一个结构体,同时定义结构体函数,修改函数print_two(),调用结构体函数:


af8efd0778baecb6d52b19e8cc148572.webp


这里没有什么问题,在本模块儿,初始化结构体,同时调用函数。


下面,修改lib_module_one::print_one()函数,同时也想初始化这个结构体,调用结构体的函数。


怎么做?


这里在lib_module_one::print_one()函数中直接初始化这个结构体肯定是不行的,因为这个结构体并不在自己的作用域内,肯定是要引入的,当然可通过绝对路径引入,如:


use crate::lib_module_two::LibModuleTwoStruct



这里想要通过使用相对路径,lib_module_two和lib_module_one是兄弟关系,所以只要引入到lib_module_two了,即很方便了。


看下树结构:


174da82fae4e1977cb9118884b642b29.webp

从上图可以看出,在lib_module_one中,需要引入LibModuleTwoStruct,即通过父级达到crate根,然后可以找到lib_module_two,那么下面就简单了:


79747da60fe451400ee8aac36c953fb0.webp

,然后lib_module_one,那么

li看回放b_module_one

这里使用了super关键字,找到父级,然后找到需要的模块,引入


综合上述代码,看下lib.rs


pub mod lib_module_one{    // use crate::lib_module_two::LibModuleTwoStruct;    use super::lib_module_two::LibModuleTwoStruct;    pub fn print_one(){        println!("print_one");        let lib_module_two_struct =  LibModuleTwoStruct{            name : String::from("lgli"),            age : 20        };        lib_module_two_struct.print_name();    }}
pub mod lib_module_two{ pub fn print_two(){ println!("print_two"); let lib_module_two_struct = LibModuleTwoStruct{ name : String::from("lgli"), age : 18 }; lib_module_two_struct.print_name(); } pub struct LibModuleTwoStruct { pub name:String, pub age : i8 } impl LibModuleTwoStruct{ pub fn print_name(&self){ println!("姓名:{},年龄:{}",self.name,self.age); } }}
pub fn print_one_and_two(){ lib_module_one::print_one(); lib_module_two::print_two();}
pub mod lib_module_one{ // use crate::lib_module_two::LibModuleTwoStruct; use super::lib_module_two::LibModuleTwoStruct; pub fn print_one(){ println!("print_one"); let lib_module_two_struct = LibModuleTwoStruct{ name : String::from("lgli"), age : 20 }; lib_module_two_struct.print_name(); }}
pub mod lib_module_two{ pub fn print_two(){ println!("print_two"); let lib_module_two_struct = LibModuleTwoStruct{ name : String::from("lgli"), age : 18 }; lib_module_two_struct.print_name(); } pub struct LibModuleTwoStruct { pub name:String, pub age : i8 } impl LibModuleTwoStruct{ pub fn print_name(&self){ println!("姓名:{},年龄:{}",self.name,self.age); } }}
pub fn print_one_and_two(){ lib_module_one::print_one(); lib_module_two::print_two();}



然后在二进制的crate中调用运行:


27d2f4330969f29f2b6e31f51fd8965e.webp


程序编译运行正常。


下面,在src目录下,新建一个out_module.rs文件,定义一个模块儿,包含一个say_hello函数:


2884136ed88f60923b5e76fa94272433.webp


这时候,在lib.rs中调用这个函数:


388cdaa6584fdc9fecf935ec3392e840.webp


编译运行:


fbd13e2442d3023f6f87a1dcc3871f22.webp


调用成功,即这个地方的out_module就只能是一个模块儿,而不能是一个crate库,即:一个包中最多只能包含一个库crate


假设某种情况下,out_module是一个比较大,同时又是一个独立的功能模块儿,需要建文件夹将其封装起来,这时候怎么弄?



Rust提供了一种类似桥接方式,首先,在src下新建文件夹:module_dir


1fcb02c4ec9481a644d075dacb87385e.webp


在module_dir中新建Rust文件:mod.rs、a.rs


b04d7aa72075ef421ab61e024d4645ff.webp


其中src/module_dir/mod.rs文件可以视为模块桥接,mod定义一个函数,调用a.rs的函数:


9e62fc13a0378805baf34b940821084c.webp


a.rs


6caf7f59139d004a2aa0df9343d294f4.webp



这时候,在根crate库中,调用这个module_dir模块


lib.rs修改:


c9260e2222123abe5f316da44e93b31e.webp


编译运行:


12ab8d9f6d39cd9123514956026e0c03.webp


桥接成功!!


一般来说,一个成熟的大型的项目,都会存在类似父项目中有子项目,在Rust中,也是可以的:


将上述Rust项目作为一个父项目,新建一个子项目:



56c84ac5fb0d5d67cc2e935d5ec20ad8.webp



3d91b07911ec8d6ab3f1b614623a163d.webp


这里选择新建二进制的Rust Crate,当然也是可以选择直接crate 库。


bbc601e73ddb363fc35422cfb939529b.webp



这时候,可以看到,这个新的inner_one是一个可以独立构建的Rust包项目

因为它有独立的Cargo.toml文件


上述步奏,如果新建一个crate库,则就是包含一个lib.rs文件,看下两者的区别:



5b7bc7addbf567f62d7faf83b4126b6a.webp


这里的inner_two是直接新建的一个crate库,这里也就直接的说明了,二进制的crate就是指main.rs,crate库就是lib.rs


然后下面要做的是在inner_two中引入父级包,然后调用父级包中的模块函数



首先在inner_two项目Cargo.toml文件中,添加父依赖:


first_large_project = { path = "../../../first-large-project" }


即:


c20becc18190242074cdaf68c4d46544.webp


first_large_project是父级项目构建名字,前面已经说了,在cargo.toml中修改了name,所以这里是first_large_project,这里的路径和前面的名字,是一定要对应上的



这时候就可以在inner_two中使用first_large_project中的crate库及其模块了



在inner_two的lib.rs文件中,引用模块,调用函数:


9fa0e4b13b67359fb8cfd2dfb44ada27.webp


下面在inner_one中,添加inner_two的依赖:


614f5ddc2b7df8111a31c3169833b5ae.webp


然后在main.rs中,引入inner_two,调用函数:



979c8296084a06eb1bb1925c0e6fba5f.webp


编译运行inner_one的main函数:


db862425023b8565e9b810605271b850.webp


因为inner_two实际调用的是

lib_module_two::print_two()函数,所以打印成功


这里这么弱智的绕来绕去操作,仅仅是为了了解引用依赖,及其二进制库和crate库的区别。


这里添加依赖的方式,是通过路径来添加的。当然也可以通过其他方式,如前面举例说到的git:


fb230088c0f693c94b603286cd206778.webp


这里需要记住的就是,前面的名字一定要和依赖Rust的构建名字一样!


这里有一个小问题,如果有2个依赖的项目名字一样了怎么办?


a944fcfbdedb50e65c30cd35ae129cbe.webp


如上图所示,这里添加了2个依赖,其都叫"rand",这个时候如何处理呢?



Cargo 提供了依赖重命名,即前面的这个名字,默认是依赖项目的构建名,可以重新命名,但是需要指定package


即:


f9e2ae5be870d1ec6771ca1ef69a80aa.webp


这个rand是在和inner_one同级目录下新建的一个crate库,仅仅包含了一个函数。:


90b05f2a21f0a9e77ce044634e9327a1.webp



这时候,在inner_one的二进制库中引入这个my_rand,同时调用其say_rand函数:


9c6fb2930d8f64512bdf8e895102e9ad.webp



总结一下前面说的内容


cargo.toml和cargo.lock


Rust3个重要约束?


二进制crate和crate库的区别?


module引入到二进制crate


module引入到crate库


use、mod、pub等关键字的使用


绝对路径和相对路径


cargo-dependencies重命名引包


上面这些问题,在本文中均是涵盖了的,如果没有什么印象了,可以回头看看!



点击下面公众号获取更多


欢迎点赞转发,谢谢

浏览 70
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报