Rust 包誉裆、crate 與 模塊

包、crate 與 模塊

編寫程序時一個核心的問題是作用域:在代碼的某處編譯器知道哪些變量名缸濒?允許調(diào)用哪些函數(shù)足丢?這些變量引用的又是什么元镀?

Rust有一系列與作用域相關(guān)的功能。這有時被稱為模塊系統(tǒng)霎桅,不過又不僅僅是模塊:

  • 是Cargo的一個功能栖疑,它允許你構(gòu)建、測試和分享crate滔驶。

  • Crates是一個模塊的樹形結(jié)構(gòu)遇革,它形成了庫或二進制項目。

  • 模塊use關(guān)鍵字允許你控制作用域和路徑的私有性揭糕。

  • 路徑是一個命名例如結(jié)構(gòu)體萝快、函數(shù)或模塊等項的方式

包和 crate 用來創(chuàng)建庫和二進制項目

讓我們來聊聊模塊和 crate。下面是一個總結(jié):

  • crate 是一個二進制或庫項目著角。
  • crate 根是一個用來藐視如何構(gòu)建 crate 的文件揪漩。
  • 帶有 Cargo.toml 文件的用以描述如何構(gòu)建一個或多個 crate。一個包中至多有一個庫項目吏口。

所以當運行cargo new時是在創(chuàng)建一個包:

cargo new my-project

因為 Cargo 創(chuàng)建了 Cargo.toml奄容,這意味者現(xiàn)在我們有了一個包。如果查看 Cargo.toml 的內(nèi)容产徊,會發(fā)現(xiàn)并沒有提到 src/main.rs昂勒。然而,Cargo 的約定是如果在代表表的 Cargo.toml 的同級目錄下包含 src 目錄且其中包含 main.rs 文件的話舟铜,Cargo 就知道這個包帶有一個與包同名的二進制 crate戈盈,且 src/main.rs 就是 crate 根。另一個約定如果包目錄中包含 src/lib.rs谆刨,則包帶有與其同名的庫 crate塘娶,且 src/lib.rs 是 crate 根。crate 根文件將由 Cargo 傳遞給 rustc來實際構(gòu)建庫或者二進制項目痊夭。

一個包可以帶有零個或一個庫 crate 和任意多個二進制 crate刁岸。一個包中至少一個(庫或二進制) crate。

模塊系統(tǒng)來控制作用域和私有性

Rust 的此部分功能通常被引用為模塊系統(tǒng)生兆,不過其包括了一些除模塊之外的功能难捌。本部分我們會討論:

  • 模塊,一個組織代碼和控制路徑私有性的方式
  • 路徑鸦难,一個命名項的方式
  • use關(guān)鍵字用來將路徑引入作用域
  • pub關(guān)鍵字使項變?yōu)楣灿?/li>
  • as關(guān)鍵字用于將項引入作用域時進行重命名
  • 使用外部包
  • 嵌套路徑來消除大量的use語句
  • 使用 glob 運算符將模塊的所有內(nèi)容引入作用域
  • 如何將不同模塊分割到單獨的文件中

模塊允許我們將代碼組織起來根吁。

mod sound {
    mod instrument {
        mod woodwind {
           fn clarinet() {
               
            } 
        }
    }
    
    mod voice {
        
    }
}

fn main() {
    
}

路徑來引用模塊樹中的項

如果想要調(diào)用函數(shù),需要知道其路徑合蔽。

路徑有兩種形式:

  • 絕對路徑從crate 根開始击敌,以 crate 名或者字面值 crate開頭
  • 相對路徑從當前模塊開始,以self拴事、supper或者當前模塊的標識符開頭沃斤。

絕對路徑和相對路徑都后跟一個或多個由雙冒號(::)分割的標識符圣蝎。

main中調(diào)用clarinet函數(shù),現(xiàn)在還不能調(diào)用

mod sound {
    mod instrument {
        fn clarinet() {
            
        }
    }
}

fn main() {
    // 絕對路徑
    crate::sound::instrument::clarinet();
    
    // 相對路徑
    sound::instrument::clarinet();  
}

instrument模塊是私有的衡瓶。instrument模塊和clarinet函數(shù)的路徑都是正確的徘公,不過Rust不讓我們使用,因為他們是私有的哮针。

模塊作為私有性的邊界

Rust采用模塊還有另外一個原因:模塊是Rust中的私有邊界关面。私有規(guī)則如下:

  • 所有項(函數(shù)、方法十厢、結(jié)構(gòu)體等太、枚舉、模塊和常量)默認是私有的蛮放。
  • 可以使用pub關(guān)鍵字使項變?yōu)楣灿小?/li>
  • 不允許使用定義于當前模塊的子模塊中私有代碼缩抡。
  • 允許使用任何定義于父模塊或當前模塊中的代碼

使用pub關(guān)鍵字使項變?yōu)楣?/h3>
mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

fn main() {
    // 絕對路徑
    crate::sound::instrument::clarinet();
    
    // 相對路徑
    sound::instrument::clarinet();
}

使用super開始相對路徑

fn main() {
    crate::sound::instrument::clarinet();
    sound::instrument::inner::clarinet();
}

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            super::voice::hello();
        }
        pub mod inner {
            pub fn clarinet() {
                super::super::voice::hello();
            }
        }
    }

    pub mod voice {
        pub fn hello() {
            println!("hello");
        }
    }
}

對結(jié)構(gòu)體和枚舉使用pub

mod plant {
    pub struct Vegetable {
        pub name: String,
        id: i32,
    }
    
    impl Vegetable {
        pub fn new(name: &str) -> Vegetable {
            Vegetable {
                name: String::from(name),
                id: 1,
            }
        }
    }
}

fn main() {
    let mut v = plant::Vegetable::new("squash");
    v.name = String::from("butternut squash");
    println!("{}", v.name);
}

plant::Vegetable結(jié)構(gòu)體的name字段是公有的,在main中可以使用點號讀寫name字段包颁。不允許在main中使用id字段因為是私有的瞻想。

使用use關(guān)鍵字將名稱引入作用域

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

use crate::sound::instrument;

fn main() {
    instrument::clarinet();
}

使用相對路徑將項引入作用域

use self::sound::instrument;

user函數(shù)路徑使用習慣 VS 其他項

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

use crate::sound::instrument::clarinet;

fn main() {
    clarinet();
}

userclarinet函數(shù)引入作用域,這是不推薦的徘六。

對于函數(shù)來說内边,通過use指定的父模塊接著指定模塊來調(diào)用方法被認為是習慣的方法。通過use指定函數(shù)的路徑待锈,清楚的表明了函數(shù)不是本地定義的,同時最小化了指定全路徑的重復嘴高。

通過 as關(guān)鍵字重命名引入作用域的類型

將兩個同名類型引入同一作用域這個問題還有另一個解決辦法:可以通過在use后加上as和一個新名稱來為此類型指定一個新的本地名稱竿音。

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    Ok(())
}

fn function2() -> IoResult<()> {
    Ok(())
}

通過pub use重導出名稱

當使用 use關(guān)鍵字將名稱導入作用域時,在新作用域中可用的名稱是私有的拴驮。如果希望調(diào)用呢編寫的代碼能夠像你一樣在其自己的作用域內(nèi)引用這些類型春瞬,可以結(jié)合pubuse。這個技術(shù)被稱為重導出套啤,因為這樣做將項引入作用域同時使其可供其他代碼引入自己的作用域宽气。

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

mod performance_group {
    pub use crate::sound::instrument;
    
    pub fn clarinet_trio() {
        instrument::clarinet();
    }
}

fn main() {
    performance_group::clarinet_trio();
    performance_group::instrument::clarinet();
}

使用外部包

在 Cargo.toml 中加入 rand依賴

[dependencies]
rand = "0.5.5"

rand定義引入項目包的作用域,加入一行use

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101);
}

嵌套路徑來消除大量的use

當需要引入很多定義于相同或相同模塊的項時潜沦,為每一項單獨列出一行會占用源碼很大的空間萄涯。

use std::cmp::Ordering;
use std::io;
// ...

可以使用嵌套的路徑將同樣的項在一行中引入而不是兩行。

use std::{cmp::Ordering, io};

通過 glob 運算符將所有的公有定義引入作用域

如果希望將一個路徑下 所有 公有項引入作用域唆鸡,可以指定路徑后跟*,glob 運算符:

use std::collections::*;

將模塊分割進不同文件

當模塊變得更大時涝影,你可能想要將它們的定義移動到一個單獨的文件中使代碼更容易閱讀。

文件名: src/main.rs

mod sound;
fn main() {
    // 絕對路徑
    create::sound::instrument::clarinet();
    
    // 相對路徑
    sound::instrument::clarinet();
}

在crate根文件聲明 sound 模塊(這里是 src/main.rs)争占,將模塊內(nèi)容移動到 src/sound.rs 文件燃逻, src/sound.rs 中會包含sound模塊的內(nèi)容

文件名: src/sound.rs

pub mod instrument {
    pub fn clarinet() {
        
    }
}

mod sound后使用分號而不是代碼塊告訴Rust在另一個與模塊同名文件中加載模塊的內(nèi)容序目。

繼續(xù)重構(gòu)我們的例子,將instrument模塊也提取到自己的文件中伯襟,修改 src/sound.rs 只包含instrument模塊的聲明:

文件名: src/sound.rs

pub mod instrument;

接著創(chuàng)建 src/sound 目錄和 src/sound/instrument.rs 文件來包含instrument模塊的定義:

文件名: src/sound/instrument.rs

pub fn clarinet() {
    
}

模塊樹依然保持相同贮懈,main中的函數(shù)調(diào)用也無需修改繼續(xù)保持有效,即使其定義存在于不同的文件中伟端。這樣隨著代碼增長可以將模塊移動到新文件中藕各。

總結(jié)

Rust 提供了將包組織進 crate, 將 crate 組織進模塊和通過指定絕對或相對路徑從一個模塊引用另一個模塊中定義的項的方式片效『炻祝可以通過use語句將路徑引入作用域,這樣在多次使用時可以使用更短的路徑淀衣。模塊定義的代碼默認是私有的昙读,不過可以選擇增加pub關(guān)鍵字使其定義變?yōu)楣小?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膨桥,隨后出現(xiàn)的幾起案子蛮浑,更是在濱河造成了極大的恐慌,老刑警劉巖只嚣,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沮稚,死亡現(xiàn)場離奇詭異,居然都是意外死亡册舞,警方通過查閱死者的電腦和手機蕴掏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來调鲸,“玉大人盛杰,你說我怎么就攤上這事∶晔” “怎么了即供?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長于微。 經(jīng)常有香客問我逗嫡,道長,這世上最難降的妖魔是什么株依? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任驱证,我火速辦了婚禮,結(jié)果婚禮上勺三,老公的妹妹穿的比我還像新娘雷滚。我一直安慰自己,他們只是感情好吗坚,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布祈远。 她就那樣靜靜地躺著呆万,像睡著了一般。 火紅的嫁衣襯著肌膚如雪车份。 梳的紋絲不亂的頭發(fā)上谋减,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音扫沼,去河邊找鬼出爹。 笑死,一個胖子當著我的面吹牛缎除,可吹牛的內(nèi)容都是我干的严就。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼器罐,長吁一口氣:“原來是場噩夢啊……” “哼梢为!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起轰坊,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铸董,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肴沫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粟害,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年颤芬,在試婚紗的時候發(fā)現(xiàn)自己被綠了悲幅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡站蝠,死狀恐怖夺艰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沉衣,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布减牺,位于F島的核電站豌习,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拔疚。R本人自食惡果不足惜肥隆,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稚失。 院中可真熱鬧栋艳,春花似錦、人聲如沸句各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矾屯,卻和暖如春兼蕊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背件蚕。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工孙技, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人排作。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓牵啦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妄痪。 傳聞我的和親對象是個殘疾皇子哈雏,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容