Rust 組織管理

任何一門編程語言如果不能組織代碼都是難以深入的,幾乎沒有一個軟件產品是由一個源文件編譯而成的。

本教程到目前為止所有的程序都是在一個文件中編寫的,主要是為了方便學習 Rust 語言的語法和概念。

對于一個工程來講,組織代碼是十分重要的。

Rust 中有三和重要的組織概念:箱、包、模塊。

箱(Crate)

"箱"是二進制程序文件或者庫文件,存在于"包"中。

"箱"是樹狀結構的,它的樹根是編譯器開始運行時編譯的源文件所編譯的程序。

注意:"二進制程序文件"不一定是"二進制可執行文件",只能確定是是包含目標機器語言的文件,文件格式隨編譯環境的不同而不同。

包(Package)

當我們使用 Cargo 執行 new 命令創建 Rust 工程時,工程目錄下會建立一個 Cargo.toml 文件。工程的實質就是一個包,包必須由一個 Cargo.toml 文件來管理,該文件描述了包的基本信息以及依賴項。

一個包最多包含一個庫"箱",可以包含任意數量的二進制"箱",但是至少包含一個"箱"(不管是庫還是二進制"箱")。

當使用 cargo new 命令創建完包之后,src 目錄下會生成一個 main.rs 源文件,Cargo 默認這個文件為二進制箱的根,編譯之后的二進制箱將與包名相同。

模塊(Module)

對于一個軟件工程來說,我們往往按照所使用的編程語言的組織規范來進行組織,組織模塊的主要結構往往是樹。Java 組織功能模塊的主要單位是類,而 JavaScript 組織模塊的主要方式是 function。

這些先進的語言的組織單位可以層層包含,就像文件系統的目錄結構一樣。Rust 中的組織單位是模塊(Module)。

mod nation {
    mod government {
        fn govern() {}
    }
    mod congress {
        fn legislate() {}
    }
    mod court {
        fn judicial() {}
    }
}

這是一段描述法治國家的程序:國家(nation)包括政府(government)、議會(congress)和法院(court),分別有行政、立法和司法的功能。我們可以把它轉換成樹狀結構:

nation
 ├── government
 │ └── govern
 ├── congress
 │ └── legislate
 └── court
   └── judicial

在文件系統中,目錄結構往往以斜杠在路徑字符串中表示對象的位置,Rust 中的路徑分隔符是 ::

路徑分為絕對路徑和相對路徑。絕對路徑從 crate 關鍵字開始描述。相對路徑從 self 或 super 關鍵字或一個標識符開始描述。例如:

crate::nation::government::govern();

是描述 govern 函數的絕對路徑,相對路徑可以表示為:

nation::government::govern();

現在你可以嘗試在一個源程序里定義類似的模塊結構并在主函數中使用路徑。

如果你這樣做,你一定會發現它不正確的地方:government 模塊和其中的函數都是私有(private)的,你不被允許訪問它們。


訪問權限

Rust 中有兩種簡單的訪問權:公共(public)和私有(private)。

默認情況下,如果不加修飾符,模塊中的成員訪問權將是私有的。

如果想使用公共權限,需要使用 pub 關鍵字。

對于私有的模塊,只有在與其平級的位置或下級的位置才能訪問,不能從其外部訪問。

實例

mod nation {
    pub mod government {
        pub fn govern() {}
    }

    mod congress {
        pub fn legislate() {}
    }
   
    mod court {
        fn judicial() {
            super::congress::legislate();
        }
    }
}

fn main() {
    nation::government::govern();
}

這段程序是能通過編譯的。請注意觀察 court 模塊中 super 的訪問方法。

如果模塊中定義了結構體,結構體除了其本身是私有的以外,其字段也默認是私有的。所以如果想使用模塊中的結構體以及其字段,需要 pub 聲明:

實例

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() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);
}
fn main() {
    eat_at_restaurant()
}

運行結果:

I'd like Wheat toast please

枚舉類枚舉項可以內含字段,但不具備類似的性質:

實例

mod SomeModule {
    pub enum Person {
        King {
            name: String
        },
        Quene
    }
}

fn main() {
    let person = SomeModule::Person::King{
        name: String::from("Blue")
    };
    match person {
        SomeModule::Person::King {name} => {
            println!("{}", name);
        }
        _ => {}
    }
}

運行結果:

Blue

難以發現的模塊

使用過 Java 的開發者在編程時往往非常討厭最外層的 class 塊——它的名字與文件名一模一樣,因為它就表示文件容器,盡管它很繁瑣但我們不得不寫一遍來強調"這個類是文件所包含的類"。

不過這樣有一些好處:起碼它讓開發者明明白白的意識到了類包裝的存在,而且可以明確的描述類的繼承關系。

在 Rust 中,模塊就像是 Java 中的類包裝,但是文件一開頭就可以寫一個主函數,這該如何解釋呢?

每一個 Rust 文件的內容都是一個"難以發現"的模塊。

讓我們用兩個文件來揭示這一點:

main.rs 文件

// main.rs
mod second_module;

fn main() {
    println!("This is the main module.");
    println!("{}", second_module::message());
}

second_module.rs 文件

// second_module.rs
pub fn message() -> String {
    String::from("This is the 2nd module.")
}

運行結果:

This is the main module.
This is the 2nd module.

use 關鍵字

use 關鍵字能夠將模塊標識符引入當前作用域:

實例

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}

use crate::nation::government::govern;

fn main() {
    govern();
}

這段程序能夠通過編譯。

因為 use 關鍵字把 govern 標識符導入到了當前的模塊下,可以直接使用。

這樣就解決了局部模塊路徑過長的問題。

當然,有些情況下存在兩個相同的名稱,且同樣需要導入,我們可以使用 as 關鍵字為標識符添加別名:

實例

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub fn govern() {}
}
   
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;

fn main() {
    nation_govern();
    govern();
}

這里有兩個 govern 函數,一個是 nation 下的,一個是 government 下的,我們用 as 將 nation 下的取別名 nation_govern。兩個名稱可以同時使用。

use 關鍵字可以與 pub 關鍵字配合使用:

實例

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub use government::govern;
}

fn main() {
    nation::govern();
}

引用標準庫

Rust 官方標準庫字典:https://doc.rust-lang.org/stable/std/all.html

在學習了本章的概念之后,我們可以輕松的導入系統庫來方便的開發程序了:

實例

use std::f64::consts::PI;

fn main() {
    println!("{}", (PI / 2.0).sin());
}

運行結果:

1

所有的系統庫模塊都是被默認導入的,所以在使用的時候只需要使用 use 關鍵字簡化路徑就可以方便的使用了。