Rust 中的 mod (模块)

Rust 2020-08-19 6251 字 1223 浏览 点赞

起步

初看《Rust 程序设计语言》第七章的模块管理时,完全有不知所云之感。看完就“懵”住,也不晓得 mod 要怎么用。上 B 站找相关视频,稍解惑,却未细讲单个文件如何拆分到多个文件。最后在 github 找到 rust 语言编写的项目,方如梦初醒。

我很喜欢这种一致性设计,也就简单说一下。

mod 关键字

在 rust 中,可用 mod 关键字划分代码作用域,使代码结构清晰。

mod 的基本使用:

// 学校
mod school {
    // 教师
    mod teacher {
        fn num() -> i32 {  // 返回教师人数
            5
        }
    }
    // 学生
    mod student {
        fn num() -> i32 {  // 返回学生人数
            120
        }
    }
}

上述示例代码就是一个层次清晰的树状结构。mod [module_name] { ... } 会划出一个代码块,将一类你认为可以放在一起的放在一起。

Rust 中的项:函数、方法、结构体、枚举、模块和常量。

要使用模块中的函数,可用::符号作为路径分割符。比方你要获取学校中教师的数量,代码可以如下:

school::teacher::num();

事实上,你想在 main 函数中使用 school::teacher::num() 是会编译出错的,你会得到:“this module is private”。mod 会在控制作用域的同时,控制私有性。默认情况下,模块中的内容均为私有,对外不可见。pub 关键字可改变这种默认行为。

mod school {
    // 对外可见
    pub mod teacher {
        pub fn num() -> i32 {
            5
        }
    }

    // 对外不可见
    mod student {
        fn num() -> i32 {
            120
        }
    }
    
    // classroom 模块对外可见
    pub mod classroom {
        // num 函数对外不可见
        fn num() -> i32 {
            6
        }
    }
}

fn main() {
    // school::teacher::num() **可**正常使用
    let teacher_nums = school::teacher::num();
    println!("{}", teacher_nums);

    // school::student::num() **不可**正常使用
    // let teacher_nums = school::student::num();
}

mod 还可以把 B 模块引入到 A 模块中。如上述代码,teacher 模块直接写到 school 模块,teacher 理所当然就在 school 里。你也可以把子模块写到“世界各地”,再用 mod 把它们组合起来:

pub mod school {
    pub mod teacher;  // 或者 mod teacher;
    pub mod student;  // 或者 mod student;
}

pub mod teacher {
    // ...
}

pub mod student {
    // ...
}

crate、self、super、use

就像你看到的,school::teacher::num 很像一个路径(school\teacher\num),通过这种路径我们就能找到模块里的项。Rust 在这里的设计跟文件系统差不错,也有会绝对路径相对路径的概念。

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

crate

先从最简单的 main.rs 文件开始:

pub mod school {
    // 教师
    pub mod teacher {
        pub fn num() -> i32 { 5 }

        pub fn average_students() -> i32 {
            // 计算一个教师平均教授的学生数量
            crate::school::student::num() / crate::school::teacher::num()  // crate
        }
    }
    // 学生
    pub mod student {
        pub fn num() -> i32 { 120 }
    }
}

fn main() {
    let avg = crate::school::teacher::average_students();  // crate
    println!("{}", avg)
}

可以看到的是,不论是 num() 函数还是 average_students() 函数,我们都从 crate:: 开始,也就是“根”开始,类似于文件系统中的 //proc, /etc……)。作为二进制 crate,main.rs 文件就是它的根。

上述用法就是 “字面值 crate 开头” 的意思。

self

self 你可以理解为文件系统中的 .,表示当前路径,如:cd ./project/srcls ./project/target。所以上述代码可以用 selfcrate 替换掉:

pub mod school {
    // 教师
    pub mod teacher {
        pub fn num() -> i32 { 5 }

        pub fn average_students() -> i32 {
            // 计算一个教师平均教授的学生数量
            crate::school::student::num() / self::num()  // self
        }
    }
    // 学生
    pub mod student {
        pub fn num() -> i32 { 120 }
    }
}

fn main() {
    let avg = self::school::teacher::average_students();  // self
    println!("{}", avg)
}
  • 说明:从“当前路径”无法访问到 student 模块中的 num() 函数,这里仍用 crate 的绝对路径表示。

正如文件系统中当前路径 . 可以省略,self 也能被省略。

pub mod school {
    // 教师
    pub mod teacher {
        pub fn num() -> i32 { 5 }

        pub fn average_students() -> i32 {
            // 计算一个教师平均教授的学生数量
            crate::school::student::num() / num()  // self
        }
    }
    // 学生
    pub mod student {
        pub fn num() -> i32 { 120 }
    }
}

fn main() {
    let avg = school::teacher::average_students();  // self
    println!("{}", avg)
}

super

在做路径切换时,我们还会常用 cd .. 跳到父级目录,super 的作用就和 .. 类似。

代码:

crate::school::student::num() / num()

可以修改为:

super::student::num() / num()

这里的 super 表示 teacher 模块的父模块 school

use

当我们不得不使用绝对路径,但绝对路径又太长时,可以用 use 关键字把名称引入作用域。

比方:

pub fn average_students() -> i32 {
    crate::school::student::num() / self::num()  
}

可修改为:

pub fn average_students() -> i32 {
    use crate::school::student;
    student::num() / num()  
}

甚至还可以用 use ... as ... 对项重命名:

pub fn average_students() -> i32 {
    use crate::school::student::num as student_num;
    student_num() / num()
}

拆分到多文件

最开头我说“喜欢这种一致性设计”,是因为在 Rust 中,多文件中的代码引入方式与 mod 关键字基本类似。这很有意思,有效降低了使用者的学习成本。

mod school {
    mod teacher {
        // ...
    }
    mod student {
        // ...
    }
}

还拿之前的模块结构为例,现在把上述代码拆分到多个文件,我们很容易想到的方式是:

|—— src                 (dir)
|  |—— main.rs          (file)
|  |—— school           (dir)
|  |  |—— teacher.rs    (file)
|  |  |—— student.rs    (file)
// (school/teacher.rs)

// 返回教师的总数
pub fn num() -> i32 { 5 }

// 计算一个教师平均教授的学生数量
pub fn average_students() -> i32 {
    use crate::school::student::num as student_num;
    student_num() / num()
}
// (school/student.rs)


// 返回学生的总数
pub fn num() -> i32 { 120 }

这个时候你想在 main.rs 文件中调用 average_students() 函数就不行了。Rust 会将一个 xxx.rs 文件默认为一个模块,但是普通的文件夹不能被 Rust 编译器识别。当文件夹下有 mod.rs 文件时,该文件夹才能被识别为模块。

|—— src                 (dir)
|  |—— main.rs          (file)
|  |—— school           (dir)
|  |  |—— mod.rs        (file) ** mod.rs 文件**
|  |  |—— teacher.rs    (file)
|  |  |—— student.rs    (file)

此时模块 teacher 、模块 student 与模块 school 还没有直接关系,需要在 mod.rs 文件中引入 teacher 和 student。

// (school/mod.rs)

pub mod teacher;
pub mod student;

现在 main.rs 文件就可以正常调用 average_students() 函数:

// (main.rs)

mod school;  // 将 school 模块引入到 crate root 中

fn main() {
    let avg = school::teacher::average_students();
    println!("{}", avg)
}

正如你看到的,不论是单个文件还是在多个文件,模块的行为完全一致。

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论