Rust 包、crate 与 模块

包、crate 与 模块

编写程序时一个核心的问题是作用域:在代码的某处编译器知道哪些变量名?允许调用哪些函数?这些变量引用的又是什么?

Rust有一系列与作用域相关的功能。这有时被称为模块系统,不过又不仅仅是模块:

  • 是Cargo的一个功能,它允许你构建、测试和分享crate。

  • Crates是一个模块的树形结构,它形成了库或二进制项目。

  • 模块use关键字允许你控制作用域和路径的私有性。

  • 路径是一个命名例如结构体、函数或模块等项的方式

包和 crate 用来创建库和二进制项目

让我们来聊聊模块和 crate。下面是一个总结:

  • crate 是一个二进制或库项目。
  • crate 根是一个用来藐视如何构建 crate 的文件。
  • 带有 Cargo.toml 文件的用以描述如何构建一个或多个 crate。一个包中至多有一个库项目。

所以当运行cargo new时是在创建一个包:

cargo new my-project

因为 Cargo 创建了 Cargo.toml,这意味者现在我们有了一个包。如果查看 Cargo.toml 的内容,会发现并没有提到 src/main.rs。然而,Cargo 的约定是如果在代表表的 Cargo.toml 的同级目录下包含 src 目录且其中包含 main.rs 文件的话,Cargo 就知道这个包带有一个与包同名的二进制 crate,且 src/main.rs 就是 crate 根。另一个约定如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc来实际构建库或者二进制项目。

一个包可以带有零个或一个库 crate 和任意多个二进制 crate。一个包中至少一个(库或二进制) crate。

模块系统来控制作用域和私有性

Rust 的此部分功能通常被引用为模块系统,不过其包括了一些除模块之外的功能。本部分我们会讨论:

  • 模块,一个组织代码和控制路径私有性的方式
  • 路径,一个命名项的方式
  • use关键字用来将路径引入作用域
  • pub关键字使项变为共有
  • as关键字用于将项引入作用域时进行重命名
  • 使用外部包
  • 嵌套路径来消除大量的use语句
  • 使用 glob 运算符将模块的所有内容引入作用域
  • 如何将不同模块分割到单独的文件中

模块允许我们将代码组织起来。

mod sound {
    mod instrument {
        mod woodwind {
           fn clarinet() {
               
            } 
        }
    }
    
    mod voice {
        
    }
}

fn main() {
    
}

路径来引用模块树中的项

如果想要调用函数,需要知道其路径

路径有两种形式:

  • 绝对路径从crate 根开始,以 crate 名或者字面值 crate开头
  • 相对路径从当前模块开始,以selfsupper或者当前模块的标识符开头。

绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符。

main中调用clarinet函数,现在还不能调用

mod sound {
    mod instrument {
        fn clarinet() {
            
        }
    }
}

fn main() {
    // 绝对路径
    crate::sound::instrument::clarinet();
    
    // 相对路径
    sound::instrument::clarinet();  
}

instrument模块是私有的。instrument模块和clarinet函数的路径都是正确的,不过Rust不让我们使用,因为他们是私有的。

模块作为私有性的边界

Rust采用模块还有另外一个原因:模块是Rust中的私有边界。私有规则如下:

  • 所有项(函数、方法、结构体、枚举、模块和常量)默认是私有的。
  • 可以使用pub关键字使项变为共有。
  • 不允许使用定义于当前模块的子模块中私有代码。
  • 允许使用任何定义于父模块或当前模块中的代码

使用pub关键字使项变为公有

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

fn main() {
    // 绝对路径
    crate::sound::instrument::clarinet();
    
    // 相对路径
    sound::instrument::clarinet();
}

使用super开始相对路径

fn main() {
    crate::sound::instrument::clarinet();
    sound::instrument::inner::clarinet();
}

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            super::voice::hello();
        }
        pub mod inner {
            pub fn clarinet() {
                super::super::voice::hello();
            }
        }
    }

    pub mod voice {
        pub fn hello() {
            println!("hello");
        }
    }
}

对结构体和枚举使用pub

mod plant {
    pub struct Vegetable {
        pub name: String,
        id: i32,
    }
    
    impl Vegetable {
        pub fn new(name: &str) -> Vegetable {
            Vegetable {
                name: String::from(name),
                id: 1,
            }
        }
    }
}

fn main() {
    let mut v = plant::Vegetable::new("squash");
    v.name = String::from("butternut squash");
    println!("{}", v.name);
}

plant::Vegetable结构体的name字段是公有的,在main中可以使用点号读写name字段。不允许在main中使用id字段因为是私有的。

使用use关键字将名称引入作用域

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

use crate::sound::instrument;

fn main() {
    instrument::clarinet();
}

使用相对路径将项引入作用域

use self::sound::instrument;

user函数路径使用习惯 VS 其他项

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

use crate::sound::instrument::clarinet;

fn main() {
    clarinet();
}

userclarinet函数引入作用域,这是不推荐的。

对于函数来说,通过use指定的父模块接着指定模块来调用方法被认为是习惯的方法。通过use指定函数的路径,清楚的表明了函数不是本地定义的,同时最小化了指定全路径的重复。

通过 as关键字重命名引入作用域的类型

将两个同名类型引入同一作用域这个问题还有另一个解决办法:可以通过在use后加上as和一个新名称来为此类型指定一个新的本地名称。

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

fn function1() -> Result {
    Ok(())
}

fn function2() -> IoResult<()> {
    Ok(())
}

通过pub use重导出名称

当使用 use关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果希望调用呢编写的代码能够像你一样在其自己的作用域内引用这些类型,可以结合pubuse。这个技术被称为重导出,因为这样做将项引入作用域同时使其可供其他代码引入自己的作用域。

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            
        }
    }
}

mod performance_group {
    pub use crate::sound::instrument;
    
    pub fn clarinet_trio() {
        instrument::clarinet();
    }
}

fn main() {
    performance_group::clarinet_trio();
    performance_group::instrument::clarinet();
}

使用外部包

在 Cargo.toml 中加入 rand依赖

[dependencies]
rand = "0.5.5"

rand定义引入项目包的作用域,加入一行use

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101);
}

嵌套路径来消除大量的use

当需要引入很多定义于相同或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。

use std::cmp::Ordering;
use std::io;
// ...

可以使用嵌套的路径将同样的项在一行中引入而不是两行。

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

通过 glob 运算符将所有的公有定义引入作用域

如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟*,glob 运算符:

use std::collections::*;

将模块分割进不同文件

当模块变得更大时,你可能想要将它们的定义移动到一个单独的文件中使代码更容易阅读。

文件名: src/main.rs

mod sound;
fn main() {
    // 绝对路径
    create::sound::instrument::clarinet();
    
    // 相对路径
    sound::instrument::clarinet();
}

在crate根文件声明 sound 模块(这里是 src/main.rs),将模块内容移动到 src/sound.rs 文件, src/sound.rs 中会包含sound模块的内容

文件名: src/sound.rs

pub mod instrument {
    pub fn clarinet() {
        
    }
}

mod sound后使用分号而不是代码块告诉Rust在另一个与模块同名文件中加载模块的内容。

继续重构我们的例子,将instrument模块也提取到自己的文件中,修改 src/sound.rs 只包含instrument模块的声明:

文件名: src/sound.rs

pub mod instrument;

接着创建 src/sound 目录和 src/sound/instrument.rs 文件来包含instrument模块的定义:

文件名: src/sound/instrument.rs

pub fn clarinet() {
    
}

模块树依然保持相同,main中的函数调用也无需修改继续保持有效,即使其定义存在于不同的文件中。这样随着代码增长可以将模块移动到新文件中。

总结

Rust 提供了将包组织进 crate, 将 crate 组织进模块和通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。可以通过use语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加pub关键字使其定义变为公有。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容