使用 use 关键字将 Paths 引入 Scope

必须写出调用函数的路径可能会让人感觉很不方便,并且 重复。在示例 7-7 中,无论我们选择 absolute 还是 relative 路径 函数,每次我们想要调用时,我们都必须指定 and too。幸运的是,有一个 简化此过程的方法:我们可以用关键字创建一次路径的快捷方式,然后在作用域中的其他所有位置使用较短的名称。add_to_waitlistadd_to_waitlistfront_of_househostinguse

在示例 7-11 中,我们将模块引入 作用域,因此我们只需要指定在 中调用函数。crate::front_of_house::hostingeat_at_restauranthosting::add_to_waitlistadd_to_waitlisteat_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 将模块引入范围

在范围中添加 and 路径类似于在 文件系统。通过在 crate 中添加 root 现在是该范围内的有效名称,就像该模块已在 crate 根中定义一样。与任何其他路径一样,带入 作用域的路径也会检查 privacy。useuse crate::front_of_house::hostinghostinghostinguse

请注意,仅为发生 的特定范围创建快捷方式。示例 7-12 将函数移动到新的 子模块,该子模块的范围与语句不同,因此函数体不会编译。useuseeat_at_restaurantcustomeruse

文件名: 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 语句仅适用于 它在

编译器错误显示该快捷方式在模块中不再适用:customer

$ 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

请注意,还有一个警告,指出 the 不再在其范围内使用!自 修复此问题,也将 移动到模块内,或引用 父模块中的快捷方式 with within 子模块。useusecustomersuper::hostingcustomer

创建惯用的 use 路径

在示例 7-11 中,你可能想知道为什么我们指定了然后调用 ,而不是一直指定路径到 该函数实现相同的结果,如示例 7-13 所示。use crate::front_of_house::hostinghosting::add_to_waitlisteat_at_restaurantuseadd_to_waitlist

文件名: 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:引入 add_to_waitlist 函数 into 范围 with use 一起使用,这是不地道的

尽管示例 7-11 和示例 7-13 都完成了相同的任务,但 清单 7-11 是将函数引入作用域的惯用方式。带 函数的父模块放入 scope 中,这意味着我们必须指定 parent 模块。指定父模块 调用该函数可以清楚地表明该函数不是本地定义的 同时仍尽量减少完整路径的重复。示例 7-13 中的代码是 不清楚在哪里定义。useuseadd_to_waitlist

另一方面,当使用 指定完整路径是惯用的。示例 7-14 显示了惯用的方式 将标准库的结构体引入二进制文件的作用域 板条箱。useHashMap

文件名: src/main.rs

use std::collections::HashMap;

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

示例 7-14:在 惯用方式

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

这个习惯用法的例外是,如果我们带来两个同名的项目 into 作用域,因为 Rust 不允许这样做。示例 7-15 演示如何将名称相同但 不同的父模块,以及如何引用它们。useResult

文件名: src/lib.rs

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

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

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

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

如您所见,使用父模块可以区分这两种类型。 如果我们指定了 和 ,则 在同一个范围内有两个类型,Rust 不知道我们是哪一个 是指当我们使用 .Resultuse std::fmt::Resultuse std::io::ResultResultResult

使用 as 关键字提供新名称

对于引入两个同名类型的问题,还有另一种解决方案 到同一作用域中,在 path 后面加上 : ,我们可以指定一个新的 类型的本地名称或别名。示例 7-16 显示了另一种编写方式 示例 7-15 中的代码,使用 重命名两种类型中的一种。useasResultas

文件名: 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:将类型引入 范围

在第二个语句中,我们为类型选择了新名称,这不会与我们也引入范围的 from 冲突。示例 7-15 和示例 7-16 是 被认为是惯用的,所以选择权在你!useIoResultstd::io::ResultResultstd::fmt

使用 pub 重新导出名称

当我们使用关键字将名称引入范围时,在 新范围是 private。启用调用我们的代码的代码以引用 该名称,就好像它是在该代码的范围内定义的一样,我们可以将 和 组合在一起。这种技术称为重新导出,因为我们引入了 item 引入范围,但也使该 item 可供其他人引入 他们的范围。usepubuse

示例 7-17 显示了示例 7-11 中的代码,其中 root 模块 更改为 。usepub 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 使用

在此更改之前,外部代码必须使用 path 调用函数,该 path 也将具有 要求将模块标记为 。现在,这已经从根模块重新导出了模块,外部代码 可以改用 path。add_to_waitlistrestaurant::front_of_house::hosting::add_to_waitlist()front_of_housepubpub usehostingrestaurant::hosting::add_to_waitlist()

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

使用外部软件包

在第 2 章中,我们编写了一个猜谜游戏项目,它使用外部 package 调用以获取随机数。为了在我们的项目中使用,我们 在 Cargo.toml 中添加了这一行:randrand

文件名: Cargo.toml

rand = "0.8.5"

Cargo.toml 中添加为依赖项会告诉 Cargo 从 crates.io 和 提供给我们的项目。randrandrand

然后,为了将定义纳入我们的包的范围,我们添加了一行以 crate 名称开头的行,并列出了项目 我们想把它们纳入范围。回想一下,在“生成随机 Number“部分,我们引入了 trait into 范围并调用函数:randuserandRngrand::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 文件中列出它们,并且 用于将 crate 中的项目带入范围。use

请注意,标准库也是一个 crate,它位于 包。因为标准库是随 Rust 语言一起提供的,所以我们 不需要更改 Cargo.toml 来包含 .但我们确实需要参考 it 替换为将 Item 从那里导入到我们的 package 范围中。例如 替换为,我们将使用这一行:stdstduseHashMap

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

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

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

如果我们使用在同一个 crate 或同一个模块中定义的多个项目,请列出 每个项目在自己的行中都会占用大量的垂直空间。为 例如,我们在示例 2-4 的猜谜游戏中的这两个语句 将项从 bring into scope:usestd

文件名: 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!"),
    }
}

相反,我们可以使用嵌套路径将相同的项目合并到一个 线。为此,我们指定了路径的公共部分,后跟两个 冒号,然后在路径的 differ,如示例 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 或 模块使用嵌套路径可以减少单独语句的数量 很多人都需要!use

我们可以在路径中的任何级别使用嵌套路径,这在组合时很有用 两个共享 subpath 的语句。例如,示例 7-19 显示了两个语句:一个 bring into scope,一个 bring into scope。useusestd::iostd::io::Write

文件名: src/lib.rs

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

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

这两条路径的共同部分是 ,这是完整的第一个 路径。要将这两个路径合并为一个语句,我们可以在 嵌套路径,如示例 7-20 所示。std::iouseself

文件名: src/lib.rs

use std::io::{self, Write};

示例 7-20:将示例 7-19 中的路径合并为 One Use 声明

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

通配符运算符

如果我们想将 path 中定义的所有公共项都引入 scope,我们可以 指定 glob 运算符后跟的 path:*

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

此语句将 中定义的所有公共项引入 当前范围。使用 glob 运算符时要小心!Glob 可以做到 更难分辨哪些名称在范围内以及名称在程序中的使用位置 被定义。usestd::collections

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

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准