Rust入坑指南:有條不紊

Rust入坑指南目錄:


Rust入坑指南:有條不紊


隨著我們的坑越來越多,越來越大,我們必須要對各種坑進行管理了。Rust為我們提供了一套坑務管理系統,方便大家有條不紊的尋找、管理、填埋自己的各種坑。

Rust提供給我們一些管理代碼的特性:

  • Packages: Cargo的一個特性,幫助你進行構建、測試和共享crates
  • Crates: 生成庫或可執行文件的模塊樹
  • Modulesuse: 用於控制代碼組織、範圍和隱私路徑
  • Paths: struct、function和module的命名方法

下面我們來具體看一下這些特性是如何幫助我們組織代碼的。

Packages和Crates

package可以理解為一個項目,而crate可以理解為一個代碼庫。crate可以供多個項目使用。那我們的項目中package和crate是怎麼定義的呢?

之前我們總是通過IDEA來新建項目,今天我們換個方法,在命令行中使用cargo命令來創建。

<code>$ cargo new hello-world
Created binary (application) `hello-world` package
$ ls hello-world
Cargo.toml
src
$ ls hello-world/src
main.rs
複製代碼/<code>

可以看到,我們使用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

Module幫助我們在crate中組織代碼,同時Module也是封裝代碼的重要工具。接下來還是通過一個栗子來詳細瞭解Module。

前面我們說過,庫crate定義在src/lib.rs文件中。這裡首先創建一個包含了庫crate的package:

<code>cargo new --lib restaurant
複製代碼/<code>

然後在src中定義一些module和函數。

<code>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() {}
}
}
複製代碼/<code>

可以看到我們使用關鍵字mod來定義Module,Module中可以繼續定義Module或函數。這樣我們就可以比較方便的把相關的函數放到一個Module中,併為Module命名,提高代碼的可讀性。另外Module中還可以定義struct和枚舉。由於Module中可以嵌套定義子Module,最終我們定義出來的代碼類似一個樹形。

那麼如何訪問Module中的函數呢?這就要提到Path了。這部分比較好理解,Module樹相當於系統文件目錄,而Path則是目錄的路徑。

Path

這裡的路徑和系統文件路徑一樣,都分為相對路徑和絕對路徑兩種。其中絕對路徑必須以crate開頭,因為它代碼整個Module樹的根節點。路徑之間使用的是雙冒號來表示引用。

現在我來嘗試在一個函數中調用add_to_waitlist函數:


Rust入坑指南:有條不紊


可以看到這裡不管用絕對路徑還是相對路徑都報錯了,錯誤信息是模塊hosting和函數add_to_waitlist是私有(private)的。我們先暫時放下這個錯誤,根據這裡的錯誤提示,我們知道了當我們定義一個module時,默認情況下是私有的,我們可以通過這種方法來封裝一些代碼的實現細節。

OK,回到剛才的問題,那我們怎麼才能解決這個錯誤呢?地球人都知道應該把對應的模塊與函數公開出來。Rust中標識模塊或函數為公有的關鍵字是pub。

我們用pub關鍵字來把對應的模塊和函數公開


Rust入坑指南:有條不紊


這樣我們就可以在module外來調用module內的函數了。

Rust中的私有規則

現在我們再回過頭來看Rust中的一些私有規則,如果你試驗了上面的例子,也許會有一些發現。

Rust中私有規則適用於所有項(函數、方法、結構體、枚舉、模塊和常量),它們默認都是私有的。父模塊中的項不能訪問子模塊中的私有項,而子模塊中的項可以訪問其祖輩(父模塊及以上)中的項。

Struct和Enum的私有性

Struct和Enum的私有性略有不同,對於Struct來講,我可以只將其中的某些字段設置為公有的,其他字段可以仍然保持私有。

<code>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);
}
複製代碼/<code>

而對於Enum,如果一個Enum是公有的,那麼它的所有值都是公有的,因為私有的值沒有意義。

相對路徑和絕對路徑的選擇

這種選擇不存在正確與否,只有是否合適。因此這裡我們只是舉例說明一些合適的情況。

我們仍以上述代碼為例,如果我們可以預見到以後需要把front_of_house模塊和eat_at_restaurant函數移動到一個新的名為customer_experience的模塊中,就應該使用相對路徑,這樣我們就對其進行調整。

類似的,如果我們需要把eat_at_restaurant函數移動到dining模塊中,那麼我們選擇絕對路徑的話就不需要做調整。

綜上,我們需要對代碼的優化方向有一些前瞻性,並以此來判斷需要使用相對路徑還是絕對路徑。

相對路徑除了以當前模塊開頭外,還可以以super開頭。它表示的是父級模塊,類似於文件系統中的兩個點(..)。

use關鍵字

絕對路徑和相對路徑可以幫助我們找到指定的函數,但用起來也非常的麻煩,每次都要寫一大長串路徑。還好Rust為我們提供了use關鍵字。在很多語言中都有import關鍵字,這裡的use就有些類似於import。不過Rust會提供更加豐富的用法。

use最基本的用法就是引入一個路徑。我們就可以更加方便的使用這個路徑下的一些方法:

<code>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();
}
複製代碼/<code>

這個路徑可以是絕對路徑,也可以是相對路徑,但如果是相對路徑,就必須要以self開頭。上面的例子可以寫成:

<code>use self::front_of_house::hosting;
複製代碼/<code>

這與我們前面講的相對路徑似乎有些矛盾,Rust官方說會在之後的版本處理這個問題。

use還可以更進一步,直接指向具體的函數或Struct或Enum。但習慣上我們使用函數時,use後面使用的是路徑,這樣可以在調用函數時知道它屬於哪個模塊;而在使用Struct/Enum時,則具體指向它們。當然,這只是官方建議的編程習慣,你也可以有自己的習慣,不過最好還是按照官方推薦或者是項目約定的規範比較好。

對於同一路徑下的某些子模塊,在引入時可以合併為一行,例如:

<code>use std::io;
use std::cmp::Ordering;
// 等價於
use std::{cmp::Ordering, io};
複製代碼/<code>

有時我們還會遇到引用不同包下相同名稱Struct的情況,這時有兩種解決辦法,一是不指定到具體的Struct,在使用時加上不同的路徑;二是使用as關鍵字,為Struct起一個別名。

方法一:

<code>use std::fmt;
use std::io;

fn function1() -> fmt::Result {
// --snip--
}

fn function2() -> io::Result {
// --snip--
}
複製代碼/<code>

方法二:

<code>use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
// --snip--
}

fn function2() -> IoResult {
// --snip--
}

複製代碼/<code>

如果要導入某個路徑下的全部模塊或函數,可以使用*來表示。當然我是非常不建議使用這種方法的,因為導入全部的話,如果出現名稱衝突就會很難排查問題。

對於外部的依賴包,我們需要先在Cargo.toml文件中添加依賴,然後就可以在代碼中使用use來引入依賴庫中的路徑。Rust提供了一些標準庫,即std下的庫。在使用這些標準庫時是不需要添加依賴的。

有些同學看到這裡可能要開始抱怨了,說好了介紹怎麼拆分文件,到現在還是在一個文件裡玩,這不是欺騙讀者嘛。

別急,這就開始拆分。

開始拆分

我們拿剛才的一段代碼為例

<code>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() {}
}
}


複製代碼/<code>

首先我們可以把front_of_house模塊下的內容拆分出去,需要在src目錄下新建一個front_of_house.rs文件,然後把front_of_house模塊下的內容寫到文件中。lib.rs文件中,只需要聲明front_of_house模塊即可,不需要具體的定義。聲明模塊時,將花括號即內容改為分號就可以了。

<code>mod front_of_house;
複製代碼/<code>

然後我們可以繼續拆分front_of_house模塊下的hosting模塊和serving模塊,這時需要新建一個名為front_of_house的文件件,在該文件夾下放置要拆分的模塊的同名文件,把模塊定義的內容寫在文件中,front_of_house.rs文件同樣只保留聲明即可。

拆分後的文件目錄如圖


Rust入坑指南:有條不紊


本文主要講了Rust中Package、Crate、Module、Path的概念和用法,有了這些基礎,我們後面才有可能開發一些比較大的項目。


分享到:


相關文章: