Split a module across several files
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.
The Rust module rules are:
- A source file is just its own module (except the special files main.rs, lib.rs and mod.rs).
- A directory is just a module path component.
- 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.
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.
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.