rust学习之包和模块

Rust 为我们提供了强大的包管理工具:

  • 项目(Package):可以用来构建、测试和分享包
  • 工作空间(WorkSpace):对于大型项目,可以进一步将多个包联合在一起,组织成工作空间
  • 包(Crate):一个由多个模块组成的树形结构,可以作为三方库进行分发,也可以生成可执行文件进行运行
  • 模块(Module):可以一个文件多个模块,也可以一个文件一个模块,模块可以被认为是真实项目中的代码组织单元

包和Package 

对于 Rust 而言,包是一个独立的可编译单元,它编译后会生成一个可执行文件或者一个库。一个包会将相关联的功能打包在一起,使得该功能可以很方便的在多个项目中分享。同一个包中不能有同名的类型。 

由于 Package 就是一个项目,因此它包含有独立的 Cargo.toml 文件,以及因为功能性被组织在一起的一个或多个包。一个 Package 只能包含一个库(library)类型的包,但是可以包含多个二进制可执行类型的包。

创建一个二进制 Package

$ cargo new my-project

Created binary (application) `my-project` package

$ ls my-project

Cargo.toml

src

$ ls my-project/src

main.rs 

Cargo 为我们创建了一个名称是 my-project 的 Package,同时在其中创建了 Cargo.toml 文件,src/main.rs 是二进制包的根文件,该二进制包的包名跟所属 Package 相同,在这里都是 my-project,所有的代码执行都从该文件中的 fn main() 函数开始。 

建一个库类型的 Package

$ cargo new my-lib --lib

Created library `my-lib` package

$ ls my-lib

Cargo.toml

src

$ ls my-lib/src

lib.rs

如果一个 Package 包含有 src/lib.rs,意味它包含有一个库类型的同名包 my-lib,该包的根文件是 src/lib.rs。库类型的 Package 只能作为三方库被其它项目引用,而不能独立运行,只有之前的二进制 Package 才可以运行。 

牢记 Package 是一个项目工程,而包只是一个编译单元,src/main.rs 和 src/lib.rs 都是编译单元,因此它们都是包。

一个真实项目中典型的 Package,会包含多个二进制包,这些包文件被放在 src/bin 目录下,每一个文件都是独立的二进制包,同时也会包含一个库包,该包只能存在一个 src/lib.rs

.
├── Cargo.toml
├── Cargo.lock
├── src
│   ├── main.rs
│   ├── lib.rs
│   └── bin
│       └── main1.rs
│       └── main2.rs
├── tests
│   └── some_integration_tests.rs
├── benches
│   └── simple_bench.rs
└── examples
    └── simple_example.rs
  • 唯一库包:src/lib.rs
  • 默认二进制包:src/main.rs,编译后生成的可执行文件与 Package 同名
  • 其余二进制包:src/bin/main1.rs 和 src/bin/main2.rs,它们会分别生成一个文件同名的二进制可执行文件
  • 集成测试文件:tests 目录下
  • 基准性能测试 benchmark 文件:benches 目录下
  • 项目示例:examples 目录下

模块Module 

使用模块,我们就能将功能相关的代码组织到一起,然后通过一个模块名称来说明这些代码为何被组织在一起。使用 mod 关键字来创建新模块,后面紧跟着模块名称。模块可以嵌套,所有模块均定义在同一个文件中。

// 餐厅前厅,用于吃饭
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() {}
    }
pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();

    // 相对路径
    front_of_house::hosting::add_to_waitlist();
}
}
模块树:
crate
 └── eat_at_restaurant
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

 其中 crate 包根是 src/lib.rs 文件,包根文件中的三个模块分别形成了模块树的剩余部分。

如果想调用一个函数或者模块,rust有两种形式:

  • 绝对路径,从包根开始,路径名以包名或者 crate 作为开头,逐层引用,每一层之间使        用 :: 分隔,例如:crate::front_of_house::hosting::add_to_waitlist();
  • 相对路径,从当前模块开始,以 selfsuper 或当前模块的标识符作为开头,例如:front_of_house::hosting::add_to_waitlist(); 

Rust 出于安全的考虑,默认情况下,所有的类型都是私有化的,父模块完全无法访问子模块中的私有项,但是子模块却可以访问父模块、父父..模块的私有项。 Rust 提供了 pub 关键字,通过它你可以控制模块和模块中指定项的可见性。模块可见性不代表模块内部项的可见性,模块的可见性仅仅是允许其它模块去引用它,但是想要引用它内部的项,还得继续将对应的项标记为 pub。而枚举与结构体的可见性不一样:

  • 将结构体设置为 pub,但它的所有字段依然是私有的
  • 将枚举设置为 pub,它的所有字段也将对外可见

使用super引用模块

super 代表的是父模块为开始的引用方式,类似于文件系统中的 .. 语法:../a/b 文件名:self 其实就是引用自身模块中的项.

fn serve_order() {
    self::back_of_house::cook_order()
}

// 厨房模块
mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order(); // rate::serve_order(); 
    }

    fn cook_order() {}
}

使用 super::serve_order 语法,调用了父模块(包根)中的 serve_order 函数,self等效于直接调用 back_of_house。

模块变多或者变大时,需要将模块放入一个单独的文件中,使模块内容与声明分开,让代码更好维护。例如上面的代码所有的模块都定义在 src/lib.rs 中,我们可以把 front_of_house分离出来,放入一个单独的文件中 src/front_of_house.rs

pub mod hosting {

pub fn add_to_waitlist() {}

然后,将以下代码留在 src/lib.rs 中:

mod front_of_house;

pub use crate::front_of_house::hosting;

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

mod front_of_house; 告诉 Rust 从另一个和模块 front_of_house 同名的文件中加载该模块的内容,用绝对路径的方式来引用 hosting 模块:crate::front_of_house::hosting;use关键字用来将外部模块中的项引入到当前作用域中来,pub 表示将该引入的内容再度设置为可见。因为当外部的模块项 A 被引入到当前模块中时,它的可见性自动被设置为私有的,如果希望其它外部代码引用我们的模块项 A,那么使用 pub use可以对它进行再导出。

当一个模块有许多子模块时,我们也可以通过文件夹的方式来组织这些子模块。在上述例子中,我们可以创建一个目录 front_of_house,在 front_of_house 同级目录里创建一个与模块(目录)同名的 rs 文件 front_of_house.rs。

模块引入方式

在 Rust 中,可以使用 use 关键字把路径提前引入到当前作用域中,随后的调用就可以省略该路径。为了避免同名引用的冲突,可以通过调用父模块的方式来解决,另一种方式使通过as关键字起别名。

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

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

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

可以使用 {} 来一起引入进来,在减少大量 use 的使用: 

use std::collections::HashMap;
use std::collections::BTreeMap;
//use std::collections::{HashMap,BTreeMap};

use std::cmp::Ordering;
use std::io;
//use std::{cmp::Ordering, io};

use std::io;
use std::io::Write;
//use std::io::{self, Write};

 self 关键字,用来替代模块自身。可以使用*号引入模块下的所有项。在实际项目中,这种引用方式往往用于快速写测试代码,它可以把所有东西一次性引入到 tests 模块中。但使用 * 来引入的时候要格外小心,避免名称冲突问题。

使用第三方包

Rust 社区已经为我们贡献了大量高质量的第三方包,可以在 crates.io 或者 lib.rs 中检索和使用,lib.rs搜索功能更强大,内容展示也更加合理,但下载依赖包还是得用crates.io。在网上找到想要的包,然后将包和版本信息写入到 Cargo.toml 中,例如:修改 Cargo.toml 文件,在 [dependencies] 区域添加一行:rand = "0.8.3"

模块的可见性

在 Rust 中,包是一个模块树,我们可以通过 pub(crate) item; 方式来限制在包内的某个模块内可见:item 虽然是对外可见的,但是只在当前包内可见,外部包无法引用到该 item。总结一下:

  • pub 意味着可见性无任何限制
  • pub(crate) 表示在当前包可见
  • pub(self) 在当前模块可见
  • pub(super) 在父模块可见
  • pub(in <path>) 表示在某个路径代表的模块中可见,其中 path 必须是父模块或者祖先模块

如果我们想要让某一项可以在整个包中都可以被使用,那么有两种办法:

  • 在包根中定义一个非 pub 类型的 X(父模块的项对子模块都是可见的,因此包根中的项对模块树上的所有模块都可见)
  • 在子模块中定义一个 pub 类型的 Y,同时通过 use 将其引入到包根
// 一个名为 `my_mod` 的模块
mod my_mod {
    // 模块中的项默认具有私有的可见性
    fn private_function() {
        println!("called `my_mod::private_function()`");
    }

    // 使用 `pub` 修饰语来改变默认可见性。
    pub fn function() {
        println!("called `my_mod::function()`");
    }

    // 在同一模块中,项可以访问其它项,即使它是私有的。
    pub fn indirect_access() {
        print!("called `my_mod::indirect_access()`, that\n> ");
        private_function();
    }

    // 模块也可以嵌套
    pub mod nested {
        pub fn function() {
            println!("called `my_mod::nested::function()`");
        }

        #[allow(dead_code)]
        fn private_function() {
            println!("called `my_mod::nested::private_function()`");
        }

        // 使用 `pub(in path)` 语法定义的函数只在给定的路径中可见。
        // `path` 必须是父模块(parent module)或祖先模块(ancestor module)
        pub(in crate::my_mod) fn public_function_in_my_mod() {
            print!("called `my_mod::nested::public_function_in_my_mod()`, that\n > ");
            public_function_in_nested()
        }

        // 使用 `pub(self)` 语法定义的函数则只在当前模块中可见。
        pub(self) fn public_function_in_nested() {
            println!("called `my_mod::nested::public_function_in_nested");
        }

        // 使用 `pub(super)` 语法定义的函数只在父模块中可见。
        pub(super) fn public_function_in_super_mod() {
            println!("called my_mod::nested::public_function_in_super_mod");
        }
    }

    pub fn call_public_function_in_my_mod() {
        print!("called `my_mod::call_public_funcion_in_my_mod()`, that\n> ");
        nested::public_function_in_my_mod();
        print!("> ");
        nested::public_function_in_super_mod();
    }

    // `pub(crate)` 使得函数只在当前包中可见
    pub(crate) fn public_function_in_crate() {
        println!("called `my_mod::public_function_in_crate()");
    }

    // 嵌套模块的可见性遵循相同的规则
    mod private_nested {
        #[allow(dead_code)]
        pub fn function() {
            println!("called `my_mod::private_nested::function()`");
        }
    }
}

fn function() {
    println!("called `function()`");
}

fn main() {
    // 模块机制消除了相同名字的项之间的歧义。
    function();
    my_mod::function();

    // 公有项,包括嵌套模块内的,都可以在父模块外部访问。
    my_mod::indirect_access();
    my_mod::nested::function();
    my_mod::call_public_function_in_my_mod();

    // pub(crate) 项可以在同一个 crate 中的任何地方访问
    my_mod::public_function_in_crate();

    // pub(in path) 项只能在指定的模块中访问
    // 报错!函数 `public_function_in_my_mod` 是私有的
    //my_mod::nested::public_function_in_my_mod();
    // 试一试 ^ 取消该行的注释

    // 模块的私有项不能直接访问,即便它是嵌套在公有模块内部的

    // 报错!`private_function` 是私有的
    //my_mod::private_function();
    // 试一试 ^ 取消此行注释

    // 报错!`private_function` 是私有的
    //my_mod::nested::private_function();
    // 试一试 ^ 取消此行的注释

    // 报错! `private_nested` 是私有的
    //my_mod::private_nested::function();
    // 试一试 ^ 取消此行的注释
}

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值