使用包、Crate 和模块管理项目(上)

目录

1、包和Crate

2、定义模块来控制作用域与私有性

2.1 在模块中对相关代码进行分组

3、引用模块项目的路径

3.1 使用 pub 关键字暴露路径

二进制和库 crate 包的最佳实践

3.2 super 开始的相对路径

3.3 创建公有的结构体和枚举


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

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

1、包和Crate

crate 是 Rust 在编译时最小的代码单位。如果你用 rustc 而不是 cargo 来编译一个文件,编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译,我们会在接下来的章节中遇到。

crate 有两种形式:二进制项和库。二进制项 可以被编译为可执行程序,比如一个命令行程序或者一个服务器。它们必须有一个 main 函数来定义当程序被执行的时候所需要做的事情。目前我们所创建的 crate 都是二进制项。

 并没有 main 函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。大多数时间说的 crate 指的都是库,这与其他编程语言中 library 概念一致。

package)是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。

包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。

当我们创建一个项目的时候,看看发生了什么?

cargo new my-project

通过上图,我们可知Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc 来实际构建库或者二进制项目。

在此,我们有了一个只包含 src/main.rs 的包,意味着它只含有一个名为 my-project 的二进制 crate。如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate:一个二进制的和一个库的,且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。

2、定义模块来控制作用域与私有性

在这里我们看下模块相关的一些知识点:

  • 从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。
  • 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码:
    • 内联,在大括号中,当mod garden后方不是一个分号而是一个大括号
    • 在文件 src/garden.rs
    • 在文件 src/garden/mod.rs
  • 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:
    • 内联,在大括号中,当mod vegetables后方不是一个分号而是一个大括号
    • 在文件 src/garden/vegetables.rs
    • 在文件 src/garden/vegetables/mod.rs
  • 模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。
  • 私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用pub mod替代mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub
  • use 关键字: 在一个作用域内,use关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的作用域,你可以通过 use crate::garden::vegetables::Asparagus;创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型。

在前面的项目中,我们可以创建对应的模块和子模块,项目目录结构如下所示:

根据以上树型结构,我们先看一下src/main.rs 文件的内容。

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {
        color: String::from("green"),
        number: 10,
    };
    println!("{:?}!", plant);
}

pub mod garden,定义了一个菜园的公共模块,告诉rust编译器这个模块对应的文件是src/garden.rs,该文件对应的内容如下所示:

pub mod vegetables;

pub mod vegetables;声明了一个蔬菜的子模块,所以对应的模块文件时src/garden/vegetables.rs,在这个模块中,我们定义了一个结构体(芦笋,包含颜色,个数2个属性)如下所示:

#[derive(Debug)]
pub struct Asparagus {
    pub color: String,
    pub number: i32,
}

最后我们执行以下,main.rs中函数,结果如下所示:

2.1 在模块中对相关代码进行分组

模块 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的 私有性。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖与它们。

我们可以把前面的代码放入 src/lib.rs 中,来定义一些模块和函数。

pub mod garden {
    pub mod vegetables {
        #[derive(Debug)]
        pub struct Asparagus {
            pub color: String,
            pub number: i32,
        }
    }
}

在main.rs中,改写如下:

use my_project::garden::vegetables::Asparagus;
fn main() {
    let plant = Asparagus {
        color: String::from("green"),
        number: 10,
    };
    println!("res {:?}!", plant);
}

在src/lib.rs 中,我们可以把相关模块组合起来,菜园模块里包括蔬菜模块,蔬菜模块里包括芦笋的结构体,这样做我们可以把相关性功能进行聚合,也能便于以后项目的维护,同时也能使代码具有更好的可读性。

在前面我们提到了,src/main.rs 和 src/lib.rs 叫做 crate 根。之所以这样叫它们是因为这两个文件的内容都分别在 crate 模块结构的根组成了一个名为 crate 的模块,该结构被称为 模块树module tree)。

crate
└─ garden
   └─ vegetables
      └─ Asparagus

3、引用模块项目的路径

ust 如何在模块树中找到一个项的位置,我们使用路径的方式,就像在文件系统使用路径一样。为了调用一个函数,我们需要知道它的路径。

路径有两种形式:

  • 绝对路径absolute path)是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值 crate 开头。
  • 相对路径relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。

在文件名:src/lib.rs 中,看下示例绝对路径和相对路径的条用:

pub mod garden {
    pub mod vegetables {
        #[derive(Debug)]
        pub struct Asparagus {
            pub color: String,
            pub number: i32,
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };

    // 相对路径
    garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };
}

在 Rust 中,默认所有项(函数、方法、结构体、枚举、模块和常量)对父模块都是私有的。如果希望创建一个私有函数或结构体,你可以将其放入一个模块。所以在以上代码中,我们增加pub关键字,如果不添加,运行会报以下错误:

3.1 使用 pub 关键字暴露路径

上图中,告诉我们 vegetables模块是私有的。如果我们在定义的时候,给对应的模块加上关键字pub 然后再看下结果:

pub mod garden {
    pub mod vegetables {
        #[derive(Debug)]
        struct Asparagus {
            color: String,
            number: i32,
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };

    // 相对路径
    garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };
}

运行一下,结果如下所示:

根据上图中的结果,我们发现虽然模块添加了关键字pub,但是运行的时候还是报错,错误提示说Asparagus 是私有的结构体。如果这样那么我们需要在定义的结构体前面也需要添加pub关键字,如下所示:

pub mod garden {
    pub mod vegetables {
        #[derive(Debug)]
        pub struct Asparagus {
            color: String,
            number: i32,
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };

    // 相对路径
    garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };
}

运行一下,结果如下所示:

根据上图结果所示,提示结构体中定义的字段也是私有,那么我们可以把对应的字段也添加上关键字pub。

二进制和库 crate 包的最佳实践

我们提到过包可以同时包含一个 src/main.rs 二进制 crate 根和一个 src/lib.rs 库 crate 根,并且这两个 crate 默认以包名来命名。通常,这种包含二进制 crate 和库 crate 的模式的包,在二进制 crate 中只有足够的代码来启动一个可执行文件,可执行文件调用库 crate 的代码。又因为库 crate 可以共享,这使得其它项目从包提供的大部分功能中受益。

模块树应该定义在 src/lib.rs 中。这样通过以包名开头的路径,公有项就可以在二进制 crate 中使用。二进制 crate 就完全变成了同其它 外部 crate 一样的库 crate 的用户:它只能使用公有 API。这有助于你设计一个好的 API;

3.2 super 开始的相对路径

我们可以通过在路径的开头使用 super ,从父模块开始构建相对路径,而不是从当前模块或者 crate 根开始。这类似以 .. 语法开始一个文件系统路径。使用 super 允许我们引用父模块中的已知项,这使得重新组织模块树变得更容易 —— 当模块与父模块关联的很紧密,但某天父模块可能要移动到模块树的其它位置。

我们看一下如下示例,就很容易理解了。

pub mod garden {
    pub mod vegetables {
        fn get_asparagus() {
            super::get_garden()
        }
    }
    fn get_garden() {
        println!("getGarden ...")
    }
}

所以我们可以使用 super 进入父级模块,就可以引用父级模块中的一些函数、结构体等。

3.3 创建公有的结构体和枚举

我们还可以使用 pub 来设计公有的结构体和枚举,不过关于在结构体和枚举上使用 pub 还有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 pub ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。所以在之前的示例中如果没有把结构体中的变量声明为公有,所以后面就报错了,为了程序运行对引用结构体的变量也要声明为公有的,如下所示:

pub mod garden {
    pub mod vegetables {
        #[derive(Debug)]
        pub struct Asparagus {
            pub color: String,
            pub number: i32,
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };

    // 相对路径
    garden::vegetables::Asparagus {
        color: String::from("green"),
        number: 10,
    };
}

如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 enum 关键字前面加上 pub,如下所示:

pub mod people {
    pub enum Sex {
        Man,
        Woman,
    }
}
fn get_sex() {
    let man = people::Sex::Man;
}

总结:

因此枚举成员默认就是公有的。结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub 关键字。

猜你喜欢

转载自blog.csdn.net/u014388408/article/details/134835978
今日推荐