Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to include files from same directory in a module using Cargo/Rust?

I have a Cargo project consisting of three files in the same directory: main.rs, mod1.rs and mod2.rs.

I want to import functions from mod2.rs to mod1.rs the same way I would import functions from mod1.rs to main.rs.
I've read about the file structure required but I don't get it - naming all the imported files mod will lead to minor confusion in the editor and also this just complicates the project hierarchy.

Is there a way to import/include files independently of directory structure as I would in Python or C++?

main.rs:

mod mod1; // Works

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}

mod1.rs:

mod mod2; // Fails

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}

mod2.rs:

pub fn mod2fn() {
    println!("2");
}

Building results in:

error: cannot declare a new module at this location
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^
  |
note: maybe move this module `src` to its own directory via `src/mod.rs`
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^
note: ... or maybe `use` the module `mod2` instead of possibly redeclaring it
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^

I can't use it as it doesn't exist as a module anywhere, and I don't want to modify the directory structure.

like image 462
Neo Avatar asked Oct 19 '17 12:10

Neo


3 Answers

All of your top level module declarations should go in main.rs, like so:

mod mod1;
mod mod2;

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}

You can then use crate::mod2 inside mod1:

use crate::mod2;

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}

I'd recommend reading the chapter on modules in the new version of the Rust book if you haven't already - they can be a little confusing for people who are new to the language.

like image 97
Joe Clay Avatar answered Oct 31 '22 06:10

Joe Clay


If you don't want your mod statements all in your main file (eg: in main.rs you won't use some public members inside some module, in this example it is mod2) you can do the following:

structure your src this way:

main.rs
my_module:
  mod.rs
  mod1.rs
  mod2.rs

then you can just mod my_module and use my_module::mod1, like so:

main.rs:

mod my_module;
use my_module::mod1;

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}

my_module/mod.rs

pub mod mod1;
pub mod mod2;

my_module/mod1.rs

use super::mod2;

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}
like image 23
giuliano-oliveira Avatar answered Oct 31 '22 06:10

giuliano-oliveira


Every file is a module and cannot import another without creating a new nested module.

a. Define modules in module index file

As @giuliano-oliveira's answer recommends.

Add pub mod mod1; pub mod mod2; in src/lib.rs / src/main.rs / src/foo/mod.rs.

b. Use #[path]

main.rs

#[path = "./mod2.rs"]
mod mod2;

fn run() { mod2::mod2fn() }

Why?

This is a common pitfall for new Rust devs and understandably so.

The reason for the confusion comes from an inconsistency in behavior of mod X for files in the same folder. You can use mod X in lib.rs which appears to import a file adjacent to it, but you can't do the same in mod1.rs or mod2.rs.

The code of every file belongs to a module. The full path of the file's module (e.g. foo::bar::baz) rather than the location of the file, determines how it resolves mod X. You can think of it as every module having a fixed spiritual home, but it may have members defined further up in the hierarchy (e.g. src/lib.rs might contain: mod foo { mod bar { pub fn hello() {} } } - although then you cannot use mod foo; alone in lib.rs).

In main.rs, you are in the top-level module crate.

mod mod1; creates a new module mod1, and adds the contents of ./mod1.ts to that module.

So all code inside ./mod1.rs is inside the crate::mod1 module.

When you call use mod2 inside ./mod1.rs, it sees that it is inside crate::mod1, whose spiritual home dir is src/mod1, and looks for either:

  • src/mod1/mod2.rs
  • src/mod1/mod2/mod.rs

The complexity comes from allowing modules to be directories and also files, instead of forcing each module to be its own directory (maybe trying to avoid the Java folder structure) which would have removed the ambiguity.

The key thing to remember is that lib.rs and mod.rs are special files that behave differently to other files in a directory.

They will always be in the module described by the parent folder path (e.g. src/foo/bar/mod.rs = foo::bar) while all other files belong to their own modules (src/foo/bar/baz.rs = foo::bar::baz).

The Rustonic Way

Rust has some opinions on this.

Using mod.rs is not recommended anymore, which is good because it has this special behavior from its siblings. But lib.rs and main.rs are still special.

If you want to put tests alongside your code (foo.rs + foo_test.rs), it's recommended you don't. I don't like this, so I use the path thing above, which I think is fine for tests because they are not exported anywhere. Having to declare tests in the module above feels wrong, and I don't like having to use foo/test.rs either.

like image 4
vaughan Avatar answered Oct 31 '22 04:10

vaughan