将模块拆分成几个文件


102

我想要一个带有多个结构的模块,每个结构都在自己的文件中。以一个Math模块为例:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

我希望每个结构都在同一个模块中,可以在主文件中使用它,如下所示:

use Math::Vector;

fn main() {
  // ...
}

但是,Rust的模块系统(开始时有点令人困惑)并没有提供一种明显的方式来做到这一点。似乎只允许您将整个模块放在一个文件中。这是不生锈的吗?如果没有,我该怎么做?


1
我解释说:“我想要一个包含多个结构的模块,每个结构都在自己的文件中。” 表示您希望每个结构定义都在其自己的文件中。
2014年

1
尽管模块系统当然允许这种结构化,但这不会被认为是粗糙的。通常,模块路径最好直接对应于文件系统路径,例如struct foo::bar::Baz应该在foo/bar.rs或中定义foo/bar/mod.rs
克里斯·摩根

Answers:


111

Rust的模块系统实际上是非常灵活的,可以让您在隐藏代码在文件中的结构时公开所需的任何类型的结构。

我认为这里的关键是利用pub use,这将允许您从其他模块中重新导出标识符。Rust的std::io板条箱中有此先例,其中子模块中的某些类型被重新导出以用于中std::io

编辑(2019-08-25):答案的以下部分是相当早之前写的。它说明了如何rustc单独设置这样的模块结构。如今,大多数情况下通常都会使用Cargo。尽管以下内容仍然有效,但其中的某些部分(例如#![crate_type = ...])可能看起来很奇怪。这不是推荐的解决方案。

为了适应您的示例,我们可以从以下目录结构开始:

src/
  lib.rs
  vector.rs
main.rs

这是您的main.rs

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}

而你的src/lib.rs

#[crate_id = "math"];
#[crate_type = "lib"];

pub mod vector; // exports the module defined in vector.rs

最后,src/vector.rs

// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;

mod vector_b; // private sub-module defined in vector_b.rs

mod vector_a { // private sub-module defined in place
    #[derive(Debug)]
    pub struct VectorA {
        xs: Vec<i64>,
    }

    impl VectorA {
        pub fn new() -> VectorA {
            VectorA { xs: vec![] }
        }
    }
}

这就是魔术发生的地方。我们定义了一个子模块math::vector::vector_a,该子模块具有某种特殊类型的矢量的实现。但是我们不希望您的图书馆的客户关心这里的vector_a子模块。相反,我们希望在math::vector模块中提供它。这是通过完成的pub use self::vector_a::VectorA,它将vector_a::VectorA在当前模块中重新导出标识符。

但是您问如何做到这一点,以便可以将特殊的矢量实现放在不同的文件中。这是mod vector_b;生产线要做的。它指示Rust编译器vector_b.rs为该模块的实现查找文件。确实,这是我们的src/vector_b.rs文件:

#[derive(Debug)]
pub struct VectorB {
    xs: Vec<i64>,
}

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}

从客户端的角度来看,在两个不同文件的两个不同模块中定义VectorAVectorB是完全不透明的事实。

如果与处于同一目录main.rs,则应该可以使用以下命令运行它:

rustc src/lib.rs
rustc -L . main.rs
./main

通常,Rust书中的“包装箱和模块”一章非常好。有很多例子。

最后,Rust编译器还会自动为您查找子目录。例如,以上代码在此目录结构下将保持不变:

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs

编译和运行的命令也保持不变。


我相信您误解了我所说的“向量”的含义。我所说的向量是数学量,而不是数据结构。另外,我没有运行最新版本的rust,因为在Windows上构建它有点痛苦。
starscape 2014年

+1并不是我真正需要的,但可以指出正确的方向。
starscape 2014年

@EpicPineapple的确!Vec可以用来表示这样的向量。(当然,对于较大的N。)
BurntSushi14年

1
@EpicPineapple您能解释一下我的答案遗漏了什么,以便我可以更新吗?我正在努力查看您的答案和我的答案之间的区别,math::Vec2而不是使用而不是math::vector::Vec2。(即,相同的概念,而是一个模块更深。)
BurntSushi5

1
我在您的问题中没有看到该标准。据我所知,我已经回答了所问的问题。(实际上是在问如何从文件中分离模块。)抱歉,它在Rust 0.9上不起作用,但这涉及使用不稳定语言的领域。
BurntSushi14年

38

Rust模块规则是:

  1. 源文件只是它自己的模块(特殊文件main.rs,lib.rs和mod.rs除外)。
  2. 目录只是模块路径组件。
  3. 文件mod.rs 只是目录的模块。

目录math中的matrix.rs 1文件就是模块math::matrix。这很容易。您在文件系统上看到的内容也可以在源代码中找到。这是文件路径和模块路径2的一一对应关系。

因此你可以导入一个结构Matrixuse math::matrix::Matrix,因为结构是目录中的数学文件matrix.rs内。不开心?use math::Matrix;相反,您会非常喜欢,不是吗?这是可能的。使用以下命令重新导出math::matrix::Matrixmath / mod.rs中的标识符:

pub use self::math::Matrix;

还有另一个步骤可以使此工作正常进行。Rust需要模块声明来加载模块。mod math;在main.rs中添加一个。如果不这样做,则在进行如下导入时会从编译器收到错误消息:

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

这里的提示是误导的。不需要额外的包装箱,除非您确实打算编写一个单独的库。

将其添加到main.rs的顶部:

mod math;
pub use math::Matrix;

该模块的声明也neccessary的子模块vectormatrix并且complex,因为math需要将其加载到重新导出。仅当您已加载标识符模块时,标识符的重新导出才有效。这意味着要重新导出math::matrix::Matrix您需要编写的标识符mod matrix;。您可以在math / mod.rs中执行此操作。因此,创建具有以下内容的文件:

mod vector;
pub use self::vector::Vector;

mod matrix;
pub use self::matrix::Matrix;

mod complex;
pub use self::complex::Complex;

Aaa,您完成了。


1源文件名通常在Rust中以小写字母开头。这就是为什么我使用matrix.rs而不是Matrix.rs的原因。

2 Java的不同。您也可以使用声明路径package。这是多余的。从文件系统中的源文件位置可以明显看出该路径。为什么要在文件顶部的声明中重复此信息?当然,有时快速浏览源代码比找出文件的文件系统位置更容易。我能理解那些说它不那么令人困惑的人。


23

Rust的纯粹主义者可能会称我为异教徒,并且讨厌这种解决方案,但这要简单得多:只需在自己的文件中做每件事,然后使用mod.rs中的“ include! ”宏即可:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

这样,您就不会再添加任何嵌套模块,并且可以避免复杂的导出和重写规则。简单,有效,大惊小怪。


1
您只是丢掉了命名空间。现在,以与另一文件无关的方式更改一个文件可能会破坏其他文件。您对“用途”的使用会泄漏(即,一切都像use super::*)。您无法从其他文件中隐藏代码(这对于不安全使用安全抽象很重要)
Demur Rumed 16/09/4

11
是的,但这正是我在这种情况下想要的:有几个文件仅出于命名间隔目的而表现得像一个文件。我并不是在每种情况下都主张这样做,但是如果您出于某种原因不想使用“每个文件一个模块”的方法,这是一个有用的解决方法。
hasvn16年

太好了,我模块的一部分仅是内部的,但却是独立的,而这就是窍门。我将尝试获得适当的模块解决方案以使其正常工作,但这并不是那么容易。
rjh

5
我不在乎被称为异端,您的解决方案很方便!
sailfish009

21

好了,我的编译器花了一段时间,终于使它工作了(感谢BurntSushi指出了pub use

main.rs:

use math::Vec2;
mod math;

fn main() {
  let a = Vec2{x: 10.0, y: 10.0};
  let b = Vec2{x: 20.0, y: 20.0};
}

数学/模数:

pub use self::vector::Vec2;
mod vector;

数学/向量

use std::num::sqrt;

pub struct Vec2 {
  x: f64,
  y: f64
}

impl Vec2 {
  pub fn len(&self) -> f64 {
    sqrt(self.x * self.x + self.y * self.y) 
  }

  // other methods...
}

其他结构可以以相同的方式添加。注意:用0.9编译,不是master。


4
请注意,使用mod math;in main.rs会将main程序与库耦合。如果您希望math模块独立,则需要单独编译并链接至该模块extern crate math(如我的答案所示)。在Rust 0.9中,语法可能会extern mod math改为。
BurntSushi14年

20
将BurntSushi5的答案标记为正确答案真的很公平。
IluTov 2015年

2
@NSAddict否。要将模块与文件离婚,无需创建单独的包装箱。工程过度。
2015年

1
为什么这不是最受好评的答案??该问题询问如何将项目分解为几个文件,就像此答案所示的那样简单,而不是如何将其拆分为板条箱,这更难,@ BurntSushi5回答了什么(也许问题已被编辑?)。 ..
Renato

6
@ BurntSushi5的答案应该已经被接受。在社交上比较尴尬,甚至可能要提出一个问题,得到一个非常好的答案,然后将其汇总为一个单独的答案并将您的摘要标记为可接受的答案。
hasanyasin

4

我想在这里添加当深度嵌套时如何包含Rust文件。我有以下结构:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

您如何访问sink.rstoilet.rs来自main.rs

正如其他人所提到的,Rust不了解文件。相反,它将所有内容视为模块和子模块。要访问bathroom目录中的文件,您需要导出它们或将它们存储在顶部。您可以通过在您要访问的目录中指定文件名并pub mod filename_inside_the_dir_without_rs_ext在文件中进行操作。

例。

// sink.rs
pub fn run() { 
    println!("Wash my hands for 20 secs!");
}

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. bathroom.rshome目录内创建一个文件:

  2. 导出文件名:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. home.rs旁边创建一个名为main.rs

  4. pub mod bathroom.rs文件

    // home.rs
    pub mod bathroom;
  5. main.rs

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    mod home;
    
    fn main() {
        home::bathroom::sink::run();
    }

    use 语句也可以使用:

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    use home::bathroom::{sink, toilet};
    
    fn main() {
        sink::run();
        sink::toilet();
    }

在子模块中包括其他同级模块(文件)

如果要使用sink.rsfrom toilet.rs,可以通过指定selfor super关键字来调用模块。

// inside toilet.rs
use self::sink;
pub fn run() {
  sink::run();
  println!("Ahhh... This is sooo relaxing.")
}

最终目录结构

您最终将得到如下结果:

|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

上面的结构仅适用于Rust 2018及更高版本。以下目录结构对2018年也有效,但是这是2015年的工作方式。

|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs

其中home/mod.rs./home.rshome/bathroom/mod.rs相同home/bathroom.rs。Rust进行了此更改,因为如果包含与目录相同名称的文件,编译器会感到困惑。2018版(最先显示的版本)修复了该结构。

有关更多信息,请参见此仓库,有关整体说明,请参见此YouTube视频

最后一件事...避免连字符!使用snake_case代替。

重要的提示

必须桶中的所有文件到顶部,即使不通过顶级那些需要深度的文件。

这意味着,为了sink.rs进行发现toilet.rs,您需要使用上述所有方法来对它们进行打包main.rs

换句话说,除非您一直将它们暴露在外,否则 在内部pub mod sink;use self::sink;内部进行操作toilet.rs不会起作用main.rs

因此,请始终记住将文件存储在顶部!


2
...这是相比于C ++,这是说一些疯狂的曲
约瑟夫·加尔文

1
最好的答案,谢谢。
etech
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.