将模块拆分为多个文件

sta*_*ape 87 module rust

我希望有一个包含多个结构的模块,每个模块都在自己的文件中.Math模块为例:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs
Run Code Online (Sandbox Code Playgroud)

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

use Math::Vector;

fn main() {
  // ...
}
Run Code Online (Sandbox Code Playgroud)

然而,Rust的模块系统(开始时有点混乱)并没有提供一种明显的方法来实现这一点.它似乎只允许您将整个模块放在一个文件中.这不是质朴的吗?如果没有,我该怎么做?

Bur*_*hi5 101

Rust的模块系统实际上非常灵活,它可以让你暴露出你想要的任何类型的结构,同时隐藏你的代码在文件中的结构.

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

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

src/
  lib.rs
  vector.rs
main.rs
Run Code Online (Sandbox Code Playgroud)

这是你的rustc:

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}
Run Code Online (Sandbox Code Playgroud)

你的#![crate_type = ...]:

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

pub mod vector; // exports the module defined in vector.rs
Run Code Online (Sandbox Code Playgroud)

最后,main.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![] }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是魔术发生的地方.我们已经定义了一个子模块src/lib.rs,它有一些特殊类型的向量的实现.但我们不希望您图书馆的客户关心有一个src/vector.rs子模块.相反,我们希望在math::vector::vector_a模块中提供它.这样做vector_a,重新导出math::vector当前模块中的标识符.

但是你问如何做到这一点,以便你可以将你的特殊矢量实现放在不同的文件中.这就是这pub use self::vector_a::VectorA条线的作用.它指示Rust编译器查找用于vector_a::VectorA实现该模块的文件.果然,这是我们的mod vector_b;文件:

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

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}
Run Code Online (Sandbox Code Playgroud)

从客户的角度来看,这样的事实vector_b.rs,并src/vector_b.rs在两个不同的模块定义在两个不同的文件是完全不透明的.

如果你在同一个目录中VectorA,你应该可以运行它:

rustc src/lib.rs
rustc -L . main.rs
./main
Run Code Online (Sandbox Code Playgroud)

一般来说,Rust书中的"Crates and Modules"章节非常好.有很多例子.

最后,Rust编译器还会自动查找子目录.例如,上面的代码将在此目录结构中保持不变:

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs
Run Code Online (Sandbox Code Playgroud)

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


nal*_*ply 32

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;
Run Code Online (Sandbox Code Playgroud)

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

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?
Run Code Online (Sandbox Code Playgroud)

提示在这里有误导性.不需要额外的板条箱,当然你真的打算写一个单独的库.

在main.rs的顶部添加:

mod math;
pub use math::Matrix;
Run Code Online (Sandbox Code Playgroud)

该模块的声明也neccessary的子模块vector,matrix并且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;
Run Code Online (Sandbox Code Playgroud)

Aaaand你完成了.


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

2 Java的不同之处.你也用路径声明了路径package.这是多余的.从文件系统中的源文件位置开始,路径已经很明显.为什么要在文件顶部的声明中重复此信息?当然,有时候更容易快速查看源代码而不是查找文件的文件系统位置.我能理解那些说它不那么混乱的人.

  • 顶部的 tl;dr 应该在 Rust 文档中! (3认同)

sta*_*ape 18

好吧,打了我的编译器一段时间,最后让它工作(感谢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};
}
Run Code Online (Sandbox Code Playgroud)

数学/ mod.rs:

pub use self::vector::Vec2;
mod vector;
Run Code Online (Sandbox Code Playgroud)

数学/ vector.rs

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...
}
Run Code Online (Sandbox Code Playgroud)

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

  • 将BurntSushi5的答案标记为正确答案真的是公平的. (19认同)
  • @ BurntSushi5的答案应该是公认的答案.这在社交方面很尴尬,甚至可能意味着提出一个问题,得到一个非常好的答案,然后将其作为一个单独的答案进行总结,并将您的摘要标记为已接受的答案. (5认同)
  • 请注意,您在`main.rs`中使用`mod math;`会将您的`main`程序与您的库耦合.如果你想让你的`math`模块独立,你需要单独编译它并用`extern crate math`链接到它(如我的答案所示).在Rust 0.9中,语法可能是`extern mod math`. (4认同)
  • @NSAddict否.要从文件中分离模块,您不需要创建单独的包.它过度设计了. (2认同)

has*_*svn 17

Rust的纯粹主义者可能会称我为异教徒并且讨厌这个解决方案,但这更简单:只需在自己的文件中执行每个操作,然后在mod.rs中使用" include! "宏:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");
Run Code Online (Sandbox Code Playgroud)

这样您就不会添加嵌套模块,并避免复杂的导出和重写规则.简单,有效,没有大惊小怪.

  • 是的,但在这种情况下,这正是我想要的:有几个文件只是一个用于命名空间的文件.我不是每个案例都提倡这个,但如果你不想处理"每个文件一个模块"的方法,无论出于什么原因,这都是一个有用的解决方法. (8认同)
  • 我不在乎被称为异端,您的解决方案很方便! (3认同)
  • 你只是扔掉了命名空间。以与另一个文件无关的方式更改一个文件现在可以破坏其他文件。你对'use'的使用变得有漏洞(即一切都像````use super::*````)。您不能从其他文件中隐藏代码(这对于不安全使用安全抽象很重要) (2认同)

Jos*_*e A 12

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

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs
Run Code Online (Sandbox Code Playgroud)

你如何访问sink.rstoilet.rsmain.rs

正如其他人提到的,Rust 不了解文件。相反,它将一切视为模块和子模块。要访问浴室目录中的文件,您需要将它们导出或将它们放置到顶部。您可以通过在文件内指定一个包含您要访问的目录的文件名来完成此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.")
}
Run Code Online (Sandbox Code Playgroud)
  1. bathroom.rshome目录内创建一个名为的文件:

  2. 导出文件名:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建一个名为home.rsnext的文件main.rs

  4. pub mod 浴室.rs文件

    // home.rs
    pub mod bathroom;
    
    Run Code Online (Sandbox Code Playgroud)
  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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

最终目录结构

你最终会得到这样的结果:

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
Run Code Online (Sandbox Code Playgroud)

上面的结构仅适用于 Rust 2018 以后的版本。以下目录结构也适用于 2018 年,但它是 2015 年过去的工作方式。

// bathroom.rs
pub mod sink;
pub mod toilet;
Run Code Online (Sandbox Code Playgroud)

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

请参阅此 repo了解更多信息,并查看YouTube 视频以获得整体解释。

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

重要的提示

即使顶级文件不需要深层文件,您也必须将所有文件桶装到顶部。

这意味着,sink.rs要发现toilet.rs,您需要使用上述方法一直到main.rs!

换句话说,除非你一直暴露它们,否则dopub mod sink;use self::sink; insidetoilet.rs不起作用main.rs

因此,请始终记住将您的文件桶装到顶部!

  • ...与 C++ 相比,这非常复杂,C++ 说明了一些事情 (5认同)

小智 5

导出模块的更令人耳目一新的方法,我从Github上找到的。

mod foo {
    //! inner docstring comment 1
    //! inner docstring comment 2

    mod a;
    mod b;

    pub use a::*;
    pub use b::*;
}
Run Code Online (Sandbox Code Playgroud)