Rust项目的代码组织
学习一种编程语言时,常常优先关注在语言的语法和标准库上,希望能够尽快用上新语言来开发,
我自己学习新的开发语言时也是这样。
不过,想用一种新的语言去开发实际的项目,或者自己做点小工具的话,除了语言本身之外,
了解它在项目中如何组织代码也是至关重要的。
毕竟在实际项目中,不可能像学习语言时那样,常常只有一个代码文件。
本文主要介绍Rust
语言中常见的代码组织方式,我用Rust
的时间也不长,不当之处,敬请指正。
1. 管理代码的单元
Rust
在语言层面提供了3个代码管理单元,分别是:
module
:模块,类似于其他语言中的命名空间,一般是封装了某个具体功能的实现。crate
:Rust
编译的最小单元,每个crate
中有一个Cargo.toml
文件来配置crate
相关信息,一个`crate`可以是一个库也可以是一个二进制。
package
:一个package
可以视为一个项目,cargo new
命令创建的就是package
。
这三者的关系是一个package可以包含多个crate,一个crate可以包含多个module。
对于一般的项目来说,常见的代码组织形式是一个crate+多个module。
如果功能复杂,业务比较多的话,可以将独立的功能或者业务封装成crate
,变成一个package
包含多个crate+多个module。
比如官方推荐用来学习Rust的项目 ripgrep(https://github.com/BurntSushi/ripgrep)。
就是多crate
的项目,每个crate
中有自己的Cargo.toml
配置。
2. 模块(module)
package
,crate
和module
三种组织代码的方式中,最常用的就是module
。
下面主要介绍Rust
中module
的定义和引用方式。
首先,我们创建一个演示用的项目。
$ cargo new myproj
Creating binary (application) `myproj` package
默认项目的目录结构如下:
2.1. 同文件中的模块
Rust
的模块不一定要定义在单独的文件中,同一个文件中可以定义多个不同的模块。
这一点和有些按照文件和目录来区分模块的语言是不一样的。
比如,下面在main.rs
中定义了两个模块data
和compute
:
mod data {
pub fn fetch_data() -> Vec<u32> {
vec![1, 2, 3]
}
}
mod compute {
pub fn square_data(data: &Vec<u32>) -> Vec<u32> {
let mut new_data: Vec<u32> = vec![];
for i in data {
new_data.push(i.pow(2));
}
new_data
}
}
use compute::square_data;
use data::fetch_data;
fn main() {
let data = fetch_data();
println!("fetch data: {:?}", data);
let new_data = square_data(&data);
println!("square data: {:?}", new_data);
}
引用同一文件中的模块很简单,直接use
模块和对应的方法即可。
use compute::square_data;
use data::fetch_data;
执行效果如下:
也许你会有这样的疑问,既然都在同一个文件中,为什么还要定义模块,直接定义两个函数不就可以了,
这样main
函数中直接就可以使用,还省得去引用模块。
因为对于一些代码比较短,但是通用性高的功能函数(比如简单的字符串转换,日期格式转换),
每个函数都创建一个模块文件的话,略显繁琐,统一在一个文件中定义反而更加清爽简洁。
以后如果模块中的功能越来越多,越来越复杂,再将此模块重构到一个单独的文件中也不迟。
2.2. 同目录中的模块
同文件中的模块引用非常简单,但是当模块中的代码逐渐增多之后,就需要考虑用一个单独的文件来定义模块。
比如,将上面代码中模块data
和compute
中的函数分别放入不同的文件。
文件结构如下:
其中 src 目录下的3个文件中的内容分别为:
// data.rs 文件
pub fn fetch_data() -> Vec<u32> {
vec![1, 2, 3]
}
// compute.rs 文件
pub fn square_data(data: &Vec<u32>) -> Vec<u32> {
let mut new_data: Vec<u32> = vec![];
for i in data {
new_data.push(i.pow(2));
}
new_data
}
// main.rs
mod compute;
mod data;
use compute::square_data;
use data::fetch_data;
fn main() {
let data = fetch_data();
println!("fetch data: {:?}", data);
let new_data = square_data(&data);
println!("square data: {:?}", new_data);
}
注意这里与上一节同文件中模块的几点不同。
首先,在模块的文件data.rs
和compute.rs
中,直接定义函数即可,
不用加上 mod data{}
这样的模块名,因为文件名data
和compute
会自动作为模块的名字。
其次,在main.rs
文件中引用不同文件的模块时,
需要先定义模块(相当于导入了data.rs
和compute.rs
),
mod compute;
mod data;
然后在 use
其中的函数。
use compute::square_data;
use data::fetch_data;
2.3. 不同目录中的模块
最后在看看不同目录中的模块如何引用。
当项目功能逐渐增多和复杂之后,一个功能模块就不止一个文件了,所以就需要将模块封装到不同的目录中。
比如一个普通的Web系统,其中日志模块,数据库模块等等都会单独封装到不同的目录中。
重新修改示例中的文件,代码改为如下的结构:
封装一个日志模块,其中有2个输出日志的代码文件(debug.rs
和info.rs
),分别输出不同级别的日志。
然后在代码中引用日志模块并输出日志。
各个文件的代码分别如下:
// debug.rs 文件
pub fn log_msg(msg: &str) {
println!("[DEBUG]: {}", msg);
}
// info.rs 文件
pub fn log_msg(msg: &str) {
println!("[INFO]: {}", msg);
}
// main.rs 文件
mod log;
use crate::log::debug;
use crate::log::info;
fn main() {
debug::log_msg("hello");
info::log_msg("hello");
}
注意,除了上面这3个文件,在log
文件夹中还有个mod.rs
文件,
这个文件至关重要,其中的内容就是定义log
文件夹中有哪些模块。
// mod.rs 文件
pub mod debug;
pub mod info;
有了这个文件,log文件夹才会被Rust
当成是一个模块,才可以在main.rs
中引用mod log;
。
执行效果如下:
接下来,再增加一个database
模块,让database
模块引用log
模块中的函数,
也就是兄弟模块间的引用。
文件目录结构变为:
新增的database
模块中两个文件中的代码如下:
// db.rs
use crate::log::debug;
use crate::log::info;
pub fn create_db() {
debug::log_msg("start to create database");
info::log_msg("Create Database");
debug::log_msg("success to create database");
}
// mod.rs
pub mod db;
main.rs
中的代码如下:
// main.rs
mod database;
mod log;
use crate::database::db;
fn main() {
db::create_db();
}
执行结果如下:
这里有一点需要注意,database
模块的db.rs
中直接引用了log
模块中的函数,
需要在main.rs
中定义mod log;
。
虽然main.rs中
没有直接使用log
模块中的内容,仍然需要定义mod log;
否则database
模块中的db.rs
无法引用log
模块中的函数。
2.4. 模块的相对和绝对引用
Rust模块的引用有两种方式,相对路径的引用和绝对路径的引用。
上面的示例中都是绝对引用,比如:use crate::log::debug;
,use crate::database::db;
。
绝对引用以crate
开头,可以把crate
理解为的代码的根模块。
相对引用有两个关键字,self
和super
,self
表示同级的模块,super
表示上一级的模块。
比如,上面示例中main.rs
和db.rs
文件中的模块也可以改为相对引用:
//main.rs
// use crate::database::db;
use self::database::db;
//db.rs
// use crate::log::debug;
// use crate::log::info;
use super::super::log::debug;
use super::super::log::info;
3. 包(crate)
crate
是Rust
编译和发布的最小单元,一般项目都是单crate多module的,
如果项目中某些模块通用型强,可能会单独发布给其他项目用,那么把这些模块封装成crate
也是不错的想法。
用Rust
时间不长,还没有实际用到多crate
的情况,感兴趣的话可以参考ripgrep项目(https://github.com/BurntSushi/ripgrep)。
4. 总结
本文重点介绍了Rust
中模块(module
)的定义和引用方式,目的为了让我们在使用Rust时能够合理的组织和重构自己的代码。
Rust项目的代码组织的更多相关文章
- nodejs实践-代码组织
nodejs实践-代码组织 laiqun@msn.cn Contents 1. 代码组织 1. 代码组织 更新版本 npm install -g n n latest 项目文件组织 MVC 前后端代码 ...
- 第一部分 代码组织概念,集成开发环境(IDE)
代码组织概念 主要是代码文件,项目和解决方案. 解决方案(.sln)包含多个项目(.csproj),一个项目又包含多个文件(.cs). 集成开发环境(IDE): 由编辑.编译.调试,以及用户图形界面, ...
- 前端代码组织优化--小demo(进阶你的思路)
事出必有因 最近在看老项目的代码,一个富客户端的js代码,几千行的代码,全是function(){} var...的垂直布局,真的是要感动的哭了. 一开始都是这样,想实现什么功能,不管三七二十一,fu ...
- OpenDaylight开发hello-world项目之代码框架搭建
OpenDaylight开发hello-world项目之开发环境搭建 OpenDaylight开发hello-world项目之开发工具安装 OpenDaylight开发hello-world项目之代码 ...
- web手工项目01-系统组织框架-测试流程-需求评审-测试计划与方案
回顾 SVN(定义,作用,使用操作) 软件缺陷(定义,表现形式,原因和根源,基本内容,跟踪流程) JIRA(基本介绍,使用者,工作流,问题,使用) 学习目标 掌握WAMP的环境搭建 掌握熟悉项目的步骤 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(16)|代码组织与模块化]
[易学易懂系列|rustlang语言|零基础|快速入门|(16)|代码组织与模块化] 实用知识 代码组织与模块化 我们知道,在现代软件开发的过程中,代码组织和模块化是应对复杂性的一种方式. 今天我们来 ...
- Android Stduio统计项目的代码行数
android studio统计项目的代码行数的步骤如下: 1)按住Ctrl+Shift+A,在弹出的框输入‘find’,然后选择Find in Path.(或者使用快捷键Ctrl+Shift+F) ...
- VS Bug 当获取其他项目的代码时, F5 无法进入调试模式. 也不报错....
在64位的机子下, 被获用的项目使用X86时会出现. 就会出现 F5 无法进入调试模式. 也不报错.... 打断点也没有用. 在不加入X86项目的代码时, 又可以运行.. 解决方案: 检查 ...
- 花20分钟写的-大白话讲解如何给github上项目贡献代码
原文地址:http://site.douban.com/196781/widget/notes/12161495/note/269163206/ 本文献给对git很迷茫的新手,注意是新手,但至少会点基 ...
- 前端JS面试题汇总 Part 2 (null与undefined/闭包/foreach与map/匿名函数/代码组织)
原文:https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questio ...
随机推荐
- Windows Server 2016 离线安装.NET Framework 3.5
Windows Server 2016 离线安装.NET Framework 3.5 1.双击Windows Server 2016的ISO,会自动挂载,比如F盘. 2.右键开始菜单- 命令提示符(管 ...
- Lucene demo演示搜索查询歌手,歌名,歌词
1.导入pom jar文件 <dependency> <groupId>org.apache.lucene</groupId> <artifactId> ...
- AGE SORT
AGE SORT 你有所有城市的人的年齡資料,而且這城市的人們都大於1歲,且都不會活超過100歲.現在你有個簡單的任務以升冪去排序所有的年齡 Input 接下來會有很多筆的資料,每筆資料從輸入n 開始 ...
- spring boot jpa 进行通用多条件动态查询和更新 接口
原因: jpa 没有类似于mybatis的那种 拼接sql的方式 想动态更新 需要使用 CriteriaUpdate的方式 去一直拼接,其实大多数场景只要传入一个非空实体类,去动态拼接sql 1.定义 ...
- 简约-Markdown教程
##注意 * 两个元素之间最好有空行 * 利用\来转义 我是一级标题 ==== 我是二级标题 ---- #我是一级标题 ##我是二级标题 ##<center>标题居中显示</cent ...
- Android无障碍自动化结合opencv实现支付宝能量自动收集
Android无障碍服务可以操作元素,手势模拟,实现基本的控制.opencv可以进行图像识别.两者结合在一起即可实现支付宝能量自动收集.opencv用于识别能量,无障碍服务用于模拟手势,即点击能量. ...
- 面试官:告诉我为什么static和transient关键字修饰的变量不能被序列化?
一.写在开头 在上一篇学习序列化的文章中我们提出了这样的一个问题: "如果在我的对象中,有些变量并不想被序列化应该怎么办呢?" 当时给的回答是:不想被序列化的变量我们可以使用tra ...
- application.properties数据库连接字符串
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://loca ...
- SpringBoot2.X新版本配置拦截器在项目中的使用
拦截器:和过滤器用途基本类似 SpringBoot2.X新版本配置拦截器 implements WebMvcConfigure 自定义拦截器 HandlerInterceptor preHandle: ...
- 全网最适合入门的面向对象编程教程:07 类和对象的Python实现-类型注解-提高代码可读性的利器
全网最适合入门的面向对象编程教程:07 类和对象的 Python 实现-类型注解-提高代码可读性的利器 摘要: 本文对类型注解的定义.使用原因进行了基本介绍,同时对使用 typing 模块实现类型提示 ...