8.使用包、crate和模块

2024-06-13 06:12
文章标签 模块 使用 crate

本文主要是介绍8.使用包、crate和模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 一、简单概念
  • 二、crate和包
    • 2.1 crate规则
    • 2.2 包规则
    • 2.3 Cargo的遵循的一些约定
    • 2.4 控制模块的作用域和私有性
      • 1) 模块
      • 2)引用模块树中的项
      • 3)使用 super 起始的相对路径
      • 4) 公有结构体和枚举
  • 三、use关键字的使用
  • 四、分割模块进入不同的文件

一、简单概念

Rust 有许多功能可以管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这有时被称为 模块系统(the module system),包括:

  • 包(Packages): Cargo 的一个功能,它允许构建、测试和分享 crate
  • Crates: 一个模块的树形结构,它形成了库或二进制项目;
  • 模块(Modules)和 use: 允许控制作用域和路径的私有性
  • 路径(path):一个命名例如结构体、函数或模块等项的方式;

二、crate和包

2.1 crate规则

  • crate 是一个二进制项或者库
  • crate root是一个源文件,Rust 编译器以它为起始点构成 crate 的根模块;
  • 包(package)是提供一系列功能的一个或者多个crate
  • 一个包会包含一个Cargo.toml文件

2.2 包规则

  • 一个包中至多只能包含一个库crate(library crate);
  • 包中可以包含任意多个二进制 crate(binary crate);
  • 包中至少包含一个crate,无论是库的还是二进制的;

2.3 Cargo的遵循的一些约定

  • 创建项目时,src/main.rs 就是一个与包同名的二进制crate的crate根;
  • 如果包目录中同时包含src/lib.rs,则有两个crate(一个库,一个二进制),且crate的名字都与包名相同;
  • 将文件放在src/bin 目录下,一个包可以拥有多个二进制crate,目录下的每个文件都会被编译成一个独立的二进制 crate;
  • 一个crate会将一个作用域内的相关功能分组到一起,这样就可以很方便地在多个项目之间共享;

2.4 控制模块的作用域和私有性

1) 模块

  • 模块可以让我们将一个crate中的代码进行分组,以提高可读性与重用性;
  • 模块还可以控制项的私有性,即项是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private);
  • 模块可以包含模块,也可以包含结构体、枚举、常量、trait或者函数;

下面的模块定义了酒店前台,其中有两个子模块,分别为hostingserving。新建一个src/lib.rs文件,将下面的代码粘贴进去。

mod front_of_house {mod hosting {fn add_to_waitlist() {}fn seat_at_table() {}}mod serving {fn take_order() {}fn serve_order() {}fn take_payment() {}}
}
  • src/main.rs和src/lib.rs被称为crate根,这两个文件中任意一个的内容会构成名为crate的模块,且该模块位于crate的被称为模块树的模块结构的根部;
  • 结构如下
crate└── front_of_house├── hosting│   ├── add_to_waitlist│   └── seat_at_table└── serving├── take_order├── serve_order└── take_payment
  • 最顶部的crate模块是隐式存在的;
  • 结构展示了模块之间的嵌套关系(hosting在front_of_house内部)以及兄弟关系(hosting与serving)

2)引用模块树中的项

路径有两种形式:
绝对路径: 从 crate 根部开始,以crate名或者字面量 crate 开头;
相对路径: 从当前模块开始,以self、super或当前模块的标识符开头;

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

代码示例

mod front_of_house {mod hosting {fn add_to_waitlist() {}}
}pub fn eat_at_restaurant(){// 绝对路径crate::front_of_house::hosting::add_to_waitlist();// 相对路径front_of_house::hosting::add_to_waitlist();
}
  • crate是隐式的顶层模块,因此绝对路径从crate开始
  • add_to_waitlist与eat_at_restaurant在同一crate下,因此相对路径从当前文件下的front_of_house开始;
  • 相对路径与绝对路径的使用依据具体的项目确定,但由于把代码定义和项调用各自独立地移动是更常见的,因此更倾向于使用绝对路径

编译后发现编译不过在这里插入图片描述

  • 报错信息说hosting模块是私有的,因此模块内部默认都是私有的
  • 事实上,Rust中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的;
  • 由于子模块封装并隐藏了他们的实现详情,但是子模块可以看到他们定义的上下文,因此父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用父模块中的项;
  • 子模块可以通过使用pub关键字创建公共项,使子模块的内部部分暴露给上级模块;
  • 添加pub关键字的模块并不使其内容也是公有的,模块上的 pub 关键字只允许其父模块引用它;

将hosting改为公有

mod front_of_house {pub mod hosting {fn add_to_waitlist() {}}
}
pub fn eat_at_restaurant(){// 绝对路径crate::front_of_house::hosting::add_to_waitlist();// 相对路径front_of_house::hosting::add_to_waitlist();
}

编译之后发现add_to_waitlist并非公有。
在这里插入图片描述
因此,将add_to_waitlist()也变成公有的,外部才能访问

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}pub fn eat_at_restaurant(){// 绝对路径crate::front_of_house::hosting::add_to_waitlist();// 相对路径front_of_house::hosting::add_to_waitlist();
}
  1. 私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
  2. eat_at_restaurant和front_of_house在同一个文件里都属于根级,属于根级的可以互相调用而无论是公有的还是私有的

3)使用 super 起始的相对路径

  • 可以使用super开头来构建从父模块开始的相对路径;
  • fix_incorrect_order函数在back_of_house模块中,所以可以使用super进入back_of_house父模块(即顶层crate)
fn serve_order(){}mod back_of_house {fn fix_incorrect_order(){cook_order();super::serve_order(); //上级路径(模块外)下的serve_ordercrate::serve_order(); //绝对路径访问}fn cook_order(){}
}

4) 公有结构体和枚举

  • 可以使用pub来定义公有结构体和枚举;
  • 定义为pub的结构体,其字段仍然是私有的,需要根据情况决定每个字段是否公有;
  • 将枚举定义为pub,则它的所有成员都将变为公有;
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"),}}}
}fn eat_at_restaurant() {// 在夏天点一份黑麦面包作为早餐let mut meal = back_of_house::Breakfast::summer("Rye");// 更改我们想要的面包meal.toast = String::from("Wheat");println!("I'd like {} toast please", meal.toast);// 如果取消下一行的注释,将会导致编译失败// 不得更改随餐搭配的季节水果// meal.seasonal_fruit = String::from("blueberries");
}
fn main(){eat_at_restaurant();
}
  • 代码中Breakfast结构体以及内部的toast为公有的,季节水果字段seasonal_fruit是私有的,不允许被外部修改
  • 由于back_of_house::Breakfast具有私有字段,所以这个结构体需要提供一个关联函数来构造实例;
  • 如果没有这样的函数,由于不能在eat_at_restaurant中设置私有字段seasonal_fruit的值,因此无法在eat_at_restaurant中创建Breakfast实例;

枚举的公有用法

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

三、use关键字的使用

使用 use 关键字将路径一次性引入作用域

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}pub fn other_function(){}}
}use crate::front_of_house::hosting;
// use front_of_house::hosting;           //可以使用相对路径pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::other_function();
}
  • 使用use之后可以简写eat_at_restaurant中对于hosting模块内部函数的调用;
  • 通过use引入作用域的路径依然会检查私有性;

use的习惯用法

  • 将函数的父级模块引入作用域时通常指定到父级,这样可以清晰的表明函数不是在本地定义的
  • struct、enum等引入作用域时通常指定完整路径(指定到本身)
  • 同名条目:指定到父级
use std::io;
use std::fmt;
use std::collections::HashMap;//返回不同的Result
fn function1() -> fmt::Result {// --snip--
}fn function2() -> io::Result<()> {// --snip--
}fn main() {let mut map = HashMap::new();  //明确表示是外部引入的HashMapmap.insert(1, 2);
}

使用use关键字定义别名

在这个类型的路径后面,可以使用as关键字指定一个新的本地名称或者别名

use std::fmt::Result;
use std::io::Result as IoResult;fn function1() -> Result {// --snip--
}fn function2() -> IoResult<()> {// --snip--
}

使用pub use重导出名称

  • 使用use将路径(名称)导入到作用域内后,该名称在此作用域内是私有的;
  • 为了让调用此代码的代码能在自己的作用域中引用这些类型,需要使用pub use进行重导出(re-exporting)
mod front_of_house{pub mod hosting{pub fn add_to_waitlist(){}}
}pub use crate::front_of_house::hosting;  //加上pub后,外部也可以看到这个hosting了pub fn eat_at_restaurant(){hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
}
  • 加上pub use后,可以通过新路径hosting::add_to_waitlist来调用add_to_waitlist函数;
  • 如果没加,则外部模块不能使用这个新路径;

使用外部包

比如要使用rand合成随机数,则在Cargo.toml文件中加入

[dependencies]
rand = "0.8.3"

使用

use rand::Rng;fn main() {let secret_number = rand::thread_rng().gen_range(1..101);
}

路径嵌套

  1. 下面的代码可以写成use std::{cmp::Ordering, io, collections::HashMap};
use std::collections::HashMap;
use std::io;
use std::cmp::Ordering;
  1. 下面的代码可以写成use std::io::{self, Write};
use std::io;
use std::io::Write;
  1. 可以使用"*"运算符引入路径下的所有公有项
use std::collections::*;
  • 这通常用于测试模块tests中;
  • 一般用于预导入(prelude)模式;

四、分割模块进入不同的文件

文件src/lib.rs的内容为

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}pub fn other_function(){}}
}use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::other_function();
}

如果变成

mod front_of_house;use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::other_function();
}

则需要以下步骤

  1. 创建模块front_of_house的同名文件名front_of_house.rs,内容为
pub mod hosting {pub fn add_to_waitlist() {}pub fn other_function(){}
}

即可。

  1. 如果要更进一步将front_of_house.rs文件的内容改为pub mod hosting;,则还需创建src/front_of_house/hosting.rs文件,内容为
pub fn add_to_waitlist() {}
pub fn other_function() {}
  1. 只做到第1步比较简单,这里贴一下第二步的目录结构
    在这里插入图片描述
    各个文件的值如下
//lib.rs文件内容
mod front_of_house;use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::other_function();
}//front_of_house.rs文件内容
pub mod hosting;//hosting.rs文件内容
pub fn add_to_waitlist(){}
pub fn other_function(){}

最复杂的这种分割方法保留了原本的模块树结构

这篇关于8.使用包、crate和模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1056490

相关文章

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QUndoView控件的具体使用

《Qt中QUndoView控件的具体使用》QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的... 目录引言一、QUndoView 的用途二、工作原理三、 如何与 QUnDOStack 配合使用四、自

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

python logging模块详解及其日志定时清理方式

《pythonlogging模块详解及其日志定时清理方式》:本文主要介绍pythonlogging模块详解及其日志定时清理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录python logging模块及日志定时清理1.创建logger对象2.logging.basicCo

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3