懂你,更懂Rust系列之不斷增長(zhǎng)的Rust項(xiàng)目管理
簡(jiǎn)單愛,你心所愛
世界也變得大了起來
所有花都為你開
所有景物也為了你安排
我們是如此的不同
肯定前世就已經(jīng)深愛過
講好了這一輩子
再度重相逢
前面所分享到的所有的Rust相關(guān)的代碼,都是在一個(gè)文件中的,那么當(dāng)在實(shí)際的開發(fā)過程中,肯定是涉及到很多很復(fù)雜的代碼量的,這個(gè)時(shí)候,就涉及到公共函數(shù)的封裝,接口調(diào)用等等關(guān)聯(lián)的東西,那么今天就給大家?guī)黻P(guān)于Rust項(xiàng)目的實(shí)際工作中的,包、模塊管理。
首先,看下新建一個(gè)名為first-large-project的Rust項(xiàng)目
其結(jié)構(gòu)如下:

新建的一個(gè)Rust項(xiàng)目,包含一個(gè)src文件夾,下面有一個(gè)main.rs文件,此外還有Cargo.lock和Cargo.toml文件
首先先來了解下Cargo.lock和Cargo.toml
下面是2個(gè)文件內(nèi)容的對(duì)比:

Cargo.lock:注釋描述中說到,該文件是Cargo自動(dòng)生成的,同時(shí)不是手動(dòng)手動(dòng)編輯的,言下之意就是,我們不需要去手動(dòng)修改這個(gè)文件。那么這個(gè)文件究竟是干啥用的呢?
Cargo.lock?contains exact information about your dependencies. It is maintained by Cargo and should not be manually edited.
這里是官方對(duì)該文件的解釋:包含項(xiàng)目有關(guān)依賴項(xiàng)的確切信息
即我們可以這么理解,這個(gè)文件用于記載項(xiàng)目的所有依賴的詳細(xì)信息
Cargo.toml:這里可以看到,描述了本項(xiàng)目的基本信息,同時(shí)下面有一個(gè)
[dependencies],即這里可以給項(xiàng)目添加依賴。
那么我們嘗試添加一個(gè)依賴:

這里添加了一個(gè)rand庫,等待項(xiàng)目構(gòu)建完成,這時(shí)候可以看看Cargo.lock文件:

上圖可以看見,Cargo.lock文件記錄了更多的依賴信息,而不是像Cargo.toml文件中記載的那一個(gè)依賴了。
然后可以通過依賴的git地址,發(fā)現(xiàn)rand項(xiàng)目所有依賴的庫和rand包含的子項(xiàng)目也都在我們的Cargo.lock文件中有所記載。
這里顯然,Cargo.lock就是對(duì)Cargo.toml依賴的一個(gè)詳細(xì)說明。
接下來,先明確幾個(gè)概念:
crate
crate:a library or executable program is called a?crate?.
上面是Rust官方給出的一個(gè)解釋,一個(gè)庫或者可執(zhí)行的程序,被稱為crate
包<package>
包:是提供一系列功能的一個(gè)或者多個(gè)crate,即包=crate+crate+...+crate
一個(gè)包會(huì)包含一個(gè)Cargo.toml文件,描述如何構(gòu)建包所包含的所有crate
那么其實(shí)上面例子中的這個(gè)first-large-project就是一個(gè)包
然后回頭看下上面的例子,還剩下一個(gè)src/main.rs文件。Cargo遵循了一個(gè)約定:src/main.rs?就是一個(gè)與包同名的二進(jìn)制 crate 的 crate 根,那么這里的src/main.rs就是一個(gè)二進(jìn)制的crate
下面有3點(diǎn)重要的約束:
1、一個(gè)包中最多只能包含一個(gè)庫crate
2、一個(gè)包中可以有多個(gè)二進(jìn)制crate
3、一個(gè)包中至少包含一個(gè)crate
以上三點(diǎn)約束是很重要很重要的,在之后構(gòu)建大型的Rust項(xiàng)目,這是一個(gè)必須掌握的,否則,我們連如果建Rust文件都搞不定,總不能把所有代碼都寫在一個(gè)文件吧!
結(jié)合上面的例子,接著看這個(gè)first-large-project,可以看到目前這個(gè)項(xiàng)目只有一個(gè)src/main.rs文件,上面也講過了,這個(gè)main.rs文件其實(shí)就是和我們項(xiàng)目同名的二進(jìn)制crate根。
那么這就滿足了3點(diǎn)約束中的第3條:一個(gè)包中至少包含一個(gè)crate,因?yàn)槿绻诎堰@個(gè)main.rs去掉,這也就不是一個(gè)程序了,Rust也無法編譯構(gòu)建了
那么看3點(diǎn)約束中的第2條:一個(gè)包中可以有多個(gè)二進(jìn)制crate,那么這個(gè)怎么做呢?
這時(shí)候,在src目錄下,新建一個(gè)main_other.rs

然后定義main函數(shù),這時(shí)候,可以發(fā)現(xiàn),雖然這里也定義了main方法,但是可以看到,這個(gè)main函數(shù)是不可被Rust編譯后可以運(yùn)行的主函數(shù)mian,
對(duì)比這2個(gè)main函數(shù):

后者沒有run標(biāo)識(shí),這就說明,這個(gè)main_other.rs不是一個(gè)二進(jìn)制的crate,僅僅是一個(gè)普通的Rust文件,在此時(shí),先透露一下,這個(gè)main_other.rs被稱為module。
我們?cè)谶@個(gè)main_other.rs的main函數(shù)前面加一個(gè)pub關(guān)鍵字,然后就可以在main.rs中調(diào)用了。

修改main.rs

編譯運(yùn)行:

這里使用mod關(guān)鍵字,將與main.rs同級(jí)目錄下的main_other模塊兒<module>引入進(jìn)來,然后調(diào)用main函數(shù)。
main_other.rs的main函數(shù)為啥需要加一個(gè)pub呢?
在Rust中,默認(rèn)的所有函數(shù)或者模塊或者結(jié)構(gòu)體、枚舉等,都是private的,即只能自己或者同級(jí)調(diào)用,不可被其他地方使用,如果需要被其他地方調(diào)用,需要將其設(shè)置為公開的,即:pub
比如,假設(shè)去掉pub
編譯報(bào)錯(cuò)

那么有人可能會(huì)問,在main.rs中,是如何使用mod,將main_other引入到main.rs中的呢?
這里,是因?yàn)?,在Rust中,src/main.rs為與包同名的二進(jìn)制crate根,這里的src/main_other.rs,為名為main_other<注,模塊名與文件同名>的crate根模塊,即兩者是平級(jí)的:

所以,在main.rs中,可以直接使用mod,引入,但是由于main_other.rs中的main函數(shù)是私有的,所以在main.rs中,不可使用,所以報(bào)錯(cuò)。
所以必須要改為pub
此外,這里有一個(gè)比較重要的,容易出錯(cuò)的地方,那就是二進(jìn)制的crate庫,只能在當(dāng)前目錄下,通過mod方式引入module。至少當(dāng)前版本是這樣子的。
這個(gè)地方跑得有些遠(yuǎn)了,這里需要實(shí)現(xiàn)擁有多個(gè)二進(jìn)制crate庫,
那么現(xiàn)在一個(gè)目的就是如何將這個(gè)main_other.rs編程二進(jìn)制crate庫,因?yàn)楝F(xiàn)在它是個(gè)module
在src下新建一個(gè)bin目錄,然后將這個(gè)main_other.rs移動(dòng)進(jìn)去看看會(huì)發(fā)生什么?

這時(shí)候,可以看見這個(gè)main_other.rs也變成了一個(gè)二進(jìn)制的crate庫
即:一個(gè)包中可以有多個(gè)二進(jìn)制crate
接下來分析第一個(gè):一個(gè)包中最多只能包含一個(gè)庫crate
回到項(xiàng)目的初始狀態(tài):

這時(shí)候,在src下新建一個(gè)lib.rs

Rust約定:這個(gè)lib.rs就是一個(gè)和項(xiàng)目同名的crate庫
在lib.rs中,定義兩個(gè)模塊兒:

這里描述下,在Rust中,模塊兒定義方式:
mod 關(guān)鍵字然后一對(duì)大括號(hào),將模塊兒內(nèi)容寫入其中,模塊兒中,可以定義方法、結(jié)構(gòu)體、枚舉等。因?yàn)橄旅嫖覀冃枰{(diào)用這個(gè)lib.rs中的內(nèi)容,所以全部定義為pub的。
然后在二進(jìn)制文件中調(diào)用這個(gè)lib.rs

這里可以看見,在二進(jìn)制的Rust文件中,調(diào)用crate庫,需要用use + crate庫名,但是我這里用的是:use first_large_project;
但是我們前面說到,Rust約定crate應(yīng)該是與包同名,這里的crate雖然不是二進(jìn)制的,難道就不是與包同名了么?
顯然不是這樣的,不管是不是二進(jìn)制的crate,都是與包同名,但是因?yàn)檫@里寫上use first-large-project;Rust解析會(huì)報(bào)錯(cuò):

因?yàn)楹完P(guān)鍵字沖突了,所以不能用-,這時(shí)候就提了一個(gè)醒,Rust包命名的時(shí)候,盡量不要用-,但是如果已經(jīng)用了怎么辦?比如上面我們這個(gè)項(xiàng)目??梢酝ㄟ^修改Cargo.toml文件的項(xiàng)目構(gòu)建信息來改變:
比如這里:

然后包的crate庫就變成了,first_large_project
所以就有了前面使用use first_large_project;將crate庫引入crate二進(jìn)制文件中。
在lib.rs文件中,再定義一個(gè)函數(shù),函數(shù)調(diào)用
lib_module_one.print_one和lib_module_two.print_two
函數(shù)

這里,可以看到直接通過
lib_module_one::print_one()
或者lib_module_two::print_two()
完成了函數(shù)調(diào)用
這是Rust語言中,通過相對(duì)路徑對(duì)模塊的引入,因?yàn)檫@里lib_module_one和print_one_and_two函數(shù)同屬一個(gè)層級(jí),即兩者是兄弟關(guān)系,所以可以直接通過相對(duì)路徑調(diào)用,當(dāng)然也可以通過絕對(duì)路徑來調(diào)用:
比如:

絕對(duì)路徑,即通過關(guān)鍵字crate : : +module路徑來表示
?在二進(jìn)制crate庫中調(diào)用下:

下面嘗試在lib_module_two中定義一個(gè)結(jié)構(gòu)體,同時(shí)定義結(jié)構(gòu)體函數(shù),修改函數(shù)print_two(),調(diào)用結(jié)構(gòu)體函數(shù):

這里沒有什么問題,在本模塊兒,初始化結(jié)構(gòu)體,同時(shí)調(diào)用函數(shù)。
下面,修改lib_module_one::print_one()函數(shù),同時(shí)也想初始化這個(gè)結(jié)構(gòu)體,調(diào)用結(jié)構(gòu)體的函數(shù)。
怎么做?
這里在lib_module_one::print_one()函數(shù)中直接初始化這個(gè)結(jié)構(gòu)體肯定是不行的,因?yàn)檫@個(gè)結(jié)構(gòu)體并不在自己的作用域內(nèi),肯定是要引入的,當(dāng)然可通過絕對(duì)路徑引入,如:
use crate::lib_module_two::LibModuleTwoStruct
這里想要通過使用相對(duì)路徑,lib_module_two和lib_module_one是兄弟關(guān)系,所以只要引入到lib_module_two了,即很方便了。
看下樹結(jié)構(gòu):

從上圖可以看出,在lib_module_one中,需要引入LibModuleTwoStruct,即通過父級(jí)達(dá)到crate根,然后可以找到lib_module_two,那么下面就簡(jiǎn)單了:

,然后lib_module_one,那么
li看回放b_module_one
這里使用了super關(guān)鍵字,找到父級(jí),然后找到需要的模塊,引入
綜合上述代碼,看下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();}
然后在二進(jìn)制的crate中調(diào)用運(yùn)行:

程序編譯運(yùn)行正常。
下面,在src目錄下,新建一個(gè)out_module.rs文件,定義一個(gè)模塊兒,包含一個(gè)say_hello函數(shù):

這時(shí)候,在lib.rs中調(diào)用這個(gè)函數(shù):

編譯運(yùn)行:

調(diào)用成功,即這個(gè)地方的out_module就只能是一個(gè)模塊兒,而不能是一個(gè)crate庫,即:一個(gè)包中最多只能包含一個(gè)庫crate
假設(shè)某種情況下,out_module是一個(gè)比較大,同時(shí)又是一個(gè)獨(dú)立的功能模塊兒,需要建文件夾將其封裝起來,這時(shí)候怎么弄?
Rust提供了一種類似橋接方式,首先,在src下新建文件夾:module_dir

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

其中src/module_dir/mod.rs文件可以視為模塊橋接,mod定義一個(gè)函數(shù),調(diào)用a.rs的函數(shù):

a.rs

這時(shí)候,在根crate庫中,調(diào)用這個(gè)module_dir模塊
lib.rs修改:

編譯運(yùn)行:

橋接成功??!
一般來說,一個(gè)成熟的大型的項(xiàng)目,都會(huì)存在類似父項(xiàng)目中有子項(xiàng)目,在Rust中,也是可以的:
將上述Rust項(xiàng)目作為一個(gè)父項(xiàng)目,新建一個(gè)子項(xiàng)目:


這里選擇新建二進(jìn)制的Rust Crate,當(dāng)然也是可以選擇直接crate 庫。

這時(shí)候,可以看到,這個(gè)新的inner_one是一個(gè)可以獨(dú)立構(gòu)建的Rust包項(xiàng)目
因?yàn)樗歇?dú)立的Cargo.toml文件
上述步奏,如果新建一個(gè)crate庫,則就是包含一個(gè)lib.rs文件,看下兩者的區(qū)別:

這里的inner_two是直接新建的一個(gè)crate庫,這里也就直接的說明了,二進(jìn)制的crate就是指main.rs,crate庫就是lib.rs
然后下面要做的是在inner_two中引入父級(jí)包,然后調(diào)用父級(jí)包中的模塊函數(shù)
首先在inner_two項(xiàng)目Cargo.toml文件中,添加父依賴:
first_large_project = { path = "../../../first-large-project" }即:

first_large_project是父級(jí)項(xiàng)目構(gòu)建名字,前面已經(jīng)說了,在cargo.toml中修改了name,所以這里是first_large_project,這里的路徑和前面的名字,是一定要對(duì)應(yīng)上的
這時(shí)候就可以在inner_two中使用first_large_project中的crate庫及其模塊了
在inner_two的lib.rs文件中,引用模塊,調(diào)用函數(shù):

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

然后在main.rs中,引入inner_two,調(diào)用函數(shù):

編譯運(yùn)行inner_one的main函數(shù):

因?yàn)閕nner_two實(shí)際調(diào)用的是
lib_module_two::print_two()函數(shù),所以打印成功
這里這么弱智的繞來繞去操作,僅僅是為了了解引用依賴,及其二進(jìn)制庫和crate庫的區(qū)別。
這里添加依賴的方式,是通過路徑來添加的。當(dāng)然也可以通過其他方式,如前面舉例說到的git:

這里需要記住的就是,前面的名字一定要和依賴Rust的構(gòu)建名字一樣!
這里有一個(gè)小問題,如果有2個(gè)依賴的項(xiàng)目名字一樣了怎么辦?

如上圖所示,這里添加了2個(gè)依賴,其都叫"rand",這個(gè)時(shí)候如何處理呢?
Cargo?提供了依賴重命名,即前面的這個(gè)名字,默認(rèn)是依賴項(xiàng)目的構(gòu)建名,可以重新命名,但是需要指定package
即:

這個(gè)rand是在和inner_one同級(jí)目錄下新建的一個(gè)crate庫,僅僅包含了一個(gè)函數(shù)。:

這時(shí)候,在inner_one的二進(jìn)制庫中引入這個(gè)my_rand,同時(shí)調(diào)用其say_rand函數(shù):

總結(jié)一下前面說的內(nèi)容
cargo.toml和cargo.lock
Rust3個(gè)重要約束?
二進(jìn)制crate和crate庫的區(qū)別?
module引入到二進(jìn)制crate
module引入到crate庫
use、mod、pub等關(guān)鍵字的使用
絕對(duì)路徑和相對(duì)路徑
cargo-dependencies重命名引包
上面這些問題,在本文中均是涵蓋了的,如果沒有什么印象了,可以回頭看看!
點(diǎn)擊下面公眾號(hào)獲取更多
歡迎點(diǎn)贊轉(zhuǎn)發(fā),謝謝
