用于引用模块树中项的路径

为了向 Rust 展示在模块树中的什么位置可以找到一个项目,我们在 方式。要调用函数,我们需要 了解其路径。

路径可以采用两种形式:

  • 绝对路径是从 crate 根开始的完整路径;对于代码 从外部 crate 中,绝对路径以 crate 名称开头,对于 code 中,它以 Literalcrate.
  • 相对路径从当前模块开始,使用self,super或 当前模块中的标识符。

绝对路径和相对路径后跟一个或多个标识符 用双冒号分隔 (::).

回到示例 7-1,假设我们想调用add_to_waitlist功能。 这与询问:路径是什么add_to_waitlist功能? 示例 7-3 包含示例 7-1 以及一些模块和函数 删除。

我们将展示两种调用add_to_waitlistfunction 从新函数中获取,eat_at_restaurant,在 crate 根中定义。这些路径是正确的,但是 还有另一个问题将阻止此示例进行编译 原样。我们稍后会解释原因。

eat_at_restaurantfunction 是我们库 crate 的公共 API 的一部分,因此 我们用pub关键词。在“使用pub关键词”部分,我们将更详细地介绍pub.

文件名: src/lib.rs

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();
}

示例 7-3:调用add_to_waitlist函数使用 绝对路径和相对路径

我们第一次调用add_to_waitlist函数eat_at_restaurant, 我们使用 absolute path。这add_to_waitlistfunction 定义在相同的 crate 设置为eat_at_restaurant,这意味着我们可以使用cratekeyword 设置为 开始一个绝对路径。然后,我们包含每个连续的模块,直到我们 前往add_to_waitlist.您可以想象一个具有相同 structure:我们会指定路径/front_of_house/hosting/add_to_waitlist自 运行add_to_waitlist程序;使用cratename 从 crate 根类似于从 shell 中的文件系统根开始。/

第二次调用add_to_waitlisteat_at_restaurant,我们使用 相对路径。路径始于front_of_house、模块的名称 在模块树的同一级别定义,与eat_at_restaurant.这里 文件系统等效的是使用路径front_of_house/hosting/add_to_waitlist.以模块名称开头的含义 路径是相对的。

选择是使用相对路径还是绝对路径是您将做出的决定 根据您的项目,这取决于您是否更有可能移动 项目定义代码与使用 项目。例如,如果我们将front_of_housemodule 和eat_at_restaurant函数导入到名为customer_experience,我们会 需要将 absolute path 更新为add_to_waitlist,但是相对路径 仍然有效。但是,如果我们将eat_at_restaurant功能 单独导入到一个名为dining,则是到add_to_waitlistcall 将保持不变,但相对路径需要 被更新。我们通常倾向于指定绝对路径,因为它是 更有可能的是,我们希望独立于 彼此。

让我们尝试编译示例 7-3 并找出为什么它还不能编译!这 我们得到的错误如示例 7-4 所示。

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
  |                            |
  |                            private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
   |                     |
   |                     private module
   |
note: the module `hosting` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hosting {
   |     ^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors

示例 7-4:在 中构建代码的编译器错误 示例 7-3

错误消息显示 modulehosting是私有的。换句话说,我们 具有正确的路径hostingmodule 和add_to_waitlist函数,但 Rust 不允许我们使用它们,因为它无法访问 private 部分。在 Rust 中,所有项目(函数、方法、结构、枚举、 modules 和 constants)是父模块的私有。如果需要帮助, 要将 function 或 struct 等项设为私有,请将其放在 Module 中。

父模块中的项不能使用子模块中的私有项,但 子模块中的项可以使用其祖先模块中的项。这是 因为子模块包装并隐藏了它们的实现细节,但是子 modules 可以看到定义它们的上下文。要继续我们的 比喻,将隐私规则想象成 餐厅:里面发生的事情对餐厅顾客来说是私人的,但是 办公室经理可以在他们经营的餐厅查看和执行所有作。

Rust 选择让模块系统以这种方式运行,以便隐藏内部 implementation details 是默认值。这样,您就知道 内部代码,您可以在不破坏外部代码的情况下进行更改。但是,Rust 确实提供了 您可以选择将子模块代码的内部部分暴露给 outer ancestor modules 使用pub关键字将项目设为公共。

使用pub关键词

让我们回到示例 7-4 中的错误,它告诉我们hostingmodule 为 私人。我们希望eat_at_restaurant函数中,将 访问add_to_waitlist函数,因此我们将hosting模块中带有pub关键字,如示例 7-5 所示。

文件名: src/lib.rs

mod front_of_house {
    pub 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();
}

示例 7-5:声明hostingmodule 设置为pub自 使用它来自eat_at_restaurant

不幸的是,示例 7-5 中的代码仍然会导致编译器错误,因为 如示例 7-6 所示。

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `add_to_waitlist` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn add_to_waitlist() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:12:30
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors

示例 7-6:在 中构建代码的编译器错误 示例 7-5

发生了什么事?添加pub关键字mod hosting使 模块 public。通过此更改,如果我们可以访问front_of_house,我们可以 访问hosting.但是hosting仍然是私有的;使 module public 不会将其内容公开。这pub关键字 只允许其祖先模块中的代码引用它,而不访问其内部代码。 因为模块是容器,所以我们只能通过创建 模块 public;我们需要更进一步,选择制作一个或多个 item 中。

示例 7-6 中的错误表示add_to_waitlist函数是私有的。 隐私规则适用于结构、枚举、函数和方法,以及 模块。

我们还将add_to_waitlist函数 public 添加pub关键字,如示例 7-7 所示。

文件名: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub 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();
}

示例 7-7:添加pubkeyword 设置为mod hostingfn add_to_waitlist让我们从eat_at_restaurant

现在代码可以编译了!要了解为什么添加pubkeyword 让我们使用 这些路径eat_at_restaurant关于隐私规则,让我们看看 在 absolute 和 relative 路径上。

在绝对路径中,我们从crate,则 crate 模块的根目录 树。这front_of_housemodule 在 crate 根目录中定义。而front_of_house不是公开的,因为eat_at_restaurant函数为 定义在front_of_house(即eat_at_restaurantfront_of_house是兄弟姐妹),我们可以参考front_of_houseeat_at_restaurant.接下来是hosting标有pub.我们可以 访问 的父模块hosting,以便我们可以访问hosting.最后,add_to_waitlist函数标有pub,我们可以访问它的父级 module,所以这个函数调用有效!

在相对路径中,逻辑与绝对路径相同,只是 第一步:路径不是从 crate 根开始,而是从front_of_house.这front_of_housemodule 在同一个 module 中定义 如eat_at_restaurant,因此从其中的模块开始的相对路径eat_at_restaurant是定义的工作。那么,因为hostingadd_to_waitlist标有pub,则路径的其余部分将起作用,并且 this 函数调用有效!

如果你打算共享你的库 crate,以便其他项目可以使用你的代码, 您的公共 API 是您与 crate 用户的合同,它决定了如何 他们可以与您的代码交互。管理有很多注意事项 更改您的公共 API,使人们更容易依赖您的 板条箱。这些考虑超出了本书的范围;如果你是 如果对本主题感兴趣,请参阅 Rust API 指南

具有二进制文件和库的包的最佳实践

我们提到过,一个包可以同时包含一个 src/main.rs 二进制 crate root 以及 src/lib.rs 库 crate root,并且这两个 crate 都将具有 默认情况下是 Package Name。通常,具有这种 同时包含一个库和一个二进制 crate 将在 binary crate 启动调用库 crate 中代码的可执行文件。 这让其他项目可以从 package 提供,因为库 crate 的代码可以共享。

模块树应在 src/lib.rs 中定义。然后,任何公共项目都可以 通过在 Binary crate 中使用,以包的名称开始 paths 。 二进制 crate 成为库 crate 的用户,就像 external crate 将使用库 crate:它只能使用公共 API。 这有助于您设计一个好的 API;您不仅是作者,还是 客户!

第 12 章中,我们将演示这个组织 使用包含二进制 crate 的命令行程序进行练习 和一个库板条箱。

以 相对路径super

我们可以构造从父模块开始的相对路径,而不是 当前模块或 crate 根目录,通过使用super在 路径。这就像使用..语法。用super允许我们引用我们知道在父模块中的项目, 当模块紧密时,这可以使重新排列模块树更容易 与父级相关,但父级可能已移至模块中的其他位置 总有一天树。

考虑示例 7-8 中的代码,它模拟了 chef 修复错误的订单并亲自将其发送给客户。这 功能fix_incorrect_orderback_of_housemodule 调用 功能deliver_order在父模块中定义,方法是指定deliver_order,以super.

文件名: src/lib.rs

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}

示例 7-8:使用相对路径调用函数 起始于super

fix_incorrect_order函数位于back_of_house模块,因此我们可以 用super要转到back_of_house,在本例中为 是crate、根。从那里,我们寻找deliver_order并找到它。 成功!我们认为back_of_housemodule 和deliver_order功能 可能会彼此保持相同的关系并被移动 我们应该一起决定重新组织 crate 的模块树。因此,我们 使用super因此,如果 代码被移动到不同的模块。

将结构和枚举设为公共

我们还可以使用pub将结构和枚举指定为 public,但有一个 使用pub使用结构和枚举。如果我们使用pub在结构体定义之前,我们将结构体设为公共,但结构体的字段 仍将是私有的。我们可以根据具体情况将每个字段设为公开或不公开 基础。在示例 7-9 中,我们定义了一个 publicback_of_house::Breakfast结构 使用公共toastfield 中,而是私有的seasonal_fruit田。此模型 在餐厅中,顾客可以选择面包类型 随餐提供,但厨师决定哪些水果搭配餐点 关于当季和库存。可用的水果变化很快,因此 客户无法选择水果,甚至无法看到他们将获得哪种水果。

文件名: src/lib.rs

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    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
    // meal.seasonal_fruit = String::from("blueberries");
}

示例 7-9:一个带有一些 public fields 和一些 私有字段

因为toast字段中的back_of_house::Breakfaststruct 是 public, 在eat_at_restaurant我们可以写入和读取toast使用 DOT 的字段 表示法。请注意,我们不能使用seasonal_fruit字段输入eat_at_restaurant因为seasonal_fruit是私有的。尝试取消注释 行修改seasonal_fruitfield 值来查看您得到什么错误!

另外,请注意,由于back_of_house::Breakfast具有私有字段, struct 需要提供一个公共关联函数,该函数构造一个 实例Breakfast(我们已将其命名为summer这里)。如果Breakfast不 有这样的函数,我们无法创建Breakfasteat_at_restaurant因为我们无法设置私有seasonal_fruit字段输入eat_at_restaurant.

相反,如果我们将枚举设为 public,则其所有变体都是 public。我们 只需要pubenum关键字,如示例 7-10 所示。

文件名: src/lib.rs

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;
}

示例 7-10:将枚举指定为 public 会使其所有 变体 public

因为我们制作了Appetizerenum public,我们可以使用SoupSalad的变体eat_at_restaurant.

除非它们的变体是公开的,否则枚举不是很有用;那会很烦人 必须用pub在每种情况下,默认的 for enum variants 是 public。结构体通常很有用,而没有它们的 fields 是公共的,因此 struct 字段遵循一切的一般规则 默认为私有,除非使用pub.

还有一种情况涉及pub我们还没有涵盖,那就是 我们的最后一个模块系统功能:use关键词。我们将涵盖use本身 首先,然后我们将展示如何组合pubuse.

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