使用 use 关键字将 Paths 引入 Scope


必须写出调用函数的路径可能会让人感到不便和重复。在示例 7-7 中,无论我们选择 add_to_waitlist 函数的绝对路径还是相对路径,每次我们想调用 add_to_waitlist 我们还必须指定 front_of_house托管。幸运的是,有一种方法可以简化这个过程:我们可以使用 keyword 一次,然后在范围中的其他所有位置使用较短的名称。


在示例 7-11 中,我们将 crate::front_of_house::hosting 模块引入 eat_at_restaurant 函数的作用域中,因此我们只需要指定 hosting::add_to_waitlist 调用 add_to_waitlist 中的 eat_at_restaurant


文件名: 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();
}


示例 7-11:使用


在作用域中添加 use 和 path 类似于在文件系统中创建符号链接。通过添加 use crate::front_of_house::hosting crate 根,托管现在是该范围内的有效名称,就像托管 module 已在 crate 根中定义。使用 use 引入范围的路径 还要检查隐私,就像任何其他路径一样。


请注意,use 只会为特定范围创建快捷方式,在该范围内, 使用发生。示例 7-12 将 eat_at_restaurant 函数移动到一个名为 customer 的新子模块中,该子模块的范围与 use 语句,因此函数体不会编译。


文件名: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}


示例 7-12:use 语句仅适用于它所在的范围


编译器错误显示该快捷方式不再适用于 客户模块:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of undeclared crate or module `hosting`
   |
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted


请注意,还有一个警告,指出 use 不再在其范围内使用!要解决这个问题,也要在 customer 模块中移动 use,或者在子模块中使用 super::hosting 引用父模块中的快捷方式 customer 模块。


创建惯用的 use 路径


在示例 7-11 中,你可能想知道为什么add_to_waitlist我们在 use crate::front_of_house::hosting eat_at_restaurant,而不是指定 use path 一直到 add_to_waitlist 函数来获得相同的结果,就像示例 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();
}


示例 7-13:使用 useadd_to_waitlist 函数引入范围,这是不地道的


尽管示例 7-11 和示例 7-13 都完成了相同的任务,但示例 7-11 是将函数引入 use 的惯用方式。使用 use 将函数的父模块引入范围意味着我们必须在调用函数时指定父模块。在调用函数时指定父模块可以清楚地表明该函数不是本地定义的,同时仍能最大限度地减少完整路径的重复。示例 7-13 中的代码不清楚 add_to_waitlist 的定义位置。


另一方面,当引入 structs、enum 和其他 use 项时,指定完整路径是惯用的。示例 7-14 展示了将标准库的 HashMap 结构体引入二进制 crate 范围的惯用方法。


文件名: src/main.rs

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}


示例 7-14:以惯用的方式将 HashMap 引入范围


这个成语背后没有很强的理由:它只是已经出现的约定,人们已经习惯了以这种方式读取和编写 Rust 代码。


这个习惯用法的例外是,如果我们使用 use 语句将两个同名的项目引入作用域,因为 Rust 不允许这样做。示例 7-15 展示了如何将两个名称相同但父模块不同的 Result 类型引入范围,以及如何引用它们。


文件名: src/lib.rs

use std::fmt;
use std::io;

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

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}


示例 7-15:将两个同名的类型引入同一范围需要使用它们的父模块。


如您所见,使用父模块可以区分两种 Result 类型。如果我们指定了 use std::fmt::Resultuse std::io::Result,那么在同一个范围内会有两个 Result 类型,而 Rust 在使用 Result 时不知道我们指的是哪一个。


使用 as 关键字提供新名称


还有另一种解决方案,即使用 use 将两种同名的类型引入同一范围:在 path 之后,我们可以为 type 指定 as 和新的本地名称或 alias。示例 7-16 显示了另一种编写示例 7-15 中代码的方法,即使用 as 重命名两种 Result 类型之一。


文件名: src/lib.rs

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

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

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


示例 7-16:使用 as 关键字将类型引入范围时重命名类型


在第二个 use 语句中,我们为 std::io::Result 类型,它不会与 std::fmt 中的 Result 冲突 我们也已将其纳入范围。示例 7-15 和示例 7-16 是 被认为是惯用的,所以选择权在你!


使用 pub 重新导出名称


当我们使用 use 关键字将名称引入范围时,新范围中可用的名称是 private。要使调用我们代码的代码能够引用该名称,就好像它是在该代码的范围内定义的一样,我们可以将 pub 并使用。这种技术称为重新导出,因为我们将一个项目引入范围,但也使该项目可供其他人引入他们的范围。


示例 7-17 显示了示例 7-11 中的代码,其中 use in root 模块更改为 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();
}


示例 7-17:使用 pub use 为任何代码提供 name 以供新 scope 使用


在此更改之前,外部代码必须调用 add_to_waitlist 函数 restaurant::front_of_house::hosting::add_to_waitlist() ,这也需要将 front_of_house 模块标记为 pub。现在,此 pub 使用已从根模块重新导出托管模块,外部代码可以改用该路径 restaurant::hosting::add_to_waitlist()


当代码的内部结构不同时,重新导出非常有用 从调用代码的程序员如何考虑域。为 例如,在这个餐厅的比喻中,经营餐厅的人认为 关于“Front of House”和“Back of House”。但是顾客光顾餐厅 可能不会从这些方面考虑餐厅的各个部分。跟 pub 使用,我们可以用一个结构来编写代码,但公开不同的结构。这样做可以使我们的库为处理库的程序员和调用库的程序员组织良好。我们将看看 pub 使用的另一个例子,以及它如何影响你的 crate 的文档 “导出一个 使用 pub 的便捷公共 API“部分。


使用外部软件包


在第 2 章中,我们编写了一个猜谜游戏项目,该项目使用名为 rand 的外部包来获取随机数。为了在我们的项目中使用 rand,我们在 Cargo.toml 中添加了这一行:


文件名: Cargo.toml


兰德 = “0.8.5”


Cargo.toml 中添加 rand 作为依赖项会告诉 Cargo 下载 rand 打包和任何依赖项 crates.io 并使 rand 可用于我们的项目。


然后,为了将 rand 定义引入我们的包范围,我们添加了一个 使用以 crate 名称 rand 开头的行,并列出我们想要引入范围的项目。回想一下,在“生成随机 Number“部分,我们将 Rng trait 引入范围并调用 rand::thread_rng 函数:

use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}


Rust 社区的成员在 crates.io,将它们中的任何一个拉入你的包中都涉及这些相同的步骤:在你的包的 Cargo.toml 文件中列出它们,并使用 use 将 crate 中的项目带入范围。


请注意,标准 std 库也是我们包外部的 crate。因为标准库是随 Rust 语言一起提供的,所以我们不需要更改 Cargo.toml 来包含 std。但是我们确实需要用 use 来引用它,以便将 Item 从那里引入我们的 package 范围。例如,对于 HashMap,我们将使用这一行:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
}


这是一个以 std 开头的绝对路径,std 是标准库 crate 的名称。


使用嵌套路径清理大型使用列表


如果我们使用在同一个 crate 或同一个模块中定义的多个项目,那么在单独的行上列出每个项目会占用我们文件中的大量垂直空间。例如,我们在示例 2-4 中的猜谜游戏中的这两个 use 语句将 std 中的项目带入作用域:


文件名: src/main.rs

use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}


相反,我们可以使用嵌套路径在一行中将相同的项目引入范围。为此,我们指定了 path 的公共部分,后跟两个冒号,然后在 paths 的不同部分列表周围用大括号括起来,如示例 7-18 所示。


文件名: src/main.rs

use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}


示例 7-18:指定一个嵌套路径,将具有相同前缀的多个项目引入作用域


在更大的程序中,使用嵌套路径从同一个 crate 或 module 中将许多项目引入范围可以减少所需的单独 use 语句的数量很多!


我们可以在路径中的任何级别使用嵌套路径,这在组合共享子路径的两个 use 语句时非常有用。例如,示例 7-19 显示了两个 use 语句:一个将 std::io 引入范围,另一个将 std::io::Write 到 scope 中。


文件名: src/lib.rs

use std::io;
use std::io::Write;


示例 7-19:两个 use 语句,其中一个是另一个的子路径


这两个路径的公共部分是 std::io,这是完整的第一个路径。要将这两个路径合并为一个 use 语句,我们可以在嵌套路径中使用 self,如示例 7-20 所示。


文件名: src/lib.rs


使用 std::io::{self, Write};


示例 7-20:将示例 7-19 中的路径合并为一个 use 语句


此行将 std::iostd::io::Write 引入范围。


通配符运算符


如果我们想将路径中定义的所有公共项目引入范围,我们可以指定该路径,后跟 * glob 运算符:

#![allow(unused)]
fn main() {
use std::collections::*;
}


use 语句将 std::collections 中定义的所有公共项引入当前范围。使用 glob 运算符时要小心!Glob 会让你更难分辨哪些名称在范围内,以及你的程序中使用的名称是在何处定义的。


测试时经常使用 glob 运算符将待测试的所有内容放入 tests 模块中;我们将在“How to Write Tests“部分。glob 运算符有时也用作 prelude 样式的一部分:请参阅标准 库文档 了解有关该模式的更多信息。