作者 | Jackyzhe

责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

随着我们的坑越来越多,越来越大,我们必须要对各种坑进行管理了。Rust为我们提供了一套坑务管理系统,方便大家有条不紊的寻找、管理、填埋自己的各种坑。

Rust提供给我们一些管理代码的特性:

  • Packages:Cargo的一个特性,帮助你进行构建、测试和共享crates

  • Crates:生成库或可执行文件的模块树

  • Modules 和use:用于控制代码组织、范围和隐私路径

  • Paths:struct、function和module的命名方法

下面我们来具体看一下这些特性是如何帮助我们组织代码的。

Packages和Crates

package可以理解为一个项目,而crate可以理解为一个代码库。crate可以供多个项目使用。那我们的项目中package和crate是怎么定义的呢?

之前我们总是通过IDEA来新建项目,今天我们换个方法,在命令行中使用cargo命令来创建。

$ cargo new hello-world     Created binary (application) `hello-world` package$ ls hello-worldCargo.tomlsrc$ ls hello-world/srcmain.rs

可以看到,我们使用cargo创建项目后,只有两个文件,Cargo.toml和src目录下的main.rs。

Cargo.toml是管理项目依赖的文件,每个Cargo.toml定义一个package。main.rs文件的存在表示package中包含一个二进制crate,它是二进制crate的入口文件,crate的名称和package相同。如果src目录下存在lib.rs文件,说明package中包含一个和package名称相同的库crate。

一个package可以包含多个二进制crate,它们由src/lib目录下的文件定义。如果你的项目想引用他人的crate,可以在Cargo.toml文件中增加依赖。每个crate都有自己的命名空间,因此如果你引入了一个crate里面定义了一个名为hello的函数,你仍然可以在自己的crate中再定义一个名为hello的函数。

Module

Module帮助我们在crate中组织代码,同时Module也是封装代码的重要工具。接下来还是通过一个栗子来详细了解Module。

前面我们说过,库crate定义在src/lib.rs文件中。这里首先创建一个包含了库crate的package:

cargo new --lib restaurant

然后在src中定义一些module和函数。

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() {}    }}

可以看到我们使用关键字mod来定义Module,Module中可以继续定义Module或函数。这样我们就可以比较方便的把相关的函数放到一个Module中,并为Module命名,提高代码的可读性。另外Module中还可以定义struct和枚举。由于Module中可以嵌套定义子Module,最终我们定义出来的代码类似一个树形。

那么如何访问Module中的函数呢?这就要提到Path了。这部分比较好理解,Module树相当于系统文件目录,而Path则是目录的路径。

Path

这里的路径和系统文件路径一样,都分为相对路径和绝对路径两种。其中绝对路径必须以crate开头,因为它代码整个Module树的根节点。路径之间使用的是双冒号来表示引用。

现在我来尝试在一个函数中调用add_to_waitlist函数:

可以看到这里不管用绝对路径还是相对路径都报错了,错误信息是模块hosting和函数add_to_waitlist是私有(private)的。我们先暂时放下这个错误,根据这里的错误提示,我们知道了当我们定义一个module时,默认情况下是私有的,我们可以通过这种方法来封装一些代码的实现细节。

OK,回到刚才的问题,那我们怎么才能解决这个错误呢?地球人都知道应该把对应的模块与函数公开出来。Rust中标识模块或函数为公有的关键字是pub。

我们用pub关键字来把对应的模块和函数公开:

这样我们就可以在module外来调用module内的函数了。

Rust中的私有规则

现在我们再回过头来看Rust中的一些私有规则,如果你试验了上面的例子,也许会有一些发现。

Rust中私有规则适用于所有项(函数、方法、结构体、枚举、模块和常量),它们默认都是私有的。父模块中的项不能访问子模块中的私有项,而子模块中的项可以访问其祖辈(父模块及以上)中的项。

Struct和Enum的私有性

Struct和Enum的私有性略有不同,对于Struct来讲,我可以只将其中的某些字段设置为公有的,其他字段可以仍然保持私有。

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

而对于Enum,如果一个Enum是公有的,那么它的所有值都是公有的,因为私有的值没有意义。

相对路径和绝对路径的选择

这种选择不存在正确与否,只有是否合适。因此这里我们只是举例说明一些合适的情况。

我们仍以上述代码为例,如果我们可以预见到以后需要把front_of_house模块和eat_at_restaurant函数移动到一个新的名为customer_experience的模块中,就应该使用相对路径,这样我们就对其进行调整。

类似的,如果我们需要把eat_at_restaurant函数移动到dining模块中,那么我们选择绝对路径的话就不需要做调整。

综上,我们需要对代码的优化方向有一些前瞻性,并以此来判断需要使用相对路径还是绝对路径。

相对路径除了以当前模块开头外,还可以以super开头。它表示的是父级模块,类似于文件系统中的两个点(..)。

use关键字

绝对路径和相对路径可以帮助我们找到指定的函数,但用起来也非常的麻烦,每次都要写一大长串路径。还好Rust为我们提供了use关键字。在很多语言中都有import关键字,这里的use就有些类似于import。不过Rust会提供更加丰富的用法。

use最基本的用法就是引入一个路径。我们就可以更加方便的使用这个路径下的一些方法:

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

use crate::front_of_house::hosting;

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

这个路径可以是绝对路径,也可以是相对路径,但如果是相对路径,就必须要以self开头。上面的例子可以写成:

use self::front_of_house::hosting;

这与我们前面讲的相对路径似乎有些矛盾,Rust官方说会在之后的版本处理这个问题。

use还可以更进一步,直接指向具体的函数或Struct或Enum。但习惯上我们使用函数时,use后面使用的是路径,这样可以在调用函数时知道它属于哪个模块;而在使用Struct/Enum时,则具体指向它们。当然,这只是官方建议的编程习惯,你也可以有自己的习惯,不过最好还是按照官方推荐或者是项目约定的规范比较好。

对于同一路径下的某些子模块,在引入时可以合并为一行,例如:

use std::io;use std::cmp::Ordering;// 等价于use std::{cmp::Ordering, io};

有时我们还会遇到引用不同包下相同名称Struct的情况,这时有两种解决办法,一是不指定到具体的Struct,在使用时加上不同的路径;二是使用as关键字,为Struct起一个别名。

方法一:

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

fn function1() -> fmt::Result {    // --snip--}

fn function2() -> io::Result<()> {    // --snip--}

方法二:

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

fn function1() -> Result {    // --snip--}

fn function2() -> IoResult<()> {    // --snip--}

如果要导入某个路径下的全部模块或函数,可以使用*来表示。当然我是非常不建议使用这种方法的,因为导入全部的话,如果出现名称冲突就会很难排查问题。

对于外部的依赖包,我们需要先在Cargo.toml文件中添加依赖,然后就可以在代码中使用use来引入依赖库中的路径。Rust提供了一些标准库,即std下的库。在使用这些标准库时是不需要添加依赖的。

有些同学看到这里可能要开始抱怨了,说好了介绍怎么拆分文件,到现在还是在一个文件里玩,这不是欺骗读者嘛。

别急,这就开始拆分。

开始拆分

我们拿刚才的一段代码为例:

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() {}    }}

首先我们可以把front_of_house模块下的内容拆分出去,需要在src目录下新建一个front_of_house.rs文件,然后把front_of_house模块下的内容写到文件中。lib.rs文件中,只需要声明front_of_house模块即可,不需要具体的定义。声明模块时,将花括号即内容改为分号就可以了。

mod front_of_house;

然后我们可以继续拆分front_of_house模块下的hosting模块和serving模块,这时需要新建一个名为front_of_house的文件件,在该文件夹下放置要拆分的模块的同名文件,把模块定义的内容写在文件中,front_of_house.rs文件同样只保留声明即可。

拆分后的文件目录如图:

本文主要讲了Rust中Package、Crate、Module、Path的概念和用法,有了这些基础,我们后面才有可能开发一些比较大的项目。

ps:本文的代码示例均来自the book(https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)。

版权声明:本文为 CSDN博主「Jackyzhe」的原创文章。

想为博主点赞?

想要请教博主?

扫描下方二维码,快速获取与博主直面沟通的方式吧!

热 文 推 荐 

传 ofo 年底裁员超 50%;Vivo 支持谷歌 Fuchsia OS;Spring Boot 2.2.2 发布 | 极客头条

☞程序员的救星-ThinkPad T490 对比体验

程序员都应该了解的一种数据格式之 JSON

☞“给微软的 10 条建议!”

☞华为生产不含美国芯片的手机!

☞抢饭碗?00 后程序员来了!

☞微软张若非:搜索引擎和广告系统,那些你所不知的AI落地技术

☞【图解】记一次手撕算法面试:字节跳动的面试官把我四连击了

点击阅读原文,查看博主精彩分享!

你点的每个“在看”,我都认真当成了喜欢

Rust 入坑指南 | CSDN 博文精选相关推荐

  1. Rust 入坑指南:鳞次栉比 | CSDN 博文精选

    作者 | Jackyzhe 责编 | 屠敏 出品 | CSDN(ID:CSDNnews) 很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉 ...

  2. Rust入坑指南:齐头并进(上)

    我们知道,如今CPU的计算能力已经非常强大,其速度比内存要高出许多个数量级.为了充分利用CPU资源,多数编程语言都提供了并发编程的能力,Rust也不例外. 聊到并发,就离不开多进程和多线程这两个概念. ...

  3. Rust入坑指南:朝生暮死

    今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...

  4. Rust入坑指南:亡羊补牢

    如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...

  5. Rust入坑指南:鳞次栉比

    很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...

  6. Rust入坑指南:核心概念

    如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...

  7. Rust入坑指南:千人千构

    坑越来越深了,在坑里的同学让我看到你们的双手! 前面我们聊过了Rust最基本的几种数据类型.不知道你还记不记得,如果不记得可以先复习一下.上一个坑挖好以后,有同学私信我说坑太深了,下来的时候差点崴了脚 ...

  8. Rust入坑指南:常规套路

    搭建好了开发环境之后,就算是正式跳进Rust的坑了,今天我就要开始继续向下挖了. 由于我们初来乍到 ,对Rust还不熟悉,所以我决定先走一遍常规套路. 变不变的变量 学习一门语言第一个要了解的当然就是 ...

  9. Rust入坑指南:坑主驾到

    欢迎大家和我一起入坑Rust,以后我就是坑主,我主要负责在前面挖坑,各位可以在上面看,有手痒的也可以和我一起挖.这个坑到底有多深?我也不知道,我是抱着有多深就挖多深的心态来的,下面我先跳了,各位请随意 ...

最新文章

  1. 超赞!2021年最惊艳的38篇AI论文!
  2. C#编码简单性之代码篇(如何编写简短的C#代码,随时更新)
  3. C++中拼接一个包含整型等类型字符串的方法
  4. Zookeeper基于Java 访问-节点权限设置
  5. anaconda下载的python在哪_Anaconda下Python环境下载及安装
  6. 《C++ Primer》7.3.4节练习
  7. 【转】SharePoint 2013 开发——开发并部署webpart
  8. 密立根油滴实验的计算机仿真实验报告,H-D光谱实验的计算机仿真
  9. LeetCode--70. 爬楼梯(动态规划)
  10. Java可变参数 Python可变参数 Scala可变参数
  11. Centos7 Zabbix3.2安装(yum)
  12. python 离线安装numpy_Python本地安装numpy包
  13. 【渝粤教育】电大中专消费者行为学_1作业 题库
  14. 天翼云RDS数据库如何修改数据库参数
  15. 简单的机械臂设计(Splay树)
  16. 条码打印软件制作数字+字母的流水号二维码
  17. 基于归一化互相关函数的语音基音周期检测
  18. Spark的spark-*和blockmgr-*目录里是什东西,怎么来的
  19. mysql week函数参数_MySQL week()函数详解
  20. jmeter使用BeanShell Sampler测试自己写的java接口(一)

热门文章

  1. 分布式数据库中全局唯一主键
  2. IT人才需要怎样的沟通表达能力
  3. 【转】C#中没有id 没有name C#怎么点击按钮
  4. 向VMware中的Linux虚拟机共享Windows宿主机的文件/文件夹
  5. [论文阅读] Looking for the Detail and Context Devils: High-Resolution Salient Object Detection
  6. 【django】数据库初始化写入信息
  7. 探索pytest的fixture(上)
  8. 中国吉非替尼行业市场供需与战略研究报告
  9. 做游戏,学编程(C语言) 20 扫雷
  10. 2017级C语言大作业 - 密室逃生