热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Rust之包,箱和模块管理(三):引用模块树中项目的路径:maven的project项目包含若干个module

本文主要介绍关于rust,开发语言,后端的知识点,对【Rust之包,箱和模块管理(三):引用模块树中项目的路径】和【maven的project项目包含若干个module】有兴趣的朋友可以看下由【枫★曦

本文主要介绍关于rust,开发语言,后端的知识点,对【Rust之包,箱和模块管理(三):引用模块树中项目的路径】和【maven的project项目包含若干个module】有兴趣的朋友可以看下由【枫★曦】投稿的技术文章,希望该技术和经验能帮到你解决你所遇的Rust相关技术问题。

maven的project项目包含若干个module

开发环境 Windows 10Rust 1.62.1

 

 VS Code 1.69.2 

    项目工程

这里继续沿用上次工程rust-demo

引用模块树中项目的路径 

为了显示在模块树中何处可以找到一个项目,我们使用了一个路径,就像我们在导航文件系统时使用路径一样。如果我们想调用一个函数,我们需要知道它的路径。

路径可以有两种形式:

绝对路径从箱 crate根开始,使用箱 crate名称(用于外部箱 crate中的代码)或文字箱 crate(用于当前箱 crate中的代码)。相对路径从当前模块开始,使用 selfsuper或当前模块中的标识符。

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

让我们回到之前的例子。我们如何调用add_to_waitlist函数?这就跟问,add_to_waitlist函数的路径是什么一样?下例中包含之前的例子,删除了一些模块和功能。我们将展示两种方法来从在crate根中定义的新函数eat_at_restaurant调用add_to_waitlist函数。eat_at_restaurant函数是我们的库crate的公共API的一部分,所以我们用pub关键字标记它。在“使用pub关键字公开路径”一节中,我们将更详细地讨论pub。注意,这个例子还不能编译;我们稍后会解释原因。

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

pub fn eat_at_restaurant() {
    // 使用crate绝对和相对路径调用
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();   // crate开始绝对路径     

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

我们第一次在eat_at_restaurant中调用add_to_waitlist函数时,使用的是绝对路径。add_to_waitlist函数与eat_at_restaurant在同一个crate中定义,这意味着我们可以使用crate关键字开始一个绝对路径。

crate之后,我们包括每个连续的模块,直到我们添加到等待列表。您可以想象一个具有相同结构的文件系统,我们将指定路径/front _ of _ house/hosting/add_to_waitlist来运行add _ to _ wait list程序;使用板条箱名称从板条箱根目录开始就像在shell中使用/从文件系统根目录开始一样。

第二次在eat_at_restaurant中调用add_to_waitlist时,我们使用的是相对路径。路径以front_of_house开始,这是在模块树中与eat_at_restaurant相同级别定义的模块的名称。在这里,文件系统等同于使用front _ of _ house/hosting/add _ to _ wait list路径。以名称开头意味着路径是相对的。

选择使用相对路径还是绝对路径取决于您的项目。这个决定应该取决于您是更倾向于将项目定义代码与使用该项目的代码分开移动还是一起移动。例如,如果我们将front_of_house模块和eat_at_restaurant函数移到名为customer_experience的模块中,我们需要将绝对路径更新为add_to_waitlist,但是相对路径仍然有效。但是,如果我们将eat_at_restaurant函数单独移到一个名为dining的模块中,那么add_to_waitlist调用的绝对路径将保持不变,但是相对路径需要更新。我们的首选是指定绝对路径,因为我们更有可能希望彼此独立地移动代码定义和项调用。

编译上述例子,出现如下的错误信息

$ 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();
  |                            ^^^^^^^ 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();
   |                     ^^^^^^^ 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` due to 2 previous errors

 错误消息指出模块hosting是私有的。换句话说,我们有hosting模块和add_to_waitlist函数的正确路径,但是Rust不允许我们使用它们,因为它不能访问私有部分。

模块不仅仅对组织代码有用。它们还定义了Rust的隐私边界:封装实现细节的代码行不允许外部代码知道、调用或依赖。所以,如果你想把一个函数或者结构变成私有的,你可以把它放在一个模块中。

Rust中隐私的工作方式是,默认情况下,所有项目(函数、方法、结构、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用其祖先模块中的项。原因是子模块包装并隐藏了它们的实现细节,但是子模块可以看到定义它们的上下文。继续餐厅的比喻,把隐私规则想象成餐厅的后勤办公室:那里发生的事情是餐厅顾客的隐私,但办公室经理可以看到并做他们经营的餐厅中的一切。

Rust选择让模块系统以这种方式运行,因此默认情况下隐藏内部实现细节。这样,您就知道可以在不破坏外部代码的情况下更改内部代码的哪些部分。但是您可以通过使用pub关键字将项目公开,从而将子模块代码的内部部分公开给外部祖先模块。

使用pub关键字公开路径

让我们回到上例中的错误,它告诉我们hosting模块是私有的。我们希望父模块中的eat_at_restaurant函数能够访问子模块中的add_to_waitlist函数,所以我们用pub关键字标记hosting模块,如下所示。

文件名:src/lib.rs

mod front_of_house {
    pub mod hosting {              // 将hosting模块声明为pub,以便从eat_at_restaurant使用它
        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();
}

然后编译,很不幸的是依然出错

$ 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` due to 2 previous errors

发生了什么事?在mod hosting前面添加pub关键字使模块成为公共的。有了这个改变,如果我们可以访问front_of_house,我们就可以访问hosting。但是hosting的内容还是私密的;将模块公开并不会将其内容公开。模块上的pub关键字只允许其祖先模块中的代码引用它。

上述的错误表明add_to_waitlist函数是私有的。隐私规则适用于结构、枚举、函数和方法以及模块。 

我们还可以通过在函数定义前添加pub关键字来公开add_to_waitlist函数,如下所示:

文件名:src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {} // 将pub关键字添加到mod hosting和fn add_to_waitlist让我们可以从eat_at_restaurant调用该函数
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

现在代码可以编译了!让我们看看绝对路径和相对路径,并仔细检查为什么添加pub关键字可以让我们根据隐私规则在add_to_waitlist中使用这些路径。

在绝对路径中,我们从箱crate开始,箱crate的模块树的根。然后在箱crate根中定义前端模块。front_of_house模块不是公共的,但是因为eat_at_restaurant函数是在与front_of_house相同的模块中定义的(也就是说,eat_at_restaurant和front_of_house是兄弟),所以我们可以从eat_at_restaurant引用front_of_house。接下来是标有pub的托管模块。我们可以访问主机的父模块,所以我们可以访问hosting。最后,add_to_waitlist函数用pub标记,我们可以访问它的父模块,所以这个函数调用是有效的!

在相对路径中,除了第一步之外,逻辑与绝对路径相同:路径不是从箱crate根开始,而是从front_of_house开始。front_of_house模块与eat_at_restaurant在同一个模块中定义,因此从定义eat_at_restaurant的模块开始的相对路径有效。然后,因为hostingadd_to_waitlist是用pub标记的,所以路径的其余部分是有效的,这个函数调用是有效的!

如果您计划共享您的库箱crate,以便其他项目可以使用您的代码,那么您的公共API就是您与库箱crate用户关于他们如何与您的代码交互的合同。有许多关于管理你的公共API的变化的考虑,以使人们更容易依赖你的机箱。

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

我们提到一个包可以包含一个src/main.rs二进制箱根目录和一个src/lib.rs库箱根目录,默认情况下两个箱都有包名。通常,具有这种模式的包在二进制箱中有足够的代码来启动一个可执行程序,该程序调用库箱中的代码。这让其他项目受益于软件包提供的大部分功能,因为库箱的代码可以共享。

应该在src/lib.rs中定义模块树。然后,通过以包的名称开始路径,可以在二进制箱中使用任何公共项目。二进制箱成为箱的用户,就像完全外部的箱使用库箱一样:它只能使用公共API。这有助于你设计一个好的API你不仅是作者,还是客户!

用super开始相对路径 

我们还可以通过在路径的开头使用super来构造从父模块开始的相对路径。这就像用..语法。我们为什么要这么做?

考虑下例中的代码,它模拟了一个厨师修正了一个错误的订单并亲自把它带给顾客的情况。back_of_house模块中定义的函数fix_incorrect_order调用父模块中定义的函数deliver_order,方法是指定以super开头的deliver_order路径:

文件名:src/lib.rs

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order(); // 使用以super开头的相对路径调用函数
    }

    fn cook_order() {}
}

fix_incorrect_order函数在back_of_house模块中,所以我们可以使用super转到back_of_house的父模块,在本例中是crate,即根。从那里,我们寻找deliver_order并找到它。成功!我们认为back_of_house模块和deliver_order函数可能彼此保持相同的关系,如果我们决定重新组织箱的模块树,它们可能会移到一起。因此,我们使用了super,这样如果代码被移动到不同的模块中,我们就有更少的地方来更新代码。

使结构和枚举成为公共的

我们也可以使用pub将结构和枚举指定为公共的,但是还有一些额外的细节。如果我们在一个结构定义之前使用pub,我们使该结构成为公共的,但是该结构的字段仍然是私有的。我们可以根据具体情况公开或不公开每个字段。在下例中,我们定义了一个公共的back_of_house::Breakfast结构,它有一个公共的toast字段和一个私有的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");
}

因为back_of_house::Breakfast结构中的toast字段是公共的,所以在eat_at_restaurant中,我们可以使用点标记来读写toast字段。请注意,我们不能使用eat_at_restaurant中的季节性水果字段,因为季节性水果是私有的。尝试取消修改seasonal_fruit字段值的行的注释,看看会得到什么样的错误!

还要注意,因为back_of_house::Breakfast有一个私有字段,所以该结构需要提供一个公共关联函数来构造一个Breakfast的实例(我们在这里将其命名为summer)。如果早餐没有这样的功能,我们就无法在eat_at_restaurant中创建Breakfast的实例,因为我们无法在eat_at_restaurant中设置私有的seasonal_fruit字段的值。

相反,如果我们使一个枚举成为公共的,那么它的所有变体都是公共的。我们只需要enum关键字前的pub,如下所示。

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

因为我们公开了Appetizer enum,所以我们可以在eat_at_restaurant中使用汤和沙拉变体。枚举不是很有用,除非它们的变体是公共的;在每种情况下都必须用pub注释所有的enum变体是很烦人的,所以enum变体的默认设置是public。如果结构的字段不是公共的,那么结构通常是有用的,所以结构字段遵循默认情况下一切都是私有的一般规则,除非用pub注释。

还有一种涉及pub的情况我们还没有涉及到,那就是我们最后一个模块系统特性:use关键字。我们将首先讨论use本身,然后展示如何将pubuse结合起来。

本章重点 绝对路径相对路径pub关键字声明公共模块声明公共接口super关键字声明公共结构体和枚举

本文《Rust之包,箱和模块管理(三):引用模块树中项目的路径》版权归枫★曦所有,引用Rust之包,箱和模块管理(三):引用模块树中项目的路径需遵循CC 4.0 BY-SA版权协议。


推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
author-avatar
野蛮生长
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有