Split a module across several files
Asked Answered
T

7

147

I want to have a module with multiple structs in it, each in its own file. Using a Math module as an example:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

I want each struct to be in the same module, which I would use from my main file, like so:

use Math::Vector;

fn main() {
  // ...
}

However Rust's module system (which is a bit confusing to begin with) does not provide an obvious way to do this. It seems to only allow you to have your entire module in one file. Is this un-rustic? If not, how do I do this?

Turret answered 23/3, 2014 at 20:53 Comment(3)
I interpreted "I want to have a module with multiple structs in it, each in it's own file." to mean that you wanted each struct definition in its own file.Takeover
This would not be considered rustic, although the module system certainly permits such structuring. It is generally preferable for a module path to directly correspond to a file system path, e.g. struct foo::bar::Baz should be defined in foo/bar.rs or foo/bar/mod.rs.Ari
Only it seems to end up more like crate_name::module::foo::Foo over and over again, complete with overly verbose, useless, stuttering... The fact that it is for unfathomable reasons considered 'abnormal' in the rust scene to want to organise files using directories, with classes/structs/traits/interfaces/etc/etc. logical split out into files, is honestly quite disturbing imo...Tweeze
T
138

Rust's module system is actually incredibly flexible and will let you expose whatever kind of structure you want while hiding how your code is structured in files.

I think the key here is to make use of pub use, which will allow you to re-export identifiers from other modules. There is precedent for this in Rust's std::io crate where some types from sub-modules are re-exported for use in std::io.

Edit (2019-08-25): the following part of the answer was written quite some time ago. It explains how to setup such a module structure with rustc alone. Today, one would usually use Cargo for most use cases. While the following is still valid, some parts of it (e.g. #![crate_type = ...]) might seem strange. This is not the recommended solution.

To adapt your example, we could start with this directory structure:

src/
  lib.rs
  vector.rs
main.rs

Here's your main.rs:

extern crate math;

use math::vector;

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

And your src/lib.rs:

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

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

And finally, 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![] }
        }
    }
}

And this is where the magic happens. We've defined a sub-module math::vector::vector_a which has some implementation of a special kind of vector. But we don't want clients of your library to care that there is a vector_a sub-module. Instead, we'd like to make it available in the math::vector module. This is done with pub use self::vector_a::VectorA, which re-exports the vector_a::VectorA identifier in the current module.

But you asked how to do this so that you could put your special vector implementations in different files. This is what the mod vector_b; line does. It instructs the Rust compiler to look for a vector_b.rs file for the implementation of that module. And sure enough, here's our src/vector_b.rs file:

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

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

From the client's perspective, the fact that VectorA and VectorB are defined in two different modules in two different files is completely opaque.

If you're in the same directory as main.rs, you should be able to run it with:

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

In general, the "Crates and Modules" chapter in the Rust book is pretty good. There are lots of examples.

Finally, the Rust compiler also looks in sub-directories for you automatically. For example, the above code will work unchanged with this directory structure:

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

The commands to compile and run remain the same as well.

Takeover answered 23/3, 2014 at 22:5 Comment(8)
I believe you misunderstood what I meant by "vector". I was speaking of vector as in the mathematical quantity, not the data structure. Also, I'm not running the lastest version of rust, because it's a bit of a pain to build on windows.Turret
+1 Wasn't exactly what I needed, but pointed me in the right direction.Turret
@EpicPineapple Indeed! And a Vec can be used to represent such vectors. (For larger N, of course.)Takeover
@EpicPineapple Could you explain what my answer has missed so that I can update it? I'm struggling to see the difference between your answer and mine other than using math::Vec2 instead of math::vector::Vec2. (i.e., Same concept but one module deeper.)Takeover
You showed how to use it as a separate crate. I was just using math as an example, I wanted it to be in the same crate. Also, the #[deriving(Show)] attribute does not work in 0.9(not really a fault on your answer though, as I should just build it from source)Turret
I don't see that criteria in your question. As far as I can see, I've answered the question asked. (Which was really asking how to divorce modules from files.) Sorry about it not working on Rust 0.9, but that comes with the territory of using an unstable language.Takeover
I'm trying this out but when I add #[crate_id = "tower"]; to lib.rs I get the error: error: expected item after attributes --> src/lib.rs:1:21 | 1 | #[crate_id = "tower"]; | ^ What did I miss? (using rustc 1.29.2 (17a9dc751 2018-10-05))Attaint
I love rust. < 1 month playing with it and i'm totally fallend in love with rustJard
D
66

The Rust module rules are:

  1. A source file is just its own module (except the special files main.rs, lib.rs and mod.rs).
  2. A directory is just a module path component.
  3. The file mod.rs is just the directory's module.

The file matrix.rs1 in the directory math is just the module math::matrix. It's easy. What you see on your filesystem you also find in your source code. This is an one-to-one correspondence of file paths and module paths2.

So you can import a struct Matrix with use math::matrix::Matrix, because the struct is inside the file matrix.rs in a directory math. Not happy? You'd prefer use math::Matrix; very much instead, don't you? It's possible. Re-export the identifier math::matrix::Matrix in math/mod.rs with:

pub use self::math::Matrix;

There's another step to get this working. Rust needs a module declaration to load the module. Add a mod math; in main.rs. If you don't do that, you get an error message from the compiler when importing like this:

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

The hint is misleading here. There's no need for additional crates, except of course you really intend to write a separate library.

Add this at the top of main.rs:

mod math;
pub use math::Matrix;

The module declaration is also neccessary for the submodules vector, matrix and complex, because math needs to load them to re-export them. A re-export of an identifier only works if you have loaded the module of the identifier. This means, to re-export the identifier math::matrix::Matrix you need to write mod matrix;. You can do this in math/mod.rs. Therefore create the file with this content:

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

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

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

Aaaand you are done.


1Source file names usually start with a lowercase letter in Rust. That's why I use matrix.rs and not Matrix.rs.

2Java's different. You declare the path with package, too. It's redundant. The path is already evident from the source file location in the filesystem. Why repeat this information in a declaration at the top of the file? Of course sometimes it's easier to have a quick look at the source code instead of finding out the filesystem location of the file. I can understand people who say it's less confusing.

Doe answered 23/5, 2015 at 22:44 Comment(3)
The tl;dr at the top should be in rust documentation!Chane
I'm ever thankful that there are people around to explain this stuff, because jesus christ if the rust module system isn't a total shitshow... Putting aside the fact there are crates, packages, and modules before you've even had time to sit down, there's a bewildering array of syntax that most other languages seem to have got on alright without. extern crates, mod this, use that, pub use the other, plus a whole separate raft of syntax in the cargo files...Tweeze
It's not a shitshow, but I agree if you tell me it is a bit weird.Doe
E
37

Rusts purists will probably call me a heretic and hate this solution, but this is much simpler: just do each thing in its own file, then use the "include!" macro in mod.rs:

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

That way you get no added nested modules, and avoid complicated export and rewrite rules. Simple, effective, no fuss.

Ehudd answered 2/2, 2016 at 16:7 Comment(7)
You just threw out namespacing. Changing one file in a way unrelated to another can now break other files. Your use of 'use' becomes leaky (ie everything is like use super::*). You can't hide code from other files (which is important for unsafe-using safe abstractions)Bombardon
Yep, but that is exactly what I wanted in that case: have several files that behave as just one for namespacing purposes. I'm not advocating this for every case, but it's a useful workaround if you don't want to deal with the "one module per file" method, for whatever reason.Ehudd
This is great, I have a part of my module that is internal only but self-contained, and this did the trick. I will try to get the proper module solution to work too, but it's not anywhere near as easy.Char
i don't care being called heretic, your solution is convenient!Marsupial
This solution only works if you avoid using the same modules in the files. That is, taking your exmple as reference, it is not possible for both meth/Matrix.rs and math/Vector.rs to have use rand; Furthermore, you cannot have tests submodules in the respective files.Thrippence
I am using the same strategy because within a module, if it's large enough, I want a separate file for each important struct/enum. For a number of reasons. Meaningful names (vs "mod.rs") and faster navigation are part of that.Amboina
@momchil-atanasov I place all the use statements in the "mod.rs" file and it has the includes. As a general rule any given struct use most of them. Could you clarify what you meant there, I'm fairly new.Amboina
T
29

Alright, fought my compiler for a while and finally got it to work(thanks to BurntSushi for pointing out 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};
}

math/mod.rs:

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

math/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...
}

Other structs could be added in the same manner. NOTE: compiled with 0.9, not master.

Turret answered 24/3, 2014 at 1:25 Comment(7)
Note that your use of mod math; in main.rs couples your main program with your library. If you want your math module to be independent, you'll need to compile it separately and link to it with extern crate math (as shown in my answer). In Rust 0.9, it's possible that the syntax is extern mod math instead.Takeover
It really would've been fair to mark BurntSushi5's answer as the correct one.Systemic
@NSAddict No. To divorce modules from files you don't need to create a separate crate. It's over-engineered.Doe
I found this answer easier to follow. Whatever Rust's virtues are, some things such as modules / namespaces are very hard to understand.Navarro
Why isn't this the top-voted answer?? The question asked how to break up the project into a few files, which is as simple as this answer shows, not how to split it up into crates, which is harder and is what @Takeover answered (maybe the question was edited?)...Spruce
@Takeover 's answer should have been the accepted answer. It is socially awkward and maybe even mean to ask a question, get a very nice answer, then summarize it as a separate answer and mark your summary as the accepted answer.Sixpack
This is nice and concise. @Takeover 's answer only confused me more and I still didn't solve my problem after reading it since it uses crate_id macros etc. This answer made use of the knowledge that was confusing to me and shows a simple solution that answers the original simple question. I think it's fair this is the accepted answer. If the question was about decoupling that would be a different story.Sadoff
P
17

I'd like to add in here how you include Rust files when they are deeply nested. I have the following structure:

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

How do you access sink.rs or toilet.rs from main.rs?

As others have mentioned, Rust has no knowledge of files. Instead it sees everything as modules and submodules. To access the files inside the bathroom directory you need to export them or barrel them to the top. You do this by specifying a filename with the directory you'd like to access and pub mod filename_inside_the_dir_without_rs_ext inside the file.

Example.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Create a file called bathroom.rs inside the home directory:

  2. Export the filenames:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
  3. Create a file called home.rs next to main.rs

  4. pub mod the bathroom.rs file

    // home.rs
    pub mod bathroom;
    
  5. Within 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 statements can be also used:

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

Including other sibling modules (files) within submodules

In the case you'd like to use sink.rs from toilet.rs, you can call the module by specifying the self or super keywords.

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

Final Directory Structure

You'd end up with something like this:

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

The structure above only works with Rust 2018 onwards. The following directory structure is also valid for 2018, but it's how 2015 used to work.

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

In which home/mod.rs is the same as ./home.rs and home/bathroom/mod.rs is the same as home/bathroom.rs. Rust made this change because the compiler would get confused if you included a file with the same name as the directory. The 2018 version (the one shown first) fixes that structure.

See this repo for more information and this YouTube video for an overall explanation.

One last thing... avoid hyphens! Use snake_case instead.

Important Note

You must barrel all the files to the top, even if deep files aren't required by top-level ones.

This means, that for sink.rs to discover toilet.rs, you'd need to barrel them by using the methods above all the way up to main.rs!

In other words, doing pub mod sink; or use self::sink; inside toilet.rs will not work unless you have exposed them all the way up to main.rs!

Therefore, always remember to barrel your files to the top!

Pruitt answered 25/3, 2020 at 4:50 Comment(2)
... that is insanely convoluted compared to C++, which is saying somethingBunk
Another thing, remember to have your files and folders inside the src folder.Pruitt
R
5

A more rustlings method to export module, which I picked up from Github.

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

    mod a;
    mod b;

    pub use a::*;
    pub use b::*;
}
Reeta answered 31/1, 2021 at 6:30 Comment(1)
As a Rust newbie, this is good enough for me. Thank you!Blalock
D
2

Adjusting the question's example directory and file names to conform to Rust naming conventions:

main.rs
math.rs
math/
  vector.rs
  matrix.rs
  complex.rs

Make sure to export the public symbols (types, functions, etc.) in each of the files in the math directory by preceding them with the keyword pub.

Define math.rs:

mod vector;
pub use vector::*;

mod matrix;
pub use matrix::*;

mod complex;
pub use complex::*;

The above file keeps the sub-modules of math private but the submodules' public symbols are exported from module math. This effectively flattens the module structure.

Use math::Vector in main.rs:

mod math;

use crate::math::Vector;

fn main() {
  // ...
}
Dogleg answered 9/1, 2023 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.