ch07-04-bringing-paths-into-scope-with-the-use-keyword.md
commit 2d605f66b3d891dea0a2f684ece508aa2282b854
到目前為止,似乎我們編寫的用于調(diào)用函數(shù)的路徑都很冗長且重復,并不方便。例如,示例 7-7 中,無論我們選擇 add_to_waitlist
函數(shù)的絕對路徑還是相對路徑,每次我們想要調(diào)用 add_to_waitlist
時,都必須指定front_of_house
和 hosting
。幸運的是,有一種方法可以簡化這個過程。我們可以使用 use
關(guān)鍵字將路徑一次性引入作用域,然后調(diào)用該路徑中的項,就如同它們是本地項一樣。
在示例 7-11 中,我們將 crate::front_of_house::hosting
模塊引入了 eat_at_restaurant
函數(shù)的作用域,而我們只需要指定 hosting::add_to_waitlist
即可在 eat_at_restaurant
中調(diào)用 add_to_waitlist
函數(shù)。
文件名: src/lib.rs
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();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
示例 7-11: 使用 ?use
?將模塊引入作用域
在作用域中增加 use
和路徑類似于在文件系統(tǒng)中創(chuàng)建軟連接(符號連接,symbolic link)。通過在 crate 根增加 use crate::front_of_house::hosting
,現(xiàn)在 hosting
在作用域中就是有效的名稱了,如同 hosting
模塊被定義于 crate 根一樣。通過 use
引入作用域的路徑也會檢查私有性,同其它路徑一樣。
你還可以使用 use
和相對路徑來將一個項引入作用域。示例 7-12 展示了如何指定相對路徑來取得與示例 7-11 中一樣的行為。
文件名: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
示例 7-12: 使用 ?use
?和相對路徑將模塊引入作用域
在示例 7-11 中,你可能會比較疑惑,為什么我們是指定 use crate::front_of_house::hosting
,然后在 eat_at_restaurant
中調(diào)用 hosting::add_to_waitlist
,而不是通過指定一直到 add_to_waitlist
函數(shù)的 use
路徑來得到相同的結(jié)果,如示例 7-13 所示。
文件名: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn 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();
}
示例 7-13: 使用 ?use
?將 ?add_to_waitlist
?函數(shù)引入作用域,這并不符合習慣
雖然示例 7-11 和 7-13 都完成了相同的任務(wù),但示例 7-11 是使用 use
將函數(shù)引入作用域的習慣用法。要想使用 use
將函數(shù)的父模塊引入作用域,我們必須在調(diào)用函數(shù)時指定父模塊,這樣可以清晰地表明函數(shù)不是在本地定義的,同時使完整路徑的重復度最小化。示例 7-13 中的代碼不清楚 add_to_waitlist
是在哪里被定義的。
另一方面,使用 use
引入結(jié)構(gòu)體、枚舉和其他項時,習慣是指定它們的完整路徑。示例 7-14 展示了將 HashMap
結(jié)構(gòu)體引入二進制 crate 作用域的習慣用法。
文件名: src/main.rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
示例 7-14: 將 ?HashMap
?引入作用域的習慣用法
這種習慣用法背后沒有什么硬性要求:它只是一種慣例,人們已經(jīng)習慣了以這種方式閱讀和編寫 Rust 代碼。
這個習慣用法有一個例外,那就是我們想使用 use
語句將兩個具有相同名稱的項帶入作用域,因為 Rust 不允許這樣做。示例 7-15 展示了如何將兩個具有相同名稱但不同父模塊的 Result
類型引入作用域,以及如何引用它們。
文件名: src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
示例 7-15: 使用父模塊將兩個具有相同名稱的類型引入同一作用域
如你所見,使用父模塊可以區(qū)分這兩個 Result
類型。如果我們是指定 use std::fmt::Result
和 use std::io::Result
,我們將在同一作用域擁有了兩個 Result
類型,當我們使用 Result
時,Rust 則不知道我們要用的是哪個。
使用 use
將兩個同名類型引入同一作用域這個問題還有另一個解決辦法:在這個類型的路徑后面,我們使用 as
指定一個新的本地名稱或者別名。示例 7-16 展示了另一個編寫示例 7-15 中代碼的方法,通過 as
重命名其中一個 Result
類型。
文件名: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
示例 7-16: 使用 ?as
? 關(guān)鍵字重命名引入作用域的類型
在第二個 use
語句中,我們選擇 IoResult
作為 std::io::Result
的新名稱,它與從 std::fmt
引入作用域的 Result
并不沖突。示例 7-15 和示例 7-16 都是慣用的,如何選擇都取決于你!
使用 use
關(guān)鍵字,將某個名稱導入當前作用域后,這個名稱在此作用域中就可以使用了,但它對此作用域之外還是私有的。如果想讓其他人調(diào)用我們的代碼時,也能夠正常使用這個名稱,就好像它本來就在當前作用域一樣,那我們可以將 pub
和 use
合起來使用。這種技術(shù)被稱為 “重導出(re-exporting)”:我們不僅將一個名稱導入了當前作用域,還允許別人把它導入他們自己的作用域。
示例 7-17 將示例 7-11 根模塊中的 use
改為 pub use
。
文件名: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn 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();
}
示例 7-17: 通過 pub use
使名稱可從新作用域中被導入至任何代碼
通過 pub use
重導出,外部代碼現(xiàn)在可以通過新路徑 restaurant::hosting::add_to_waitlist
來調(diào)用 add_to_waitlist
函數(shù)。如果沒有指定 pub use
,外部代碼需在其作用域中調(diào)用 restaurant::front_of_house::hosting::add_to_waitlist
。
當你代碼的內(nèi)部結(jié)構(gòu)與調(diào)用你代碼的程序員所想象的結(jié)構(gòu)不同時,重導出會很有用。例如,在這個餐館的比喻中,經(jīng)營餐館的人會想到“前臺”和“后臺”。但顧客在光顧一家餐館時,可能不會以這些術(shù)語來考慮餐館的各個部分。使用 pub use
,我們可以使用一種結(jié)構(gòu)編寫代碼,卻將不同的結(jié)構(gòu)形式暴露出來。這樣做使我們的庫井井有條,也使開發(fā)這個庫的程序員和調(diào)用這個庫的程序員都更加方便。
在第二章中我們編寫了一個猜猜看游戲。那個項目使用了一個外部包,rand
,來生成隨機數(shù)。為了在項目中使用 rand
,在 Cargo.toml 中加入了如下行:
文件名: Cargo.toml
rand = "0.8.3"
在 Cargo.toml 中加入 rand
依賴告訴了 Cargo 要從 crates.io 下載 rand
和其依賴,并使其可在項目代碼中使用。
接著,為了將 rand
定義引入項目包的作用域,我們加入一行 use
起始的包名,它以 rand
包名開頭并列出了需要引入作用域的項?;貞浺幌碌诙碌?“生成一個隨機數(shù)” 部分,我們曾將 Rng
trait 引入作用域并調(diào)用了 rand::thread_rng
函數(shù):
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
crates.io 上有很多 Rust 社區(qū)成員發(fā)布的包,將其引入你自己的項目都需要一道相同的步驟:在 Cargo.toml 列出它們并通過 use
將其中定義的項引入項目包的作用域中。
注意標準庫(std
)對于你的包來說也是外部 crate。因為標準庫隨 Rust 語言一同分發(fā),無需修改 Cargo.toml 來引入 std
,不過需要通過 use
將標準庫中定義的項引入項目包的作用域中來引用它們,比如我們使用的 HashMap
:
use std::collections::HashMap;
這是一個以標準庫 crate 名 std
開頭的絕對路徑。
當需要引入很多定義于相同包或相同模塊的項時,為每一項單獨列出一行會占用源碼很大的空間。例如猜猜看章節(jié)示例 2-4 中有兩行 use
語句都從 std
引入項到作用域:
文件名: src/main.rs
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
相反,我們可以使用嵌套路徑將相同的項在一行中引入作用域。這么做需要指定路徑的相同部分,接著是兩個冒號,接著是大括號中的各自不同的路徑部分,如示例 7-18 所示。
文件名: src/main.rs
// --snip--
use std::{cmp::Ordering, io};
// --snip--
示例 7-18: 指定嵌套的路徑在一行中將多個帶有相同前綴的項引入作用域
在較大的程序中,使用嵌套路徑從相同包或模塊中引入很多項,可以顯著減少所需的獨立 use
語句的數(shù)量!
我們可以在路徑的任何層級使用嵌套路徑,這在組合兩個共享子路徑的 use
語句時非常有用。例如,示例 7-19 中展示了兩個 use
語句:一個將 std::io
引入作用域,另一個將 std::io::Write
引入作用域:
文件名: src/lib.rs
use std::io;
use std::io::Write;
示例 7-19: 通過兩行 ?use
?語句引入兩個路徑,其中一個是另一個的子路徑
兩個路徑的相同部分是 std::io
,這正是第一個路徑。為了在一行 use
語句中引入這兩個路徑,可以在嵌套路徑中使用 self
,如示例 7-20 所示。
文件名: src/lib.rs
use std::io::{self, Write};
示例 7-20: 將示例 7-19 中部分重復的路徑合并為一個 ?use
?語句
這一行便將 std::io
和 std::io::Write
同時引入作用域。
如果希望將一個路徑下 所有 公有項引入作用域,可以指定路徑后跟 *
,glob 運算符:
use std::collections::*;
這個 use
語句將 std::collections
中定義的所有公有項引入當前作用域。使用 glob 運算符時請多加小心!Glob 會使得我們難以推導作用域中有什么名稱和它們是在何處定義的。
glob 運算符經(jīng)常用于測試模塊 tests
中,這時會將所有內(nèi)容引入作用域;我們將在第十一章 “如何編寫測試” 部分講解。glob 運算符有時也用于 prelude 模式;查看 標準庫中的文檔 了解這個模式的更多細節(jié)。
更多建議: