跨多个文件拆分模块

Posted

技术标签:

【中文标题】跨多个文件拆分模块【英文标题】:Split a module across several files 【发布时间】:2014-05-01 01:45:00 【问题描述】:

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

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

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

use Math::Vector;

fn main() 
  // ...

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

【问题讨论】:

我解释说“我想要一个包含多个结构的模块,每个结构都在它自己的文件中。”表示您希望每个结构定义都在其自己的文件中。 这不会被认为是质朴的,尽管模块系统当然允许这样的结构。模块路径通常最好直接对应于文件系统路径,例如struct foo::bar::Baz 应该在 foo/bar.rsfoo/bar/mod.rs 中定义。 【参考方案1】:

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

我认为这里的关键是使用pub use,这将允许您从其他模块重新导出标识符。在 Rust 的 std::io crate 中有这样的先例,其中子模块中的某些类型是 re-exported for use in 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 书中的"Crates and Modules" chapter 还是不错的。例子很多。

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

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

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

【讨论】:

我相信你误解了我所说的“矢量”的意思。我说的是the mathematical quantity 中的向量,而不是数据结构。另外,我没有运行最新版本的 rust,因为在 windows 上构建有点痛苦。 +1 不是我所需要的,但为我指明了正确的方向。 @EpicPineapple 确实!并且可以使用 Vec 来表示这样的向量。 (当然,对于更大的 N。) @EpicPineapple 你能解释一下我的答案遗漏了什么以便我更新吗?除了使用math::Vec2 而不是math::vector::Vec2 之外,我很难看到你的答案和我的答案之间的区别。 (即,相同的概念,但更深一个模块。) 我在您的问题中没有看到该标准。据我所知,我已经回答了提出的问题。 (这实际上是在问如何将模块与文件分离。)很抱歉它不适用于 Rust 0.9,但这与使用不稳定语言有关。【参考方案2】:

Rust 模块规则是:

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

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

因此您可以使用use math::matrix::Matrix 导入结构Matrix,因为该结构位于matrix.rs 目录的math 目录中。不开心?你更喜欢use math::Matrix;,不是吗?这是可能的。在 math/mod.rs 中重新导出标识符 math::matrix::Matrix

pub use self::math::Matrix;

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

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

这里的提示具有误导性。不需要额外的 crate,当然你真的打算编写一个单独的库。

在 main.rs 的顶部添加这个:

mod math;
pub use math::Matrix;

子模块vectormatrixcomplex也需要模块声明,因为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;

啊,你已经完成了。


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

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

【讨论】:

顶部的 tl;dr 应该在 rust 文档中!【参考方案3】:

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

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

这样您就不会添加嵌套模块,并避免复杂的导出和重写规则。 简单、有效、不费吹灰之力。

【讨论】:

你刚刚扔掉了命名空间。以与另一个文件无关的方式更改一个文件现在可以破坏其他文件。您对“使用”的使用变得泄漏(即一切都像use super::*)。您不能从其他文件中隐藏代码(这对于不安全使用安全抽象很重要) 是的,但这正是我在这种情况下想要的:有几个文件作为命名空间的一个。我并不是在每种情况下都提倡这种做法,但如果您不想处理“每个文件一个模块”方法,无论出于何种原因,这是一个有用的解决方法。 这太棒了,我的模块的一部分只是内部的,但自包含,这就成功了。我也会尝试让合适的模块解决方案发挥作用,但这并不容易。 我不在乎被称为异端,你的解决方案很方便!【参考方案4】:

好吧,与我的编译器斗争了一段时间,终于让它工作了(感谢 BurntSushi 指出pub use

main.rs:

use math::Vec2;
mod math;

fn main() 
  let a = Vec2x: 10.0, y: 10.0;
  let b = Vec2x: 20.0, y: 20.0;

数学/mod.rs:

pub use self::vector::Vec2;
mod 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...

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

【讨论】:

请注意,您在main.rs 中使用mod math; 会将您的main 程序与您的库结合起来。如果您希望 math 模块是独立的,则需要单独编译它并使用 extern crate math 链接到它(如我的回答所示)。在 Rust 0.9 中,语法可能改为 extern mod math 将 BurntSushi5 的答案标记为正确答案真的很公平。 @NSAddict 否。要从文件中分离模块,您不需要创建单独的 crate。它被过度设计了。 为什么这不是投票最多的答案?该问题询问如何将项目分解为几个文件,这与此答案所示的一样简单,而不是如何将其拆分为板条箱,这更难,这是@BurntSushi5 回答的(也许问题已被编辑?)。 .. @BurntSushi5 的答案应该是公认的答案。这在社交上很尴尬,甚至可能意味着提出一个问题,得到一个非常好的答案,然后将其总结为一个单独的答案并将您的总结标记为已接受的答案。【参考方案5】:

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

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

如何从main.rs 访问sink.rstoilet.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.")

    home 目录中创建一个名为bathroom.rs 的文件:

    导出文件名:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    

    main.rs旁边创建一个名为home.rs的文件

    pub mod浴室.rs文件

    // home.rs
    pub mod bathroom;
    

    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();
    
    

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

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

// 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.rs 相同,home/bathroom/mod.rshome/bathroom.rs 相同。 Rust 做出了这个改变,因为如果你包含一个与目录同名的文件,编译器会感到困惑。 2018 版本(第一个显示的版本)修复了该结构。

查看this repo了解更多信息,查看YouTube video了解整体说明。

最后一件事...避免使用连字符!请改用snake_case

重要提示

必须将所有文件放到顶部,即使***文件不需要深层文件。

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

换句话说,在toilet.rs 中执行pub mod sink;use self::sink;行不通,除非您将它们一直暴露到main.rs

因此,请始终记住将文件放到顶部!

【讨论】:

另一件事,请记住将您的文件和文件夹放在src 文件夹中。【参考方案6】:

导出模块的一个更沙沙的方法,我从Github 中挑选出来的。

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

    mod a;
    mod b;

    pub use a::*;
    pub use b::*;

【讨论】:

作为一个 Rust 新手,这对我来说已经足够好了。谢谢!

以上是关于跨多个文件拆分模块的主要内容,如果未能解决你的问题,请参考以下文章

Maven -- 分模块开发与设计 & 分模块开发示例(拆分POJO & 拆分DAO)

Maven -- 分模块开发与设计 & 分模块开发示例(拆分SERVICE & 拆分CONTROLLER)

如何将 Python 模块拆分为多个文件?

如何将 terraform 文件(main.tf)拆分为多个文件(无模块)?

跨模块的前向引用[重复]

跨多个模块访问单例[重复]