隨着咱們的坑愈來愈多,愈來愈大,咱們必需要對各類坑進行管理了。Rust爲咱們提供了一套坑務管理系統,方便你們有條不紊的尋找、管理、填埋本身的各類坑。html
Rust提供給咱們一些管理代碼的特性:編程
下面咱們來具體看一下這些特性是如何幫助咱們組織代碼的。bash
package能夠理解爲一個項目,而crate能夠理解爲一個代碼庫。crate能夠供多個項目使用。那咱們的項目中package和crate是怎麼定義的呢?app
以前咱們老是經過IDEA來新建項目,今天咱們換個方法,在命令行中使用cargo命令來建立。函數
$ cargo new hello-world
Created binary (application) `hello-world` package
$ ls hello-world
Cargo.toml
src
$ ls hello-world/src
main.rs
複製代碼
能夠看到,咱們使用cargo建立項目後,只有兩個文件,Cargo.toml和src目錄下的main.rs。工具
Cargo.toml是管理項目依賴的文件,每一個Cargo.toml定義一個package。main.rs文件的存在表示package中包含一個二進制crate,它是二進制crate的入口文件,crate的名稱和package相同。若是src目錄下存在lib.rs文件,說明package中包含一個和package名稱相同的庫crate。測試
一個package能夠包含多個二進制crate,它們由src/lib目錄下的文件定義。若是你的項目想引用他人的crate,能夠在Cargo.toml文件中增長依賴。每一個crate都有本身的命名空間,所以若是你引入了一個crate裏面定義了一個名爲hello的函數,你仍然能夠在本身的crate中再定義一個名爲hello的函數。優化
Module幫助咱們在crate中組織代碼,同時Module也是封裝代碼的重要工具。接下來仍是經過一個栗子來詳細瞭解Module。ui
前面咱們說過,庫crate定義在src/lib.rs文件中。這裏首先建立一個包含了庫crate的package:spa
cargo new --lib restaurant
複製代碼
而後在src中定義一些module和函數。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
複製代碼
能夠看到咱們使用關鍵字mod
來定義Module,Module中能夠繼續定義Module或函數。這樣咱們就能夠比較方便的把相關的函數放到一個Module中,併爲Module命名,提升代碼的可讀性。另外Module中還能夠定義struct和枚舉。因爲Module中能夠嵌套定義子Module,最終咱們定義出來的代碼相似一個樹形。
那麼如何訪問Module中的函數呢?這就要提到Path了。這部分比較好理解,Module樹至關於系統文件目錄,而Path則是目錄的路徑。
這裏的路徑和系統文件路徑同樣,都分爲相對路徑和絕對路徑兩種。其中絕對路徑必須以crate
開頭,由於它代碼整個Module樹的根節點。路徑之間使用的是雙冒號來表示引用。
如今我來嘗試在一個函數中調用add_to_waitlist函數:
能夠看到這裏無論用絕對路徑仍是相對路徑都報錯了,錯誤信息是模塊hosting和函數add_to_waitlist是私有(private)的。咱們先暫時放下這個錯誤,根據這裏的錯誤提示,咱們知道了當咱們定義一個module時,默認狀況下是私有的,咱們能夠經過這種方法來封裝一些代碼的實現細節。
OK,回到剛纔的問題,那咱們怎麼才能解決這個錯誤呢?地球人都知道應該把對應的模塊與函數公開出來。Rust中標識模塊或函數爲公有的關鍵字是pub
。
咱們用pub關鍵字來把對應的模塊和函數公開
這樣咱們就能夠在module外來調用module內的函數了。
如今咱們再回過頭來看Rust中的一些私有規則,若是你試驗了上面的例子,也許會有一些發現。
Rust中私有規則適用於全部項(函數、方法、結構體、枚舉、模塊和常量),它們默認都是私有的。父模塊中的項不能訪問子模塊中的私有項,而子模塊中的項能夠訪問其祖輩(父模塊及以上)中的項。
Struct和Enum的私有性略有不一樣,對於Struct來說,我能夠只將其中的某些字段設置爲公有的,其餘字段能夠仍然保持私有。
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
}
複製代碼
而對於Enum,若是一個Enum是公有的,那麼它的全部值都是公有的,由於私有的值沒有意義。
這種選擇不存在正確與否,只有是否合適。所以這裏咱們只是舉例說明一些合適的狀況。
咱們仍以上述代碼爲例,若是咱們能夠預見到之後須要把front_of_house模塊和eat_at_restaurant函數移動到一個新的名爲customer_experience的模塊中,就應該使用相對路徑,這樣咱們就對其進行調整。
相似的,若是咱們須要把eat_at_restaurant函數移動到dining模塊中,那麼咱們選擇絕對路徑的話就不須要作調整。
綜上,咱們須要對代碼的優化方向有一些前瞻性,並以此來判斷須要使用相對路徑仍是絕對路徑。
相對路徑除了以當前模塊開頭外,還能夠以super開頭。它表示的是父級模塊,相似於文件系統中的兩個點(..
)。
絕對路徑和相對路徑能夠幫助咱們找到指定的函數,但用起來也很是的麻煩,每次都要寫一大長串路徑。還好Rust爲咱們提供了use關鍵字。在不少語言中都有import關鍵字,這裏的use就有些相似於import。不過Rust會提供更加豐富的用法。
use最基本的用法就是引入一個路徑。咱們就能夠更加方便的使用這個路徑下的一些方法:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
複製代碼
這個路徑能夠是絕對路徑,也能夠是相對路徑,但若是是相對路徑,就必需要以self開頭。上面的例子能夠寫成:
use self::front_of_house::hosting;
複製代碼
這與咱們前面講的相對路徑彷佛有些矛盾,Rust官方說會在以後的版本處理這個問題。
use還能夠更進一步,直接指向具體的函數或Struct或Enum。但習慣上咱們使用函數時,use後面使用的是路徑,這樣能夠在調用函數時知道它屬於哪一個模塊;而在使用Struct/Enum時,則具體指向它們。固然,這只是官方建議的編程習慣,你也能夠有本身的習慣,不過最好仍是按照官方推薦或者是項目約定的規範比較好。
對於同一路徑下的某些子模塊,在引入時能夠合併爲一行,例如:
use std::io;
use std::cmp::Ordering;
// 等價於
use std::{cmp::Ordering, io};
複製代碼
有時咱們還會遇到引用不一樣包下相同名稱Struct的狀況,這時有兩種解決辦法,一是不指定到具體的Struct,在使用時加上不一樣的路徑;二是使用as
關鍵字,爲Struct起一個別名。
方法一:
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
複製代碼
方法二:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
複製代碼
若是要導入某個路徑下的所有模塊或函數,可使用*
來表示。固然我是很是不建議使用這種方法的,由於導入所有的話,若是出現名稱衝突就會很難排查問題。
對於外部的依賴包,咱們須要先在Cargo.toml文件中添加依賴,而後就能夠在代碼中使用use來引入依賴庫中的路徑。Rust提供了一些標準庫,即std下的庫。在使用這些標準庫時是不須要添加依賴的。
有些同窗看到這裏可能要開始抱怨了,說好了介紹怎麼拆分文件,到如今仍是在一個文件裏玩,這不是欺騙讀者嘛。
別急,這就開始拆分。
咱們拿剛纔的一段代碼爲例
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
複製代碼
首先咱們能夠把front_of_house模塊下的內容拆分出去,須要在src目錄下新建一個front_of_house.rs文件,而後把front_of_house模塊下的內容寫到文件中。lib.rs文件中,只須要聲明front_of_house模塊便可,不須要具體的定義。聲明模塊時,將花括號即內容改成分號就能夠了。
mod front_of_house;
複製代碼
而後咱們能夠繼續拆分front_of_house模塊下的hosting模塊和serving模塊,這時須要新建一個名爲front_of_house的文件件,在該文件夾下放置要拆分的模塊的同名文件,把模塊定義的內容寫在文件中,front_of_house.rs文件一樣只保留聲明便可。
拆分後的文件目錄如圖
本文主要講了Rust中Package、Crate、Module、Path的概念和用法,有了這些基礎,咱們後面纔有可能開發一些比較大的項目。
ps:本文的代碼示例均來自the book。