Rust入坑指南:有条不紊

随着咱们的坑愈来愈多,愈来愈大,咱们必需要对各类坑进行管理了。Rust为咱们提供了一套坑务管理系统,方便你们有条不紊的寻找、管理、填埋本身的各类坑。html

Rust提供给咱们一些管理代码的特性:编程

  • Packages: Cargo的一个特性,帮助你进行构建、测试和共享crates
  • Crates: 生成库或可执行文件的模块树
  • Modulesuse: 用于控制代码组织、范围和隐私路径
  • Paths: struct、function和module的命名方法

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

Packages和Crates

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

以前咱们老是经过IDEA来新建项目,今天咱们换个方法,在命令行中使用cargo命令来建立。函数

$ cargo new hello-world
     Created binary (application) `hello-world` package
$ ls hello-world
Cargo.toml
src
$ ls hello-world/src
main.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。ui

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

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函数:

05-1

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

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

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

05-2

这样咱们就能够在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文件一样只保留声明便可。

拆分后的文件目录如图

rust05-3

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

ps:本文的代码示例均来自the book

相关文章
相关标签/搜索