Rust如何进行模块化开发技巧分享

目录
  • Rust如何进行模块化开发?
  • Package和Create
  • Cargo的惯例
  • Create的作用
  • 定义module来控制作用域和私有性
  • 路径Path
  • 私有边界(private boundary)
  • pub关键字
  • super关键字
  • pub struct
  • pub enum
  • Use关键字
  • use的习惯用法
  • as关键字
  • 使用 pub use 重新导出名称
  • 导入外部包
  • 如何将模块放入其他文件?

类似es6的模块化,Rust通过package、create、module来实现代码的模块化管理

Rust如何进行模块化开发?

Rust的代码组织包括:哪些细节可以暴露,哪些细节是私有的,作用域内哪些名称有效等等。

  • Package(包):Cargo的特性,让你构建、测试、共享create
  • Create(单元包):一个模块树,它可以产生一个library或可执行文件
  • Module(模块)、use:让你控制代码的组织、作用域、私有路径
  • Path(路径):为struct、function或module等项命名的方式

Package和Create

create的类型:

  • binary(二进制create)
  • library(库create)

其中,关于Create,还有个概念——Create Root:

是源代码文件Rust编译器从这里开始,组成你的Create的根Module

一个Package:

  • 包含一个Cargo.toml,它描述了如何构建这些Crates
  • 只能包含0-1个library create(库create)
  • 可以包含任意数量的binary create(二进制create)
  • 但必须至少包含一个create(library或binary)

我们使用cargo新建一个项目

然后会提示: Created binary (application) my-project package,这代表我们创建了一个二进制的应用程序,名叫my-project的package

我们进入这个文件夹:

我们可以看到src/min.rs文件,这是我们程序的入口文件,但是我们在Cargo.toml中并没有看到相关的配置:

[package]
name = "my-project"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies] 

这是因为cargo有一些惯例

Cargo的惯例

  • src/main.rs是binary create的create root* create的名与package名相同如果我们还有一个这个文件:src/lib.rs,那么:
  • 表明package包含一个library create
  • 它是library create的create root
  • create的名与package名相同

Cargo将会把create root文件交给rustc(rust编译器)来构建library或者binary

一个Package可以同时包含src/main.rs和src/lib.rs

一个Package也可以有多个binary create:

  • 文件放在src/bin,放在这里的每个文件都是单独的binary create

Create的作用

将相关功能组合到一个作用域内,便于在项目间进行共享。

同时,这也能防止命名冲突,例如rand create,访问它的功能需要通过它的名字:rand

定义module来控制作用域和私有性

Module:

  • 在一个create内,将代码进行分组
  • 增加可读性,易于复用
  • 控制项目(item)的私有性。public,private

建立module:

  • mod关键字
  • 可嵌套
  • 可包含其他项(struct、enum、常量、trait、函数等)的定义
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 叫做create roots:

  • 这两个文件(任意一个)的内容形成了名为create的模块,位于整个模块树的根部
  • 整个模块树在隐式的模块下

路径Path

路径的作用是为了在rust的模块中找到某个条目

路径的两种形式:

  • 绝对路径:从create root开始,使用create名或字面值create
  • 相对路径:从当前模块开始,使用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();//相对路径
} 

那么为什么会报错呢?

我们查看报错的原因:module hosting is private,编译器告诉我们,hosting这个module是私有的。至此,为了解决这个问题,我们应该去了解一下私有边界

私有边界(private boundary)

  • 模块不仅可以组织代码,还可以定义私有边界
  • 如果把函数或struct等设为私有,可以将它放到某个模块中。
  • rust中所有的条目(函数,方法,struct,enum,模块,常量)默认情况下是私有的
  • 父级模块无法访问子模块中的私有条目
  • 但是在子模块中可以使用所有祖先模块中的条目

为什么rust默认这些条目是私有的呢?因为rust希望能够隐藏内部的实现细节,这样就会让开发者明确知道:更改哪些内部代码的时候,不会破坏外部的代码。同时,我们可以使用pub关键字将其声明为公共的。

pub关键字

rust默认这些条目为私有的,我们可以使用pub关键字来将某些条目标记为公共的。

我们将hosting声明pub,add_to_waitlist这个function也要声明pub

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();//相对路径
} 

为什么front_of_house这个mod不需要添加pub呢?因为它们是同级的。

super关键字

super:用来访问父级模块路径中的内容,类似文件系统中的..

fn serve_order() {}
mod front_of_house {fn fix_incorrect_order() {cook_order();super::serve_order();}fn cook_order() {}
} 

pub struct

声明一个公共的struct就是将pub放在struct前:

mod back_of_house {pub struct Breakfast {}
} 

声明了一个公共的struct后:

  • struct是公共的
  • struct的字段默认是私有的

而我们想让struct中的字段为公有的必须在前面加上pub

mod back_of_house {pub struct Breakfast {pub toast: String,//公有的seasonal_fruit: String, //私有的}
} 

也就是说:struct的字段需要单独设置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"),}}}
}

pub 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");//报错:field `seasonal_fruit` is private
} 

pub enum

声明一个公共的enum就是将pub放在enum前:

mod back_of_house {pub enum Appetizer {}
} 

我们声明了一个公共的enum后:

  • enum是公共的
  • enum的变体也都是公共的
mod back_of_house {pub enum Appetizer {Soup,//公共的Salad, //公共的}
} 

为什么呢?因为枚举里面只有变体,只有变体是公共的这个枚举才有用。而struct中某些部分为私有的也不影响struct的使用,所以rust规定公共的struct中的字段默认为私有的。

Use关键字

我们可以使用use关键字将路径导入到作用域内,而我们引入的东西也任然遵循私有性规则(公共的引入的才能用)

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}fn some_function() {}//私有的,使用use导入后,外部依然不能调用这个函数}
}

use crate::front_of_house::hosting;
// 相当于mod hosting {}

pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
} 

使用use来指定相对路径(和使用条目时的规则相同):

use front_of_house::hosting; 

我们可以注意到我们调用的add_to_waitlist是导入的hostingmod下的,那我们可不可以直接导入function呢?

当然是可以的(不过并不推荐直接导入方法):

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

use crate::front_of_house::hosting::add_to_waitlist;
// 相对于mod hosting {}

pub fn eat_at_restaurant() {add_to_waitlist();
} 

use的习惯用法

当我们直接导入方法时,我们有可能就搞不清楚是从其他模块导入的还是在这个作用域下声明的。

所以,通常情况下,我们导入的通常为父级模块。

//...
use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {hosting::add_to_waitlist();
} 

不过,struct,enum,其他:指定完整路径(指定到本身)

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

但是同名的条目,我们在引入时需指定父级模块(比如下面的例子,两个类型都叫Result)

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

fn f1() -> fmt::Result {//...
}

fn f2() -> io::Result {//...
}
//... 

as关键字

关于上面同名的问题,还有另一种解决方法:使用as关键字

as关键字可以为引入的路径指定本地的别名

use std::fmt::Result;
use std::io::Result as IoResult;

fn f1() -> Result {//...
}

fn f2() -> IoResult {//...
} 

使用 pub use 重新导出名称

使用 use 将路径(名称)导入到作用域内后,该名称在此作用域内是私有的,外部的模块是没办法访问use导入的模块的。

由前面pub的作用可知,类似pub fn、pub mod,我们可以使用pub use来导入,相当于它导入了这个内容,然后又将它导出了。

(当我们使用pub use时会发现没有警告:“导入了但没有使用”,因为它同时也导出了,也被视作使用了这个导入的内容)

导入外部包

我们通过在Cargo.toml中的[dependencies]添加依赖:

# ...
[dependencies]
rand = "^0.8.5" 

出现:Blocking waiting for file lock on package cache

删除User/.cargo文件夹中的.package-cache文件。重新执行cargo build下载依赖。

很多时候我们的下载速度很慢,我们可以将下载源换到国内,在用户文件夹下的.cargo文件夹中添加 config 文件,写入以下内容:

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
# 如果所处的环境中不允许使用 git 协议,可以把上面的地址改为
# registry = "https://mirrors.ustc.edu.cn/crates.io-index"
#[http]
#check-revoke = false 

这时候cargo build就会很快了。

我们这样导入:

use rand::Rng; 

另外:标准库也被当做外部包,需要导入,并且:

  • 我们不需要修改Cargo.toml来添加依赖
  • 需要使用use将std的特定条目导入到当前作用域 use多次导入(嵌套导入)
use std::{ascii, io};
//相当于:use std::ascii;
// use std::io; 

这样的导入该如何简写呢?

use std::io;
use std::io::Chain; 

可以使用self

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

如何将模块放入其他文件?

假如我们的src/lib.rs中的内容是这样:

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}
//... 

mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}
//... 

我们可以在lib.rs同级目录下新建front_of_house.rs,然后将模块内容写在文件中:

front_of_house.rs

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

lib.rs

mod front_of_house;
//... 

如果我们想将hosting模块的内容单独存放呢?

我们需要新建一个front_of_house文件夹,并新建hosting.rs文件

hosting.rs

pub fn add_to_waitlist() {} 

front_of_house.rs

pub mod hosting; 

lib.rs

mod front_of_house;//... 

原来的文件内容:

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

随着模块逐渐变大,这项功能将能够帮助我们更好的管理代码

到此这篇关于Rust如何进行模块化开发的文章就介绍到这了,更多相关Rust模块化开发内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 从迷你todo 命令行入门Rust示例详解

    目录 一个迷你 todo 应用 需要安装的依赖 文件目录组织 主文件 读取文件 状态处理工厂函数 Trait(特征) Create trait Get trait Delete trait Edit trait 导出 trait 为 struct 实现 trait Pending Done 导出 struct Process 输入处理 最后 一个迷你 todo 应用 该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用 你将学会什么? 基本的命令行操作 文件读写和文件结构组织 我

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • rust引用和借用的使用小结

    目录 引用和借用 引用的使用 可变引用与不可变引用 NLL 总结 引用和借用 如果每次都发生所有权的转移,程序的编写就会变得异常复杂.因此rust和其它编程语言类似,提供了引用的方式来操作.获取变量的引用,称为借用.类似于你借别人的东西来使用,但是这个东西的所有者不是你.引用不会发生所有权的转移. 引用的使用 在rust中,引用的语法非常简单.通过&来取引用,通过*来解引用.例如: fn main() { let s1: String = "Hello".to_string()

  • Rust应用调用C语言动态库的操作方法

    目录 外部功能接口FFI UDP套接字的读超时 Rust调用C语言动态库中的函数 避免重复造轮子,使用Rust官方C语言库 外部功能接口FFI 虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现.调用其他编程语言的接口,被称为Foreign Function Interface,直译为外部功能接口.该接口通常是调用C语言实现的外部功能模块,因为C语言接近于全能,几乎任何功能都能够实现:正如同使用汇编语言也可以实现很多功能一样,但开发效

  • Rust 所有权机制原理深入剖析

    目录 what's ownership? Scope (作用域) ownership transfer(所有权转移) move clone copy References and Borrowing(引用和借用) Mutable References(可变引用) Dangling References(悬垂引用) what's ownership? 常见的高级语言都有自己的 Garbage Collection(GC)机制来管理程序运行的内存,例如 Java.Go 等.而 Rust 引入了一种全

  • 向Rust学习Go考虑简单字符串插值特性示例解析

    目录 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 简单字符串插值 其他语言例子 Swift Kotlin C Rust 争论矛盾点 总结 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 在日常开发 Go 工程中,我们经常会用 fmt.Printf 或 fmt.Sprintf 去写类似的拼装字符串的业务. 如下代码: fmt.Printf("Hello Gopher %s, you are %d years old and you're favorite

  • Rust如何进行模块化开发技巧分享

    目录 Rust如何进行模块化开发? Package和Create Cargo的惯例 Create的作用 定义module来控制作用域和私有性 路径Path 私有边界(private boundary) pub关键字 super关键字 pub struct pub enum Use关键字 use的习惯用法 as关键字 使用 pub use 重新导出名称 导入外部包 如何将模块放入其他文件? 类似es6的模块化,Rust通过package.create.module来实现代码的模块化管理 Rust如

  • Dubbo本地开发技巧分享

    背景 作为后端服务负载.前后分离的主要手段,dubbo在业界中使用率还比较高.随着Dubbo系统的增多,本地开发.调试就出现了麻烦之处 直接在开发本地起同样一份服务 由于Dubbo采用负载均衡的策略,多次请求的情况下总会随机一次到本地的服务上,有点儿看命的感觉. 修改Group 作为Dubbo支持的分Group策略,把各自开发人员独立注册一个Gruop是可行的.这样需求把Consumer端和provider端同步进行修改,稍微有点儿麻烦. 而且还是会想注册中心注册一份服务列表. 直连 直连就是不

  • 7个很棒的Vue开发技巧分享

    目录 1.路由参数解耦 2.功能组件 3.样式范围 4.watch的高级使用 5.watch监听多个变量 6.事件参数$event 7.程序化事件监听器 1.路由参数解耦 通常在组件中使用路由参数,大多数人会做以下事情. export default { methods: { getParamsId() { return this.$route.params.id } } } 在组件中使用 $route 会导致与其相应路由的高度耦合,通过将其限制为某些 URL 来限制组件的灵活性.正确的做法是通

  • 八个一看就觉得很棒的Vue开发技巧分享

    目录 1.路由参数解耦 2.功能组件 3.样式范围 4.watch的高级使用 5.watch监听多个变量 6.事件参数$event 7.程序化事件监听器 8.监听组件生命周期 总结 1.路由参数解耦 通常在组件中使用路由参数,大多数人会做以下事情. export default { methods: { getParamsId() { return this.$route.params.id } } } 在组件中使用 $route 会导致与其相应路由的高度耦合,通过将其限制为某些 URL 来限制

  • PHP技术开发技巧分享

    1. 提高PHP的运行效率 PHP的优点之一是速度很快,对于一般的网站应用,可以说是已经足够了.不过如果站点的访问量很高.带宽窄或者其它的因素令服务器产生性能瓶颈的时候,你可能得想想其它的办法来进一步提高PHP的速度了. 1.1. 代码优化 1.用i+=1代替i=i+1.符合c/c++的习惯,效率还高. 2.尽可能的使用PHP内部函数.自己编写函数之前要详细查阅手册,看有没有相关的函数,否则费力不讨好. 3.能使用单引号字符串尽量使用单引号字符串.单引号字符串的效率要高于双引号字符串. 4.用f

  • JavaScript 模块化开发实例详解【seajs、requirejs库使用】

    本文实例讲述了JavaScript 模块化开发.分享给大家供大家参考,具体如下: JS开发的问题 冲突 依赖 JS引入的文件,产生依赖. 使用命名空间解决: 命名空间的弊端 调用的时候 名字比较长. 只能减低冲突,不能完全避免 SeaJs使用 引入sea.js的库 如何变成模块? define 如何调用模块? exports 和 seajs.use 如何依赖模块? require //html: <script src="js/sea.js" type="text/ja

  • ES6中module模块化开发实例浅析

    本文实例讲述了ES6中module模块化开发.分享给大家供大家参考,具体如下: 多人开发JavaScript时伴随着命名冲突等问题,先后有了模拟块级作用域.命名空间.模块化开发等方法. 之前,模块化开发一直是由第三方库来模拟的,比较知名的有AMD规范和CMD规范. 两个规范分别对应requirejs和seajs. 而现在,ES6提出了自己的模块化统一标准. 一个ES6的模块是一个包含了js代码的文件.ES6里没有所谓的module关键字,一个模块就是一个普通的脚本文件,除了以下两个区别: 1.

  • vue 搭建后台系统模块化开发详解

    本文主要介绍了vue 搭建后台系统模块化开发,分享给大家,具体如下: 效果 目录结构 ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── confi

  • 分享经典的JavaScript开发技巧

    JavaScript开发经典技巧分享给大家: 1.首次为变量赋值时务必使用var关键字 变量没有声明而直接赋值得话,默认会作为一个新的全局变量,要尽量避免使用全局变量. 2.使用===取代== ==和!=操作符会在需要的情况下自动转换数据类型.但===和!==不会,它们会同时比较值和数据类型,这也使得它们要比==和!=快. [10] === 10    // is false [10]  == 10    // is true '10' == 10     // is true '10' ===

  • 关于TypeScript开发的6六个实用小技巧分享

    目录 1. 开发之前确定实体类型 2. 请求接口时只需要定义自己需要用到的字段 3. 使用枚举类型 4. DOM元素的类型要正常给 5.对象的类型要怎么给 6.结构赋值时类型怎么给 总结 本文总结一下使用TypeScript开发应用程序的一点小经验.说之前,推荐一个VSCODE立即执行TS代码的插件quokka.js, 使用方式,ctrl+shipt+p,输入关键字quokka 回车之后,输入代码之后会立即执行 1. 开发之前确定实体类型 在正式编码之前,如果没有接口文档的话,最好能拿到数据字典

随机推荐