Confused by Rust module system

After reading TRPL chapter about modules and short discussion on IRC I still have no understanding of how Rust's module system works (especially how to apply it to restructure my project). I'll try to describe flow of my thoughts and it would be great if someone could explain me further steps.

I'm writing a game client and have src/main.rs with struct Client in it.
Client instantiates struct State, trait Ai and trait Driver. So I have something like this:

struct Client {
    state: State,   // stores client state
    ai: Ai,         // something implementing Ai trait
                    // to make decisions based on State
    driver: Driver, // underlying networking
}

struct State { ... }

trait Ai {
    fn update (state: &State);
}

struct SimpleAi { ... }
impl Ai for SimpleAi { ... }

struct ComplexAi { ... }
impl Ai for ComplexAi { ... }

trait Driver { ... }

struct MioDriver { ... }
impl Driver for MioDriver { ... }

and because of 2000+ lines of code I want to split this one to smaller pieces in separate files.
Separation is obvious (or not?):

src/main.rs:

struct Client {
    state: State,   // stores client state
    ai: Ai,                      // something implementing Ai trait
                    // to make decisions based on State
    driver: Driver, // underlying networking
}

src/ai.rs:

trait Ai {
    fn update (state: &State);
}

src/ai_simple.rs:

struct SimpleAi { ... }
impl Ai for SimpleAi { ... }

src/ai_complex.rs:

struct ComplexAi { ... }
impl Ai for ComplexAi { ... }

src/driver.rs

trait Driver { ... }

src/driver_mio.rs

struct MioDriver { ... }
impl Driver for MioDriver { ... }

I suppose that I can't just split my code to separate files. I do not understand "mod" exact meaning. By analogy with C or C++ or Python I expected something like "this is module foo" or "this is part of module foo" or "include module foo here" but it's not. It's something different.
Could someone please explain what should I add to this files to make them compile and (what is more important) why?

Thank you.

In my head, I think of the module system as providing name-spacing and importing.

From main you would have:

mod ai;
mod ai_simple;
mod ai_complex;
mod driver;
mod driver_mio;

At the top of the file. This declares that these name-spaces exist at all - it's how the compiler knows to compile them. The code for each module lives either in a file named after that module at the same place in the filesystem it is declared (as you have done) or in a directory with the same name, and a file named mod.rs. In this way, src/ai.rs and src/ai/mod.rs are equivalent.

To use code from a given module, you need to use it. This is the importing side of the equation - it makes the functions/structs/traits available in the current namespace. So, to use the struct SimpleAi from main, you would include:

use ai::SimpleAI;
SimpleAI::new(); // SimpleAI is available without specifying its namespace

You could also do this:

use ai;
ai::SimpleAI::new(); // SimpleAI is available because its module is imported

So, a few simple rules:

  1. For code to be compiled it must be declared as a mod first, or explicitly added to Cargo.toml as a crate.
  2. For code to be used it must be imported into the current namespace via use - either explicitly or made available through its namespace.

Hope that helps,
Adam

1 Like

There are two important things to know. First, each item in a module is private by default and cannot be used outside of that module. You can make an item public with the pub keyword. For example:

pub trait Ai { ... }

In your example, every struct and trait is used outside the module where it's defined (except for Client in the root module), so you should add pub to each of them.

Secondly, a module creates a namespace. If you define trait Ai inside of mod ai then the full path of the trait is ::ai::Ai. If you want to refer to it from within any other module, you can either use that full path:

impl ::ai::Ai for ComplexAi { ... }

or import it into the other module's namespace with the use keyword. Importing a trait with use also lets you call its methods.

use ai::Ai;
impl Ai for ComplexAi { ... }

After making these changes, you'll end up with something like this in your main.rs:

mod ai;
mod ai_simple;
mod ai_complex;
mod driver;
mod driver_mio;

use ai::Ai;
use driver::Driver;

struct Client<A, D> where A: Ai, D: Driver {
    state: State,   // stores client state
    ai: A,          // something implementing Ai trait
                    // to make decisions based on State
    driver: D,      // underlying networking
}

struct State { ... }

Each mod foo; line will get translated to mod foo { /* contents of foo.rs */ } before it is compiled.

Note that modules can contain other modules, so you might want to reorganize this so that instead of ai_simple and ai_complex you have ai::simple and ai::complex. But that is partly a matter of taste.

(Note that I also changed the definition of Client slightly. This has nothing to do with modules; it's because a trait can't be stored directly in a struct field, since it names a family of types rather than a single type. However, you can make a generic struct where the generic parameters are bounded by traits, as above.)

2 Likes

This is almost off-topic, but if you have struct ComplexAi, I would call the module complex_ai not ai_complex.

Thank you guys! Your answers helps me a lot. My project is now modular. :blush:
What confuses me most is that to "use" Ai in ai_simple.rs I have to "mod" it in main.rs and not in ai_simple.rs. :smile:
Main thought for me here is that modules tree is "growing" from main.rs in the case of executable and from lib.rs in the case of library, so main.rs and lib.rs is the roots.

Well, it is a matter of taste, I think. For me with my C background "ai_complex" is more habitually because 1) when I write functions related to ai they all started with ai_*** 2) ai related files grouped by name 3) in the near future I hope they will be ai/simple.rs and ai/comlex.rs. But, to be honest, I thought to call it complex_ai. :wink: