程式都會遇到 scope 的問題,在某一個時間點,compiler 知道些變數,允許呼叫哪些函數,變數引用是什麼?
rust 有 module system
- packages: 是 cargo 的功能,可建立、測試、分享 crate
- crates 是一個 module 的樹狀結構,有 library 或 binary project
- modules 和 use: 允許你控制 scope 及 privacy of paths
- paths: 對 struct, function, module 命名的方法
Packages and Crate
- crate 是 binary 或是 library
- crate root 一份描述如何建構 create 的文件
- 一個 Package 是一個或多個 crates
Package 透過 Cargo.toml 用來描述建構 crates
package 可包含 0 或 1 個 library crate,不能超過。可包含多個 binary crates。至少要包含一個 crate (library 或 binary 都可以)
cargo new
就是在產生一個新的 package
$ cargo new my-project
Created binary (application) `my-project` package
cargo package 預設就是在 Cargo.toml 同一層的目錄中,有包含 src 目錄,且裡面有 main.rs 時, cargo 就知道這個 package 帶有一個跟 package 同名的 binary crate,且 src/main.rs 就是 crate root。另外如果 src/lib.rs,其 package 帶有同名的 library crate,且 src/lib.rs 是 crate root。 crate root 檔案將會傳給 rust 用來建構 library 或是 binary。
package 可包含 0 或 1 個 library crate,不能超過。可包含多個 binary crates。至少要包含一個 crate (library 或 binary 都可以)
如果 package 同時包含 src/main.rs 及 src/lib.rs,就是帶有兩個 crate: 一個同名 library,一個同名 binary。package 可帶有多個 binary crate,要將其檔案放在 src/bin 目錄,每個檔案都是一個獨立的 binary crate。
Defining Modules to Control Scope and Privacy
module system
- module: 組織 code 與控制路徑私有性的方法
- paths: 命名 items 的方法
- use: 將某個 path 帶入 scope
- pub: 開放 items,讓 items 變成公有
- as: 將 item 引入 scope 時,用來 rename
- external packages
- glob operator: 將 module 的所有內容引入 scope
- 將不同 module 分割到獨立的檔案中
module 可組織程式碼,將 crate 分組,增加 readability 與 reuse 便利性。module 也可以控制 privacy of items,item 可以是 public (可被 outside code 使用),或是 private,只是 internal implementation。
以下是 restaurant 的 library crate 範例
在一個餐廳可分兩個部分:front of house 及 back of house。front of house 包含 seating customers, 收單的 servers,payment,製作飲料的 bartenders。back of house 包含chef,在廚房燒菜,洗碗機,及負責管理的 managers。
首先建立一個新的 library
cargo new --lib restaurant
修改 src/lib.rs
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() {}
}
}
modules 可包含其他 items 的定義:structs, enums, constants, traits, functions
module 可將相關的定義放在一起。
src/main.rs, src/lib.rs 分別稱為 crate roots
以上就是產生了這樣的 module tree。類似 file system 的目錄樹,另一個類似的地方,是需要用 path 來找到適當的 module 或 item。
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Paths for Referring to an item in the Module Tree
rust 以 path 在module tree 中定位 item,如果要呼叫某個 function 就必須要知道他的 path
path 有兩種形式,兩種都有一或多個 identifiers,id 之間以 ::
區隔
- absolute path: 從 crate root 開始,以 crate 開頭
- relative path: 從目前的 module 開始,以 self, super 或目前的 module 的 id 開始
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
在編譯時,會發生 error ,這是 privacy 的問題
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^
rust 的所有 items: funcitons, methods, structs, enums, modules, constants 預設都是 private。在 parent module 的 items 不能使用 child modules 裡面的 private items。但 child modules 的 items 可以使用 ancestor modules 的 items。原因是 child modules 會 wrap & hide implementation details,但 child modules 可看到跟其定義相同的 context 的其他 items。
rust 會隱藏 inner implementation details,因此我們可知道如何在不影響 outer code 的狀況下,改變 inner code。可利用 pub
keyword 讓 item 變成 public。
Exposing Paths with pub
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 因為 eat_at_restaurant 跟 front_of_house 是相同的 module,故可直接使用 front_of_house
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Starting Relative Paths with super
super
類似 filesystem 的 ..
用途,代表 parent module。
backofhouse 跟 serve_order 在相同的 module
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
// super 就等同於 crate
super::serve_order();
}
fn cook_order() {}
}
Making Structs and Enums Public
如果在 struct 定義前面加上 pub
,可讓 struct 變成 public,但 struct 裡面的欄位還是 private。必須單獨每一個欄位分開設定 pub
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
// 為 Breakfast 增加 public method: summer
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);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// 無法修改 seasonal_fruit,因為該欄位是 private
// meal.seasonal_fruit = String::from("blueberries");
}
對於將 enum 設定為 public,只需要在定義的地方加上 pub
,就會讓所有 variants 都是 public
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Bringing Paths info Scope with use
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 原本在使用 hosting,必須要透過 相對或絕對路徑
// 透過 use 宣告後,後面就可直接使用 hosting
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use
要使用相對路徑有一點點不同,路徑必須要用 self
開頭。不過目前實測,如果不寫 self
也沒有問題。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// self 也可以不寫
//use self::front_of_house::hosting;
use front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
Creating Idiomatic use
Paths
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// use 可以將 add_to_waitlist 引入,這樣後面的程式,就可以直接呼叫這個 fn
// 但這樣是不好的寫法
// 因為使用 add_to_waitlist 時,感覺 add_to_waitlist 是定義在這個 module 裡面
// 而上面 hosting::add_to_waitlist(); 這樣的呼叫方式
// 比較清楚知道,add_to_waitlist 是定義在其他地方
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}
當 use structs, enums, 其他 items 時,最好指定 full path。例如指定使用標準函式庫的 HashMap
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
唯一的例外是,在 scope 中,使用了兩個相同名稱的 items,這時必須要加上 parent module,用來區分 items
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
}
fn function2() -> io::Result<()> {
}
Providing New Names with as
使用相同名稱的 items 有另一種解決方法,就是用 as
加上替代的 new local name
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
}
fn function2() -> IoResult<()> {
}
Re-exporting Names with pub
use
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 在 use 前面再加上 pub
// 就等於將 hosting 變成 public,外面的 code 也可以直接使用 hosting::add_to_waitlist()
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
Re-exporting 在程式碼的 internal structure 跟外面呼叫你的 code 的方法不同時,會很有用。例如餐廳,營運者很容易了解 "front of house" "back of house" 的意義。但客戶可能不懂這兩個名詞,這時候,可以透過 pub use
調整結構。
Using External Packages
先前專案有使用 rand
這個 external package,是在 Cargo.toml
裡面加上 rand
[dependencies]
rand = "0.5.5"
加上 rand 的定義後,等於將 rand 帶入這個 package 的 scope。透過 use
引用 Rng
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
}
Rust community 將許多 library packages 都放在 https://crates.io,
標準函式庫 std
也是一個 crate。但因為 std
是在 rust language 裡面,所以不需要另外在 Cargo.toml 裡面填寫到 dependencies 中。
Using Nested Paths to Clean Up Large use
Lists
如果同時要使用兩個以上的 module,但 std::
會一直重複。
use std::cmp::Ordering;
use std::io;
改用這個方式
use std::{cmp::Ordering, io};
另一個例子
use std::io;
use std::io::Write;
// 改為
use std::io::{self, Write};
The Glob Operator
如果想使用某個 path 的所有 public items,就用 *
use std::collections::*;
Separating Modules info Different Files
先前的範例都是將多個 module 定義在一個檔案中。可以將 module 分在不同檔案。
修改 src/lib.rs
// 定義在 src/front_of_house.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
src/frontofhouse.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
可再進一步分離 hosting
修改 src/frontofhouse.rs
pub mod hosting;
新增 src/frontofhouse/hosting.rs
pub fn add_to_waitlist() {}