是否有可能避免使用`mod.rs`文件?

13

我阅读了mod文档,发现我必须在每个文件夹中添加mod.rs。例如,在我的项目结构中,我必须像这样定义mod:

src/
  models/
    login.rs
    mod.rs
  routes/
    login_route.rs
    mod.rs
  services/
    login_service.rs
    mod.rs
  main.rs

main.rs 中这样使用:

mod models;
mod routes;
mod services;

为什么要这样做?为什么要这样设计?如果项目变大,只是为了暴露模块而有很多mod.rs文件,这是否是一个好的实践?我不理解。这种方式有什么优势吗?如果我们只是这样做:

crate::models::login;

它非常清晰易懂。

4个回答

20

有些内容在What is the purpose of a mod.rs file?中有解释。

基本上,Rust不会对文件结构做出任何假设,也不会考虑其他未经开发人员声明的.rs文件。相反,它旨在使用模块系统在代码内部建立组织结构。

虽然这与其他一些编程语言不同,但它给了开发人员更多的控制权。由于导入路径与文件结构解耦,您的模块、结构体、函数等可以根据需要重新组织和重新导出。这也允许您准确地声明要编译的内容,从而有助于条件编译

那么Rust什么时候使用文件结构呢?当您声明一个没有主体的模块时:

mod models;

它将查找一个用于该模块的文件。

  • 从一个普通文件中,例如utils.rs,它将寻找一个嵌套的文件/目录:

./utils/models.rs
./utils/models/mod.rs
  • 如果从mod.rs、顶级lib.rsmain.rs中调用(它们在这方面是特殊的),它将查找一个同级的文件/目录:

    ./models.rs
    ./models/mod.rs
    
  • 使用mod.rs的概念允许您将目录像文件一样使用(如果您熟悉Javascript,则类似于index.js)。


    有没有办法避免所有这些mod.rs文件?

    有两个不错的替代方法:

    1. 使用models.rs而不是models/mod.rs

      只需将您的mod.rs文件移动到上一级并将其重命名以匹配其目录即可。 您不需要修改main.rs或任何文件的内容。 您的结构可能如下所示:

      src/
        models/
          login.rs
        routes/
          login_route.rs
        services/
          login_service.rs
        main.rs
        models.rs
        routes.rs
        services.rs
      

      由于它可以为文件提供更具描述性的名称,因此这正逐渐成为首选方法[需要引证]

    2. 只需在 main.rs 中声明你的模块结构:

      这有点不寻常,但是嵌套的模块声明将找到嵌套的文件。 你不需要在任何地方使用 mod.rs

      src/
        models/
          login.rs
        routes/
          login_route.rs
        services/
          login_service.rs
        main.rs
      

      如果您在main.rs中声明模块,像这样:

      mod models {
          mod login; // this will use ./models/login.rs
      }
      mod routes {
          mod login_route; // this will use ./routes/login_route.rs
      }
      mod services {
          mod login_service; // this will use ./services/login_service.rs
      }
      

      这并不是特别推荐的做法。在小型项目中可能效果良好,但随着代码库的增大,会变得笨重难以处理。您可能会想使用mod.rs或上述方法来保持代码结构清晰。


    最后,这些传递性模块不仅仅用于声明其他文件。它们也是一个方便的地方:

    • 放置在其子模块中使用的共享代码
    • 包含文档,介绍其包含的结构体、特质等的设计和/或用法
    • 重新向导深度嵌套的项,使它们更容易访问到您的代码中

    总的来说,模块系统只是另一层抽象,用于保持您的代码良好封装。


    3
    我在几个项目中看到过选项1的使用,但我认为这是一个错误。我不认为models.rs比models/mod.rs更具描述性。如果有什么区别,它反而更不具描述性,并且很令人困惑,因为在找到文件夹之前不清楚该文件的存在意义。让文件夹封装自己的模块设置比在顶层混杂着一堆令人困惑的重复文件更有意义。 - Drew Nutter
    Rust对文件结构不做任何假设,但是在你的回答中详细阐述了许多假设。 - allidoiswin
    @allidoiswin 整个句子解释了这句话的含义:仅仅因为在你的src/目录中有一个.rs文件,并不意味着它被编译。这只是意味着你必须选择哪些文件进行编译,显然你必须按名称引用你想要的文件(mod name;),这些文件相对于当前文件来说。我不确定你是否将其作为一种“假设”加以量化,但每个人有自己的见解。 - kmdreko
    是的,假设要么存在./name.rs文件,要么存在./name/mod.rs文件。问题是为什么不再进一步:如果既不存在./name.rs又不存在./name/mod.rs,但存在./name(或./name/<file>),那么将mod name;视为与存在空的./name/mod.rs文件相同。 - allidoiswin
    @allidoiswin 听起来这样做只会产生更多的假设,而不是减少。但无论如何,StackOverflow并不是抱怨语言设计的地方。 - kmdreko

    6
    自从Rust 2018版发布以来,mod.rs文件已经变成可选项:

    (参考链接)

    In Rust 2015, if you have a sub-module:

    // This `mod` declaration looks for the `foo` module in
    // `foo.rs` or `foo/mod.rs`.
    mod foo;
    

    It can live in foo.rs or foo/mod.rs. If it has sub-modules of its own, it must be foo/mod.rs. So a bar sub-module of foo would live at foo/bar.rs.

    In Rust 2018 the restriction that a module with sub-modules must be named mod.rs is lifted. foo.rs can just be foo.rs, and the sub-module is still foo/bar.rs. This eliminates the special name, and if you have a bunch of files open in your editor, you can clearly see their names, instead of having a bunch of tabs named mod.rs.

    其他资源:


    并不完全是可选的,它仍然必须存在,只是以不同的名称(和不同的路径)存在。 - Chayim Friedman
    @ChayimFriedman 你的评论是错误的或者写得令人困惑,或者两者兼而有之。这里的答案是正确的。在所有情况下,mod.rs并不是必需的。简单来说,我引用了Rust 2018源码:"foo.rs和foo/子目录可以共存;当将子模块放在子目录中时,不再需要mod.rs文件。" - undefined

    2

    添加路径属性

    就像您在问题中提到的那样,但有一个小注释。这个例子应该展示如何做到这一点:

    示例

    使用与 OP 问题中相同的目录结构:

    src/
      models/
        login.rs
      routes/
        login_route.rs
      services/
        login_service.rs
      main.rs
    

    main.rs文件可以按照以下方式声明模块:

    #[path = "models/login.rs"]
    mod models;
    
    #[path = "routes/login_route.rs"]
    mod routes;
    
    #[path = "services/login_service.rs"]
    mod services;
    

    这种方法的优点是您的mod <module_name>不需要与原始文件或目录相同,从而在需要时允许使用较短的名称。

    然而,缺点是它无法引用子目录中的多个模块。 (例如,如果models/目录中有更多模块,则会出现问题。)


    2
    另一个缺点,也是我没有在自己的答案中提到它作为解决方案的原因:这是一个非常诱人的工具,因为它似乎允许您随心所欲地组织您的文件,但不应该用于盲目地访问另一个文件。如果您有两个#[path = ...]属性指向同一个文件,则它们将成为具有相同内容的不同模块,因此可能会出现奇怪的警告或混淆的错误。 - kmdreko
    没错。对于某些特定的用例,这已经足够了。这是一种方法,也许不是最好的... - diviquery

    0
    问题似乎进一步发展为“是否可以避免使用mod.rs<directory>.rs”,或者“如果两者都不存在,但./<directory>(或./<directory>/<file>)存在,为什么不将mod name;视为与空的./<directory>/mod.rs相同”,正如@allidoiswin所提到的。
    这样一个文件可能是不可避免的。这可能是因为目录只表示模块树中的一个节点的存在,并且不描述该节点。然而,在.rs文件中的mod可以完全描述节点的信息(例如,模块中的函数,子模块的可见性)。
    示例
    假设我们想将main.rs中的mod house移动到一个目录中。
    .
    └── main.rs
    

    // main.rs
    mod house {
        mod guest {}
        pub mod host { 
            pub fn clean() {
                super::clean_house();
            }
        }
    
        fn clean_house() {
            println!("Cleaned!");
        }
    }
    

    所以我们这样创建目录,并且希望避免使用house.rs
    .
    ├── house
    │   ├── guest.rs
    │   └── host.rs
    └── main.rs
    

    // main.rs
    mod house;
    fn main() {
        house::host::clean();
    }
    
    // host.rs
    pub fn clean() {
        super::clean_house();
    }
    

    但是我们找不到地方来编写 clean_house() 函数并给 mod host 可见性,因为 house 是一个目录而不是一个 Rust 代码文件。
    解决方案是添加一个 house.rs 文件,它提供了目录 house 的额外信息。
    .
    ├── house
    │   ├── guest.rs
    │   └── host.rs
    ├── house.rs
    └── main.rs
    

    // house.rs
    mod guest;
    pub mod host;
    
    fn clean_house() {
        println!("Cleaned!");
    }
    

    如果我们考虑到househouse.rs是相互协作的,并且house目录是存储house.rs子模块的地方,那么可能存在一些一致性。

    1
    我不认为这是适合的地方。是的,这个问题是在评论中提出的,但它并不是这个 Stack Overflow 问题中的问题。如果你认为这是重要信息,你可以提出一个问题并自己回答,但要注意你的问题很可能会被关闭,因为它基于观点(也许还会被投票反对)。 - Chayim Friedman

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接