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

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

路径可以采用两种形式:

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

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

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

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

该函数是我们库 crate 的公共 API 的一部分,因此 我们用关键字标记它。在“使用 pub 关键字公开路径”部分中,我们将更详细地介绍 .eat_at_restaurantpubpub

文件名: 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:使用 绝对路径和相对路径

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

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

选择是使用相对路径还是绝对路径是您将做出的决定 根据您的项目,这取决于您是否更有可能移动 项目定义代码与使用 项目。例如,如果我们将模块和函数移动到名为 的模块中,我们将 需要将绝对路径更新为 ,但相对路径 仍然有效。但是,如果我们将函数 单独放入名为 的模块中,调用的绝对路径将保持不变,但相对路径需要 被更新。我们通常倾向于指定绝对路径,因为它是 更有可能的是,我们希望独立于 彼此。front_of_houseeat_at_restaurantcustomer_experienceadd_to_waitlisteat_at_restaurantdiningadd_to_waitlist

让我们尝试编译示例 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

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

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

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

使用 pub 关键字公开路径

让我们回到示例 7-4 中的错误,它告诉我们模块是 私人。我们希望父模块中的函数具有 访问子模块中的函数,所以我们用关键字标记模块,如图 7-5 所示。hostingeat_at_restaurantadd_to_waitlisthostingpub

文件名: 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:将托管模块声明为 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

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

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

让我们通过在函数定义之前添加关键字来将函数设为 public,如示例 7-7 所示。add_to_waitlistpub

文件名: 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:将 pub 关键字添加到 mod 托管fn add_to_waitlist 中,我们可以从 eat_at_restaurant 调用函数

现在代码可以编译了!要了解为什么添加关键字允许我们使用 这些路径 在隐私规则方面,让我们看看 在 absolute 和 relative 路径上。pubeat_at_restaurant

在绝对路径中,我们从 开始,这是我们 crate 模块的根 树。该模块在 crate 根目录中定义。While 不是 public,因为函数是 定义在同一个模块中(即 和 是同级),我们可以引用 FROM 。接下来是标有 的模块。我们可以 访问 的父模块 ,以便我们可以访问 。最后,该函数被标记为 ,我们可以访问其父函数 module,所以这个函数调用有效!cratefront_of_housefront_of_houseeat_at_restaurantfront_of_houseeat_at_restaurantfront_of_housefront_of_houseeat_at_restauranthostingpubhostinghostingadd_to_waitlistpub

在相对路径中,逻辑与绝对路径相同,只是 第一步:路径不是从 crate 根开始,而是从 .该模块在同一个模块中定义 as ,因此从定义 module 开始的相对路径有效。然后,由于 和 被标记为 ,因此路径的其余部分有效,并且 this 函数调用有效!front_of_housefront_of_houseeat_at_restauranteat_at_restauranthostingadd_to_waitlistpub

如果你打算共享你的库 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 根,通过在 路径。这就像使用语法启动文件系统路径一样。Using 允许我们引用我们知道在父模块中的项目, 当模块紧密时,这可以使重新排列模块树更容易 与父级相关,但父级可能已移至模块中的其他位置 总有一天树。super..super

考虑示例 7-8 中的代码,它模拟了 chef 修复错误的订单并亲自将其发送给客户。这 函数调用 函数,方法是指定 的路径 ,以 开头。fix_incorrect_orderback_of_housedeliver_orderdeliver_ordersuper

文件名: 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 开始

函数在模块中,因此我们可以 用于转到 的父模块 ,在本例中为 是 ,根。从那里,我们寻找并找到它。 成功!我们认为模块和功能 可能会彼此保持相同的关系并被移动 我们应该一起决定重新组织 crate 的模块树。因此,我们 used,因此我们将来更新代码的地方会更少,如果这个 代码被移动到不同的模块。fix_incorrect_orderback_of_housesuperback_of_housecratedeliver_orderback_of_housedeliver_ordersuper

将结构和枚举设为公共

我们也可以用于将结构和枚举指定为 public,但有一个 使用 with structs 和 enum 的额外细节。如果我们使用 before a struct 定义,我们会将 struct 设为 public,但 struct 的字段 仍将是私有的。我们可以根据具体情况将每个字段设为公开或不公开 基础。在示例 7-9 中,我们定义了一个 public struct 具有 public 字段,但具有 private 字段。此模型 在餐厅中,顾客可以选择面包类型 随餐提供,但厨师决定哪些水果搭配餐点 关于当季和库存。可用的水果变化很快,因此 客户无法选择水果,甚至无法看到他们将获得哪种水果。pubpubpubback_of_house::Breakfasttoastseasonal_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 和一些 私有字段

因为结构体中的字段是 public 的,所以 in 中,我们可以使用 DOT 对字段进行写入和读取 表示法。请注意,我们不能在 中使用 中的字段,因为 是私有的。尝试取消注释 行修改字段值以查看您得到的错误!toastback_of_house::Breakfasteat_at_restauranttoastseasonal_fruiteat_at_restaurantseasonal_fruitseasonal_fruit

另外,请注意,由于具有私有字段,因此 struct 需要提供一个公共关联函数,该函数构造一个 实例(我们在此处命名了它)。如果没有 有这样的函数,我们无法创建 in 的实例,因为我们无法在 中设置私有字段的值。back_of_house::BreakfastBreakfastsummerBreakfastBreakfasteat_at_restaurantseasonal_fruiteat_at_restaurant

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

文件名: 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

因为我们公开了枚举,所以我们可以在 中使用 和 变体。AppetizerSoupSaladeat_at_restaurant

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

还有一种情况我们没有介绍,那就是 我们的最后一个模块系统功能:关键字。我们将单独介绍 首先,然后我们将展示如何组合 和 。pubuseusepubuse

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